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 | |