| 1 | /* $Id$ $Revision$ */ |
| 2 | /* vim:set shiftwidth=4 ts=8: */ |
| 3 | |
| 4 | /************************************************************************* |
| 5 | * Copyright (c) 2011 AT&T Intellectual Property |
| 6 | * All rights reserved. This program and the accompanying materials |
| 7 | * are made available under the terms of the Eclipse Public License v1.0 |
| 8 | * which accompanies this distribution, and is available at |
| 9 | * http://www.eclipse.org/legal/epl-v10.html |
| 10 | * |
| 11 | * Contributors: See CVS logs. Details at http://www.graphviz.org/ |
| 12 | *************************************************************************/ |
| 13 | |
| 14 | |
| 15 | #include "config.h" |
| 16 | |
| 17 | #include <stdlib.h> |
| 18 | #include <stddef.h> |
| 19 | #include <string.h> |
| 20 | #include <fcntl.h> |
| 21 | |
| 22 | #include "gvplugin_render.h" |
| 23 | #include "gvio.h" |
| 24 | #include "gd.h" |
| 25 | |
| 26 | #ifdef HAVE_GD_PNG |
| 27 | |
| 28 | /* for N_GNEW() */ |
| 29 | #include "memory.h" |
| 30 | |
| 31 | /* for gvcolor_t */ |
| 32 | #include "color.h" |
| 33 | |
| 34 | /* for late_double() */ |
| 35 | #include "agxbuf.h" |
| 36 | #include "utils.h" |
| 37 | |
| 38 | /* for wind() */ |
| 39 | #include "pathutil.h" |
| 40 | |
| 41 | extern shape_kind shapeOf(node_t *); |
| 42 | extern pointf gvrender_ptf(GVJ_t *job, pointf p); |
| 43 | extern pointf Bezier(pointf * V, int degree, double t, pointf * Left, pointf * Right); |
| 44 | |
| 45 | typedef enum { FORMAT_VRML, } format_type; |
| 46 | |
| 47 | #define BEZIERSUBDIVISION 10 |
| 48 | |
| 49 | /* static int N_pages; */ |
| 50 | /* static point Pages; */ |
| 51 | static double Scale; |
| 52 | static double MinZ; |
| 53 | /* static int onetime = TRUE; */ |
| 54 | static int Saw_skycolor; |
| 55 | |
| 56 | static gdImagePtr im; |
| 57 | static FILE *PNGfile; |
| 58 | static int IsSegment; /* set true if edge is line segment */ |
| 59 | static double CylHt; /* height of cylinder part of edge */ |
| 60 | static double EdgeLen; /* length between centers of endpoints */ |
| 61 | static double HeadHt, TailHt; /* height of arrows */ |
| 62 | static double Fstz, Sndz; /* z values of tail and head points */ |
| 63 | |
| 64 | /* gdirname: |
| 65 | * Returns directory pathname prefix |
| 66 | * Code adapted from dgk |
| 67 | */ |
| 68 | static char *gdirname(char *pathname) |
| 69 | { |
| 70 | char *last; |
| 71 | |
| 72 | /* go to end of path */ |
| 73 | for (last = pathname; *last; last++); |
| 74 | /* back over trailing '/' */ |
| 75 | while (last > pathname && *--last == '/'); |
| 76 | /* back over non-slash chars */ |
| 77 | for (; last > pathname && *last != '/'; last--); |
| 78 | if (last == pathname) { |
| 79 | /* all '/' or "" */ |
| 80 | if (*pathname != '/') |
| 81 | *last = '.'; |
| 82 | /* preserve // */ |
| 83 | else if (pathname[1] == '/') |
| 84 | last++; |
| 85 | } else { |
| 86 | /* back over trailing '/' */ |
| 87 | for (; *last == '/' && last > pathname; last--); |
| 88 | /* preserve // */ |
| 89 | if (last == pathname && *pathname == '/' && pathname[1] == '/') |
| 90 | last++; |
| 91 | } |
| 92 | last++; |
| 93 | *last = '\0'; |
| 94 | |
| 95 | return pathname; |
| 96 | } |
| 97 | |
| 98 | static char *nodefilename(const char *filename, node_t * n, char *buf) |
| 99 | { |
| 100 | static char *dir; |
| 101 | static char disposable[1024]; |
| 102 | |
| 103 | if (dir == 0) { |
| 104 | if (filename) |
| 105 | dir = gdirname(strcpy(disposable, filename)); |
| 106 | else |
| 107 | dir = "." ; |
| 108 | } |
| 109 | sprintf(buf, "%s/node%d.png" , dir, AGSEQ(n)); |
| 110 | return buf; |
| 111 | } |
| 112 | |
| 113 | static FILE *nodefile(const char *filename, node_t * n) |
| 114 | { |
| 115 | FILE *rv; |
| 116 | char buf[1024]; |
| 117 | |
| 118 | rv = fopen(nodefilename(filename, n, buf), "wb" ); |
| 119 | return rv; |
| 120 | } |
| 121 | |
| 122 | #define NODE_PAD 1 |
| 123 | |
| 124 | static pointf vrml_node_point(GVJ_t *job, node_t *n, pointf p) |
| 125 | { |
| 126 | pointf rv; |
| 127 | |
| 128 | /* make rv relative to PNG canvas */ |
| 129 | if (job->rotation) { |
| 130 | rv.x = ( (p.y - job->pad.y) - ND_coord(n).y + ND_lw(n) ) * Scale + NODE_PAD; |
| 131 | rv.y = (-(p.x - job->pad.x) + ND_coord(n).x + ND_ht(n) / 2.) * Scale + NODE_PAD; |
| 132 | } else { |
| 133 | rv.x = ( (p.x - job->pad.x) - ND_coord(n).x + ND_lw(n) ) * Scale + NODE_PAD; |
| 134 | rv.y = (-(p.y - job->pad.y) + ND_coord(n).y + ND_ht(n) / 2.) * Scale + NODE_PAD; |
| 135 | } |
| 136 | return rv; |
| 137 | } |
| 138 | |
| 139 | static int color_index(gdImagePtr im, gvcolor_t color) |
| 140 | { |
| 141 | int alpha; |
| 142 | |
| 143 | /* convert alpha (normally an "opacity" value) to gd's "transparency" */ |
| 144 | alpha = (255 - color.u.rgba[3]) * gdAlphaMax / 255; |
| 145 | |
| 146 | if(alpha == gdAlphaMax) |
| 147 | return (gdImageGetTransparent(im)); |
| 148 | else |
| 149 | return (gdImageColorResolveAlpha(im, |
| 150 | color.u.rgba[0], |
| 151 | color.u.rgba[1], |
| 152 | color.u.rgba[2], |
| 153 | alpha)); |
| 154 | } |
| 155 | |
| 156 | static int set_penstyle(GVJ_t * job, gdImagePtr im, gdImagePtr brush) |
| 157 | { |
| 158 | obj_state_t *obj = job->obj; |
| 159 | int i, pen, pencolor, transparent, width, dashstyle[40]; |
| 160 | |
| 161 | pen = pencolor = color_index(im, obj->pencolor); |
| 162 | transparent = gdImageGetTransparent(im); |
| 163 | if (obj->pen == PEN_DASHED) { |
| 164 | for (i = 0; i < 20; i++) |
| 165 | dashstyle[i] = pencolor; |
| 166 | for (; i < 40; i++) |
| 167 | dashstyle[i] = transparent; |
| 168 | gdImageSetStyle(im, dashstyle, 20); |
| 169 | pen = gdStyled; |
| 170 | } else if (obj->pen == PEN_DOTTED) { |
| 171 | for (i = 0; i < 2; i++) |
| 172 | dashstyle[i] = pencolor; |
| 173 | for (; i < 24; i++) |
| 174 | dashstyle[i] = transparent; |
| 175 | gdImageSetStyle(im, dashstyle, 24); |
| 176 | pen = gdStyled; |
| 177 | } |
| 178 | width = obj->penwidth * job->scale.x; |
| 179 | if (width < PENWIDTH_NORMAL) |
| 180 | width = PENWIDTH_NORMAL; /* gd can't do thin lines */ |
| 181 | gdImageSetThickness(im, width); |
| 182 | /* use brush instead of Thickness to improve end butts */ |
| 183 | if (width != PENWIDTH_NORMAL) { |
| 184 | brush = gdImageCreate(width, width); |
| 185 | gdImagePaletteCopy(brush, im); |
| 186 | gdImageFilledRectangle(brush, 0, 0, width - 1, width - 1, pencolor); |
| 187 | gdImageSetBrush(im, brush); |
| 188 | if (pen == gdStyled) |
| 189 | pen = gdStyledBrushed; |
| 190 | else |
| 191 | pen = gdBrushed; |
| 192 | } |
| 193 | return pen; |
| 194 | } |
| 195 | |
| 196 | /* warmed over VRML code starts here */ |
| 197 | |
| 198 | static void vrml_begin_page(GVJ_t *job) |
| 199 | { |
| 200 | Scale = (double) DEFAULT_DPI / POINTS_PER_INCH; |
| 201 | gvputs(job, "#VRML V2.0 utf8\n" ); |
| 202 | |
| 203 | Saw_skycolor = FALSE; |
| 204 | MinZ = MAXDOUBLE; |
| 205 | gvputs(job, "Group { children [\n" ); |
| 206 | gvputs(job, " Transform {\n" ); |
| 207 | gvprintf(job, " scale %.3f %.3f %.3f\n" , .0278, .0278, .0278); |
| 208 | gvputs(job, " children [\n" ); |
| 209 | } |
| 210 | |
| 211 | static void vrml_end_page(GVJ_t *job) |
| 212 | { |
| 213 | double d, z; |
| 214 | box bb = job->boundingBox; |
| 215 | |
| 216 | d = MAX(bb.UR.x - bb.LL.x,bb.UR.y - bb.LL.y); |
| 217 | /* Roughly fill 3/4 view assuming FOV angle of M_PI/4. |
| 218 | * Small graphs and non-square aspect ratios will upset this. |
| 219 | */ |
| 220 | z = (0.6667*d)/tan(M_PI/8.0) + MinZ; /* fill 3/4 of view */ |
| 221 | |
| 222 | if (!Saw_skycolor) |
| 223 | gvputs(job, " Background { skyColor 1 1 1 }\n" ); |
| 224 | gvputs(job, " ] }\n" ); |
| 225 | gvprintf(job, " Viewpoint {position %.3f %.3f %.3f}\n" , |
| 226 | Scale * (bb.UR.x + bb.LL.x) / 72., |
| 227 | Scale * (bb.UR.y + bb.LL.y) / 72., |
| 228 | Scale * 2 * z / 72.); |
| 229 | gvputs(job, "] }\n" ); |
| 230 | } |
| 231 | |
| 232 | static void vrml_begin_node(GVJ_t *job) |
| 233 | { |
| 234 | obj_state_t *obj = job->obj; |
| 235 | node_t *n = obj->u.n; |
| 236 | double z = obj->z; |
| 237 | int width, height; |
| 238 | int transparent; |
| 239 | |
| 240 | gvprintf(job, "# node %s\n" , agnameof(n)); |
| 241 | if (z < MinZ) |
| 242 | MinZ = z; |
| 243 | if (shapeOf(n) != SH_POINT) { |
| 244 | PNGfile = nodefile(job->output_filename, n); |
| 245 | |
| 246 | width = (ND_lw(n) + ND_rw(n)) * Scale + 2 * NODE_PAD; |
| 247 | height = (ND_ht(n) ) * Scale + 2 * NODE_PAD; |
| 248 | im = gdImageCreate(width, height); |
| 249 | |
| 250 | /* make background transparent */ |
| 251 | transparent = gdImageColorResolveAlpha(im, |
| 252 | gdRedMax - 1, gdGreenMax, |
| 253 | gdBlueMax, gdAlphaTransparent); |
| 254 | gdImageColorTransparent(im, transparent); |
| 255 | } |
| 256 | } |
| 257 | |
| 258 | static void vrml_end_node(GVJ_t *job) |
| 259 | { |
| 260 | if (im) { |
| 261 | gdImagePng(im, PNGfile); |
| 262 | fclose(PNGfile); |
| 263 | gdImageDestroy(im); |
| 264 | im = NULL; |
| 265 | } |
| 266 | } |
| 267 | |
| 268 | static void vrml_begin_edge(GVJ_t *job) |
| 269 | { |
| 270 | obj_state_t *obj = job->obj; |
| 271 | edge_t *e = obj->u.e; |
| 272 | |
| 273 | IsSegment = 0; |
| 274 | gvprintf(job, "# edge %s -> %s\n" , agnameof(agtail(e)), agnameof(aghead(e))); |
| 275 | gvputs(job, " Group { children [\n" ); |
| 276 | } |
| 277 | |
| 278 | static void |
| 279 | finishSegment (GVJ_t *job, edge_t *e) |
| 280 | { |
| 281 | pointf p0 = gvrender_ptf(job, ND_coord(agtail(e))); |
| 282 | pointf p1 = gvrender_ptf(job, ND_coord(aghead(e))); |
| 283 | double o_x, o_y, o_z; |
| 284 | double x, y, y0, z, theta; |
| 285 | |
| 286 | o_x = ((double)(p0.x + p1.x))/2; |
| 287 | o_y = ((double)(p0.y + p1.y))/2; |
| 288 | o_z = (Fstz + Sndz)/2; |
| 289 | /* Compute rotation */ |
| 290 | /* Pick end point with highest y */ |
| 291 | if (p0.y > p1.y) { |
| 292 | x = p0.x; |
| 293 | y = p0.y; |
| 294 | z = Fstz; |
| 295 | } |
| 296 | else { |
| 297 | x = p1.x; |
| 298 | y = p1.y; |
| 299 | z = Sndz; |
| 300 | } |
| 301 | /* Translate center to the origin */ |
| 302 | x -= o_x; |
| 303 | y -= o_y; |
| 304 | z -= o_z; |
| 305 | if (p0.y > p1.y) |
| 306 | theta = acos(2*y/EdgeLen) + M_PI; |
| 307 | else |
| 308 | theta = acos(2*y/EdgeLen); |
| 309 | if (!x && !z) /* parallel to y-axis */ |
| 310 | x = 1; |
| 311 | |
| 312 | y0 = (HeadHt-TailHt)/2.0; |
| 313 | gvputs(job, " ]\n" ); |
| 314 | gvprintf(job, " center 0 %.3f 0\n" , y0); |
| 315 | gvprintf(job, " rotation %.3f 0 %.3f %.3f\n" , -z, x, -theta); |
| 316 | gvprintf(job, " translation %.3f %.3f %.3f\n" , o_x, o_y - y0, o_z); |
| 317 | gvputs(job, " }\n" ); |
| 318 | } |
| 319 | |
| 320 | static void vrml_end_edge(GVJ_t *job) |
| 321 | { |
| 322 | if (IsSegment) |
| 323 | finishSegment(job, job->obj->u.e); |
| 324 | gvputs(job, "] }\n" ); |
| 325 | } |
| 326 | |
| 327 | extern void gdgen_text(gdImagePtr im, pointf spf, pointf epf, int fontcolor, double fontsize, int fontdpi, double fontangle, char *fontname, char *str); |
| 328 | |
| 329 | static void vrml_textspan(GVJ_t *job, pointf p, textspan_t * span) |
| 330 | { |
| 331 | obj_state_t *obj = job->obj; |
| 332 | pointf spf, epf, q; |
| 333 | |
| 334 | if (! obj->u.n || ! im) /* if not a node - or if no im (e.g. for cluster) */ |
| 335 | return; |
| 336 | |
| 337 | switch (span->just) { |
| 338 | case 'l': |
| 339 | break; |
| 340 | case 'r': |
| 341 | p.x -= span->size.x; |
| 342 | break; |
| 343 | default: |
| 344 | case 'n': |
| 345 | p.x -= span->size.x / 2; |
| 346 | break; |
| 347 | } |
| 348 | q.x = p.x + span->size.x; |
| 349 | q.y = p.y; |
| 350 | |
| 351 | spf = vrml_node_point(job, obj->u.n, p); |
| 352 | epf = vrml_node_point(job, obj->u.n, q); |
| 353 | |
| 354 | gdgen_text(im, spf, epf, |
| 355 | color_index(im, obj->pencolor), |
| 356 | span->font->size, |
| 357 | DEFAULT_DPI, |
| 358 | job->rotation ? (M_PI / 2) : 0, |
| 359 | span->font->name, |
| 360 | span->str); |
| 361 | } |
| 362 | |
| 363 | /* interpolate_zcoord: |
| 364 | * Given 2 points in 3D p = (fst.x,fst.y,fstz) and q = (snd.x, snd.y, sndz), |
| 365 | * and a point p1 in the xy plane lying on the line segment connecting |
| 366 | * the projections of the p and q, find the z coordinate of p1 when it |
| 367 | * is projected up onto the segment (p,q) in 3-space. |
| 368 | * |
| 369 | * Why the special case for ranks? Is the arithmetic really correct? |
| 370 | */ |
| 371 | static double |
| 372 | interpolate_zcoord(GVJ_t *job, pointf p1, pointf fst, double fstz, pointf snd, double sndz) |
| 373 | { |
| 374 | obj_state_t *obj = job->obj; |
| 375 | edge_t *e = obj->u.e; |
| 376 | double len, d, rv; |
| 377 | |
| 378 | if (fstz == sndz) |
| 379 | return fstz; |
| 380 | if (ND_rank(agtail(e)) != ND_rank(aghead(e))) { |
| 381 | if (snd.y == fst.y) |
| 382 | rv = (fstz + sndz) / 2.0; |
| 383 | else |
| 384 | rv = fstz + (sndz - fstz) * (p1.y - fst.y) / (snd.y - fst.y); |
| 385 | } |
| 386 | else { |
| 387 | len = DIST(fst, snd); |
| 388 | d = DIST(p1, fst)/len; |
| 389 | rv = fstz + d*(sndz - fstz); |
| 390 | } |
| 391 | return rv; |
| 392 | } |
| 393 | |
| 394 | /* collinear: |
| 395 | * Return true if the 3 points starting at A are collinear. |
| 396 | */ |
| 397 | static int |
| 398 | collinear (pointf * A) |
| 399 | { |
| 400 | double w; |
| 401 | |
| 402 | w = wind(A[0],A[1],A[2]); |
| 403 | return (fabs(w) <= 1); |
| 404 | } |
| 405 | |
| 406 | /* straight: |
| 407 | * Return true if bezier points are collinear |
| 408 | * At present, just check with 4 points, the common case. |
| 409 | */ |
| 410 | static int |
| 411 | straight (pointf * A, int n) |
| 412 | { |
| 413 | if (n != 4) return 0; |
| 414 | return (collinear(A) && collinear(A+1)); |
| 415 | } |
| 416 | |
| 417 | static void |
| 418 | doSegment (GVJ_t *job, pointf* A, pointf p0, double z0, pointf p1, double z1) |
| 419 | { |
| 420 | obj_state_t *obj = job->obj; |
| 421 | double d1, d0; |
| 422 | double delx, dely, delz; |
| 423 | |
| 424 | delx = p0.x - p1.x; |
| 425 | dely = p0.y - p1.y; |
| 426 | delz = z0 - z1; |
| 427 | EdgeLen = sqrt(delx*delx + dely*dely + delz*delz); |
| 428 | d0 = DIST(A[0],p0); |
| 429 | d1 = DIST(A[3],p1); |
| 430 | CylHt = EdgeLen - d0 - d1; |
| 431 | TailHt = HeadHt = 0; |
| 432 | |
| 433 | IsSegment = 1; |
| 434 | gvputs(job, "Transform {\n" ); |
| 435 | gvputs(job, " children [\n" ); |
| 436 | gvputs(job, " Shape {\n" ); |
| 437 | gvputs(job, " geometry Cylinder {\n" ); |
| 438 | gvputs(job, " bottom FALSE top FALSE\n" ); |
| 439 | gvprintf(job, " height %.3f radius %.3f }\n" , CylHt, obj->penwidth); |
| 440 | gvputs(job, " appearance Appearance {\n" ); |
| 441 | gvputs(job, " material Material {\n" ); |
| 442 | gvputs(job, " ambientIntensity 0.33\n" ); |
| 443 | gvprintf(job, " diffuseColor %.3f %.3f %.3f\n" , |
| 444 | obj->pencolor.u.rgba[0] / 255., |
| 445 | obj->pencolor.u.rgba[1] / 255., |
| 446 | obj->pencolor.u.rgba[2] / 255.); |
| 447 | gvputs(job, " }\n" ); |
| 448 | gvputs(job, " }\n" ); |
| 449 | gvputs(job, " }\n" ); |
| 450 | } |
| 451 | |
| 452 | /* nearTail: |
| 453 | * Given a point a and edge e, return true if a is closer to the |
| 454 | * tail of e than the head. |
| 455 | */ |
| 456 | static int |
| 457 | nearTail (GVJ_t* job, pointf a, Agedge_t* e) |
| 458 | { |
| 459 | pointf tp = gvrender_ptf(job, ND_coord(agtail(e))); |
| 460 | pointf hp = gvrender_ptf(job, ND_coord(aghead(e))); |
| 461 | |
| 462 | return (DIST2(a, tp) < DIST2(a, hp)); |
| 463 | } |
| 464 | |
| 465 | /* this is gruesome, but how else can we get z coord */ |
| 466 | #define GETZ(jp,op,p,e) (nearTail(jp,p,e)?op->tail_z:op->head_z) |
| 467 | |
| 468 | static void |
| 469 | vrml_bezier(GVJ_t *job, pointf * A, int n, int arrow_at_start, int arrow_at_end, int filled) |
| 470 | { |
| 471 | obj_state_t *obj = job->obj; |
| 472 | edge_t *e = obj->u.e; |
| 473 | double fstz, sndz; |
| 474 | pointf p1, V[4]; |
| 475 | int i, j, step; |
| 476 | |
| 477 | assert(e); |
| 478 | |
| 479 | fstz = Fstz = obj->tail_z; |
| 480 | sndz = Sndz = obj->head_z; |
| 481 | if (straight(A,n)) { |
| 482 | doSegment (job, A, gvrender_ptf(job, ND_coord(agtail(e))),Fstz,gvrender_ptf(job, ND_coord(aghead(e))),Sndz); |
| 483 | return; |
| 484 | } |
| 485 | |
| 486 | gvputs(job, "Shape { geometry Extrusion {\n" ); |
| 487 | gvputs(job, " spine [" ); |
| 488 | V[3] = A[0]; |
| 489 | for (i = 0; i + 3 < n; i += 3) { |
| 490 | V[0] = V[3]; |
| 491 | for (j = 1; j <= 3; j++) |
| 492 | V[j] = A[i + j]; |
| 493 | for (step = 0; step <= BEZIERSUBDIVISION; step++) { |
| 494 | p1 = Bezier(V, 3, (double) step / BEZIERSUBDIVISION, NULL, NULL); |
| 495 | gvprintf(job, " %.3f %.3f %.3f" , p1.x, p1.y, |
| 496 | interpolate_zcoord(job, p1, A[0], fstz, A[n - 1], sndz)); |
| 497 | } |
| 498 | } |
| 499 | gvputs(job, " ]\n" ); |
| 500 | gvprintf(job, " crossSection [ %.3f %.3f, %.3f %.3f, %.3f %.3f, %.3f %.3f ]\n" , |
| 501 | (obj->penwidth), (obj->penwidth), -(obj->penwidth), |
| 502 | (obj->penwidth), -(obj->penwidth), -(obj->penwidth), |
| 503 | (obj->penwidth), -(obj->penwidth)); |
| 504 | gvputs(job, "}\n" ); |
| 505 | gvprintf(job, " appearance DEF E%ld Appearance {\n" , AGSEQ(e)); |
| 506 | gvputs(job, " material Material {\n" ); |
| 507 | gvputs(job, " ambientIntensity 0.33\n" ); |
| 508 | gvprintf(job, " diffuseColor %.3f %.3f %.3f\n" , |
| 509 | obj->pencolor.u.rgba[0] / 255., |
| 510 | obj->pencolor.u.rgba[1] / 255., |
| 511 | obj->pencolor.u.rgba[2] / 255.); |
| 512 | gvputs(job, " }\n" ); |
| 513 | gvputs(job, " }\n" ); |
| 514 | gvputs(job, "}\n" ); |
| 515 | } |
| 516 | |
| 517 | /* doArrowhead: |
| 518 | * If edge is straight, we attach a cone to the edge as a group. |
| 519 | */ |
| 520 | static void doArrowhead (GVJ_t *job, pointf * A) |
| 521 | { |
| 522 | obj_state_t *obj = job->obj; |
| 523 | edge_t *e = obj->u.e; |
| 524 | double rad, ht, y; |
| 525 | pointf p0; /* center of triangle base */ |
| 526 | |
| 527 | p0.x = (A[0].x + A[2].x)/2.0; |
| 528 | p0.y = (A[0].y + A[2].y)/2.0; |
| 529 | rad = DIST(A[0],A[2])/2.0; |
| 530 | ht = DIST(p0,A[1]); |
| 531 | |
| 532 | y = (CylHt + ht)/2.0; |
| 533 | |
| 534 | gvputs(job, "Transform {\n" ); |
| 535 | if (nearTail (job, A[1], e)) { |
| 536 | TailHt = ht; |
| 537 | gvprintf(job, " translation 0 %.3f 0\n" , -y); |
| 538 | gvprintf(job, " rotation 0 0 1 %.3f\n" , M_PI); |
| 539 | } |
| 540 | else { |
| 541 | HeadHt = ht; |
| 542 | gvprintf(job, " translation 0 %.3f 0\n" , y); |
| 543 | } |
| 544 | gvputs(job, " children [\n" ); |
| 545 | gvputs(job, " Shape {\n" ); |
| 546 | gvprintf(job, " geometry Cone {bottomRadius %.3f height %.3f }\n" , |
| 547 | rad, ht); |
| 548 | gvputs(job, " appearance Appearance {\n" ); |
| 549 | gvputs(job, " material Material {\n" ); |
| 550 | gvputs(job, " ambientIntensity 0.33\n" ); |
| 551 | gvprintf(job, " diffuseColor %.3f %.3f %.3f\n" , |
| 552 | obj->pencolor.u.rgba[0] / 255., |
| 553 | obj->pencolor.u.rgba[1] / 255., |
| 554 | obj->pencolor.u.rgba[2] / 255.); |
| 555 | gvputs(job, " }\n" ); |
| 556 | gvputs(job, " }\n" ); |
| 557 | gvputs(job, " }\n" ); |
| 558 | gvputs(job, " ]\n" ); |
| 559 | gvputs(job, "}\n" ); |
| 560 | } |
| 561 | |
| 562 | static void vrml_polygon(GVJ_t *job, pointf * A, int np, int filled) |
| 563 | { |
| 564 | obj_state_t *obj = job->obj; |
| 565 | node_t *n; |
| 566 | edge_t *e; |
| 567 | double z = obj->z; |
| 568 | pointf p, mp; |
| 569 | gdPoint *points; |
| 570 | int i, pen; |
| 571 | gdImagePtr brush = NULL; |
| 572 | double theta; |
| 573 | |
| 574 | switch (obj->type) { |
| 575 | case ROOTGRAPH_OBJTYPE: |
| 576 | gvprintf(job, " Background { skyColor %.3f %.3f %.3f }\n" , |
| 577 | obj->fillcolor.u.rgba[0] / 255., |
| 578 | obj->fillcolor.u.rgba[1] / 255., |
| 579 | obj->fillcolor.u.rgba[2] / 255.); |
| 580 | Saw_skycolor = TRUE; |
| 581 | break; |
| 582 | case CLUSTER_OBJTYPE: |
| 583 | break; |
| 584 | case NODE_OBJTYPE: |
| 585 | n = obj->u.n; |
| 586 | pen = set_penstyle(job, im, brush); |
| 587 | points = N_GGNEW(np, gdPoint); |
| 588 | for (i = 0; i < np; i++) { |
| 589 | mp = vrml_node_point(job, n, A[i]); |
| 590 | points[i].x = ROUND(mp.x); |
| 591 | points[i].y = ROUND(mp.y); |
| 592 | } |
| 593 | if (filled) |
| 594 | gdImageFilledPolygon(im, points, np, color_index(im, obj->fillcolor)); |
| 595 | gdImagePolygon(im, points, np, pen); |
| 596 | free(points); |
| 597 | if (brush) |
| 598 | gdImageDestroy(brush); |
| 599 | |
| 600 | gvputs(job, "Shape {\n" ); |
| 601 | gvputs(job, " appearance Appearance {\n" ); |
| 602 | gvputs(job, " material Material {\n" ); |
| 603 | gvputs(job, " ambientIntensity 0.33\n" ); |
| 604 | gvputs(job, " diffuseColor 1 1 1\n" ); |
| 605 | gvputs(job, " }\n" ); |
| 606 | gvprintf(job, " texture ImageTexture { url \"node%ld.png\" }\n" , AGSEQ(n)); |
| 607 | gvputs(job, " }\n" ); |
| 608 | gvputs(job, " geometry Extrusion {\n" ); |
| 609 | gvputs(job, " crossSection [" ); |
| 610 | for (i = 0; i < np; i++) { |
| 611 | p.x = A[i].x - ND_coord(n).x; |
| 612 | p.y = A[i].y - ND_coord(n).y; |
| 613 | gvprintf(job, " %.3f %.3f," , p.x, p.y); |
| 614 | } |
| 615 | p.x = A[0].x - ND_coord(n).x; |
| 616 | p.y = A[0].y - ND_coord(n).y; |
| 617 | gvprintf(job, " %.3f %.3f ]\n" , p.x, p.y); |
| 618 | gvprintf(job, " spine [ %.5g %.5g %.5g, %.5g %.5g %.5g ]\n" , |
| 619 | ND_coord(n).x, ND_coord(n).y, z - .01, |
| 620 | ND_coord(n).x, ND_coord(n).y, z + .01); |
| 621 | gvputs(job, " }\n" ); |
| 622 | gvputs(job, "}\n" ); |
| 623 | break; |
| 624 | case EDGE_OBJTYPE: |
| 625 | e = obj->u.e; |
| 626 | if (np != 3) { |
| 627 | static int flag; |
| 628 | if (!flag) { |
| 629 | flag++; |
| 630 | agerr(AGWARN, |
| 631 | "vrml_polygon: non-triangle arrowheads not supported - ignoring\n" ); |
| 632 | } |
| 633 | } |
| 634 | if (IsSegment) { |
| 635 | doArrowhead (job, A); |
| 636 | return; |
| 637 | } |
| 638 | p.x = p.y = 0.0; |
| 639 | for (i = 0; i < np; i++) { |
| 640 | p.x += A[i].x; |
| 641 | p.y += A[i].y; |
| 642 | } |
| 643 | p.x = p.x / np; |
| 644 | p.y = p.y / np; |
| 645 | |
| 646 | /* it is bad to know that A[1] is the aiming point, but we do */ |
| 647 | theta = |
| 648 | atan2((A[0].y + A[2].y) / 2.0 - A[1].y, |
| 649 | (A[0].x + A[2].x) / 2.0 - A[1].x) + M_PI / 2.0; |
| 650 | |
| 651 | z = GETZ(job,obj,p,e); |
| 652 | |
| 653 | /* FIXME: arrow vector ought to follow z coord of bezier */ |
| 654 | gvputs(job, "Transform {\n" ); |
| 655 | gvprintf(job, " translation %.3f %.3f %.3f\n" , p.x, p.y, z); |
| 656 | gvputs(job, " children [\n" ); |
| 657 | gvputs(job, " Transform {\n" ); |
| 658 | gvprintf(job, " rotation 0 0 1 %.3f\n" , theta); |
| 659 | gvputs(job, " children [\n" ); |
| 660 | gvputs(job, " Shape {\n" ); |
| 661 | gvprintf(job, " geometry Cone {bottomRadius %.3f height %.3f }\n" , |
| 662 | obj->penwidth * 2.5, obj->penwidth * 10.0); |
| 663 | gvprintf(job, " appearance USE E%ld\n" , AGSEQ(e)); |
| 664 | gvputs(job, " }\n" ); |
| 665 | gvputs(job, " ]\n" ); |
| 666 | gvputs(job, " }\n" ); |
| 667 | gvputs(job, " ]\n" ); |
| 668 | gvputs(job, "}\n" ); |
| 669 | break; |
| 670 | } |
| 671 | } |
| 672 | |
| 673 | /* doSphere: |
| 674 | * Output sphere in VRML for point nodes. |
| 675 | */ |
| 676 | static void |
| 677 | doSphere (GVJ_t *job, node_t *n, pointf p, double z, double rx, double ry) |
| 678 | { |
| 679 | obj_state_t *obj = job->obj; |
| 680 | |
| 681 | // if (!(strcmp(cstk[SP].fillcolor, "transparent"))) { |
| 682 | // return; |
| 683 | // } |
| 684 | |
| 685 | gvputs(job, "Transform {\n" ); |
| 686 | gvprintf(job, " translation %.3f %.3f %.3f\n" , p.x, p.y, z); |
| 687 | gvprintf(job, " scale %.3f %.3f %.3f\n" , rx, rx, rx); |
| 688 | gvputs(job, " children [\n" ); |
| 689 | gvputs(job, " Transform {\n" ); |
| 690 | gvputs(job, " children [\n" ); |
| 691 | gvputs(job, " Shape {\n" ); |
| 692 | gvputs(job, " geometry Sphere { radius 1.0 }\n" ); |
| 693 | gvputs(job, " appearance Appearance {\n" ); |
| 694 | gvputs(job, " material Material {\n" ); |
| 695 | gvputs(job, " ambientIntensity 0.33\n" ); |
| 696 | gvprintf(job, " diffuseColor %.3f %.3f %.3f\n" , |
| 697 | obj->pencolor.u.rgba[0] / 255., |
| 698 | obj->pencolor.u.rgba[1] / 255., |
| 699 | obj->pencolor.u.rgba[2] / 255.); |
| 700 | gvputs(job, " }\n" ); |
| 701 | gvputs(job, " }\n" ); |
| 702 | gvputs(job, " }\n" ); |
| 703 | gvputs(job, " ]\n" ); |
| 704 | gvputs(job, " }\n" ); |
| 705 | gvputs(job, " ]\n" ); |
| 706 | gvputs(job, "}\n" ); |
| 707 | } |
| 708 | |
| 709 | static void vrml_ellipse(GVJ_t * job, pointf * A, int filled) |
| 710 | { |
| 711 | obj_state_t *obj = job->obj; |
| 712 | node_t *n; |
| 713 | edge_t *e; |
| 714 | double z = obj->z; |
| 715 | double rx, ry; |
| 716 | int dx, dy; |
| 717 | pointf npf, nqf; |
| 718 | point np; |
| 719 | int pen; |
| 720 | gdImagePtr brush = NULL; |
| 721 | |
| 722 | rx = A[1].x - A[0].x; |
| 723 | ry = A[1].y - A[0].y; |
| 724 | |
| 725 | switch (obj->type) { |
| 726 | case ROOTGRAPH_OBJTYPE: |
| 727 | case CLUSTER_OBJTYPE: |
| 728 | break; |
| 729 | case NODE_OBJTYPE: |
| 730 | n = obj->u.n; |
| 731 | if (shapeOf(n) == SH_POINT) { |
| 732 | doSphere (job, n, A[0], z, rx, ry); |
| 733 | return; |
| 734 | } |
| 735 | pen = set_penstyle(job, im, brush); |
| 736 | |
| 737 | npf = vrml_node_point(job, n, A[0]); |
| 738 | nqf = vrml_node_point(job, n, A[1]); |
| 739 | |
| 740 | dx = ROUND(2 * (nqf.x - npf.x)); |
| 741 | dy = ROUND(2 * (nqf.y - npf.y)); |
| 742 | |
| 743 | PF2P(npf, np); |
| 744 | |
| 745 | if (filled) |
| 746 | gdImageFilledEllipse(im, np.x, np.y, dx, dy, color_index(im, obj->fillcolor)); |
| 747 | gdImageArc(im, np.x, np.y, dx, dy, 0, 360, pen); |
| 748 | |
| 749 | if (brush) |
| 750 | gdImageDestroy(brush); |
| 751 | |
| 752 | gvputs(job, "Transform {\n" ); |
| 753 | gvprintf(job, " translation %.3f %.3f %.3f\n" , A[0].x, A[0].y, z); |
| 754 | gvprintf(job, " scale %.3f %.3f 1\n" , rx, ry); |
| 755 | gvputs(job, " children [\n" ); |
| 756 | gvputs(job, " Transform {\n" ); |
| 757 | gvputs(job, " rotation 1 0 0 1.57\n" ); |
| 758 | gvputs(job, " children [\n" ); |
| 759 | gvputs(job, " Shape {\n" ); |
| 760 | gvputs(job, " geometry Cylinder { side FALSE }\n" ); |
| 761 | gvputs(job, " appearance Appearance {\n" ); |
| 762 | gvputs(job, " material Material {\n" ); |
| 763 | gvputs(job, " ambientIntensity 0.33\n" ); |
| 764 | gvputs(job, " diffuseColor 1 1 1\n" ); |
| 765 | gvputs(job, " }\n" ); |
| 766 | gvprintf(job, " texture ImageTexture { url \"node%ld.png\" }\n" , AGSEQ(n)); |
| 767 | gvputs(job, " }\n" ); |
| 768 | gvputs(job, " }\n" ); |
| 769 | gvputs(job, " ]\n" ); |
| 770 | gvputs(job, " }\n" ); |
| 771 | gvputs(job, " ]\n" ); |
| 772 | gvputs(job, "}\n" ); |
| 773 | break; |
| 774 | case EDGE_OBJTYPE: |
| 775 | e = obj->u.e; |
| 776 | z = GETZ(job,obj,A[0],e); |
| 777 | |
| 778 | gvputs(job, "Transform {\n" ); |
| 779 | gvprintf(job, " translation %.3f %.3f %.3f\n" , A[0].x, A[0].y, z); |
| 780 | gvputs(job, " children [\n" ); |
| 781 | gvputs(job, " Shape {\n" ); |
| 782 | gvprintf(job, " geometry Sphere {radius %.3f }\n" , (double) rx); |
| 783 | gvprintf(job, " appearance USE E%d\n" , AGSEQ(e)); |
| 784 | gvputs(job, " }\n" ); |
| 785 | gvputs(job, " ]\n" ); |
| 786 | gvputs(job, "}\n" ); |
| 787 | } |
| 788 | } |
| 789 | |
| 790 | static gvrender_engine_t vrml_engine = { |
| 791 | 0, /* vrml_begin_job */ |
| 792 | 0, /* vrml_end_job */ |
| 793 | 0, /* vrml_begin_graph */ |
| 794 | 0, /* vrml_end_graph */ |
| 795 | 0, /* vrml_begin_layer */ |
| 796 | 0, /* vrml_end_layer */ |
| 797 | vrml_begin_page, |
| 798 | vrml_end_page, |
| 799 | 0, /* vrml_begin_cluster */ |
| 800 | 0, /* vrml_end_cluster */ |
| 801 | 0, /* vrml_begin_nodes */ |
| 802 | 0, /* vrml_end_nodes */ |
| 803 | 0, /* vrml_begin_edges */ |
| 804 | 0, /* vrml_end_edges */ |
| 805 | vrml_begin_node, |
| 806 | vrml_end_node, |
| 807 | vrml_begin_edge, |
| 808 | vrml_end_edge, |
| 809 | 0, /* vrml_begin_anchor */ |
| 810 | 0, /* vrml_end_anchor */ |
| 811 | 0, /* vrml_begin_label */ |
| 812 | 0, /* vrml_end_label */ |
| 813 | vrml_textspan, |
| 814 | 0, /* vrml_resolve_color */ |
| 815 | vrml_ellipse, |
| 816 | vrml_polygon, |
| 817 | vrml_bezier, |
| 818 | 0, /* vrml_polyline - FIXME */ |
| 819 | 0, /* vrml_comment */ |
| 820 | 0, /* vrml_library_shape */ |
| 821 | }; |
| 822 | |
| 823 | static gvrender_features_t render_features_vrml = { |
| 824 | GVRENDER_DOES_Z, /* flags */ |
| 825 | 0., /* default pad - graph units */ |
| 826 | NULL, /* knowncolors */ |
| 827 | 0, /* sizeof knowncolors */ |
| 828 | RGBA_BYTE, /* color_type */ |
| 829 | }; |
| 830 | |
| 831 | static gvdevice_features_t device_features_vrml = { |
| 832 | GVDEVICE_BINARY_FORMAT |
| 833 | | GVDEVICE_NO_WRITER, /* flags */ |
| 834 | {0.,0.}, /* default margin - points */ |
| 835 | {0.,0.}, /* default page width, height - points */ |
| 836 | {72.,72.}, /* default dpi */ |
| 837 | }; |
| 838 | #endif /* HAVE_GD_PNG */ |
| 839 | |
| 840 | gvplugin_installed_t gvrender_vrml_types[] = { |
| 841 | #ifdef HAVE_GD_PNG |
| 842 | {FORMAT_VRML, "vrml" , 1, &vrml_engine, &render_features_vrml}, |
| 843 | #endif |
| 844 | {0, NULL, 0, NULL, NULL} |
| 845 | }; |
| 846 | |
| 847 | gvplugin_installed_t gvdevice_vrml_types[] = { |
| 848 | #ifdef HAVE_GD_PNG |
| 849 | {FORMAT_VRML, "vrml:vrml" , 1, NULL, &device_features_vrml}, |
| 850 | #endif |
| 851 | {0, NULL, 0, NULL, NULL} |
| 852 | }; |
| 853 | |