gvrender_gd_vrml.c 24 KB

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