1 /* Copyright (C) 1992-1998 The Geometry Center
2 * Copyright (C) 1998-2000 Stuart Levy, Tamara Munzner, Mark Phillips
3 *
4 * This file is part of Geomview.
5 *
6 * Geomview is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU Lesser General Public License as published
8 * by the Free Software Foundation; either version 2, or (at your option)
9 * any later version.
10 *
11 * Geomview is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with Geomview; see the file COPYING. If not, write
18 * to the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139,
19 * USA, or visit http://www.gnu.org.
20 */
21
22 #if HAVE_CONFIG_H
23 # include "config.h"
24 #endif
25
26 #if 0
27 static char copyright[] = "Copyright (C) 1992-1998 The Geometry Center\n\
28 Copyright (C) 1998-2000 Stuart Levy, Tamara Munzner, Mark Phillips";
29 #endif
30
31
32 /* Authors: Stuart Levy, Tamara Munzner, Mark Phillips */
33
34 #include <stdio.h>
35 #include <math.h>
36 #include <string.h>
37 #include "drawer.h"
38 #include "ui.h"
39 #include "lang.h"
40 #include "lisp.h"
41 #include "mg.h"
42 #include "geom.h"
43 #include "vect.h"
44 #include "color.h"
45 #include "camera.h"
46 #include "space.h"
47
48 #define DGobj(obj) ((DGeom *)obj)
49 #define DVobj(obj) ((DView *)obj)
50
51 char *
spacename(int space)52 spacename(int space)
53 {
54 switch(space) {
55 case TM_SPHERICAL: return "spherical";
56 case TM_EUCLIDEAN: return "euclidean";
57 case TM_HYPERBOLIC: return "hyperbolic";
58 default: return NULL;
59 }
60 }
61
62 void
camera_space_relabel(int id)63 camera_space_relabel(int id)
64 {
65 DView *dv = (DView *)drawer_get_object(id);
66 char *extra=NULL;
67 char *fmt;
68 char label[256];
69
70 if(!ISCAM(id) || dv == NULL || dv->mgctx == NULL)
71 return;
72 mgctxselect(dv->mgctx);
73 extra = keywordname(hmodelkeyword("", dv->hmodel));
74 switch(spaceof(WORLDGEOM)) {
75 case TM_SPHERICAL: fmt = "%s (spherical %s view)"; break;
76 case TM_HYPERBOLIC: fmt = "%s (hyperbolic %s view)"; break;
77 case TM_EUCLIDEAN: fmt = "%s (Euclidean%s view)";
78 switch (dv->hmodel) {
79 case VIRTUAL:
80 case PROJECTIVE:
81 extra = "";
82 break;
83 case CONFORMALBALL:
84 extra = " inverted";
85 break;
86 }
87 break;
88 default: return;
89 }
90 sprintf(label, fmt, dv->name[1], extra);
91 mgctxset(MG_WnSet, WN_NAME, label, WN_END, MG_END);
92 }
93
94 /*
95 * Is this a good directory to select in the file browser by default?
96 * Say it is if it contains the 3-char prefix of our space name.
97 */
98 int
our_space_dir(char * dirname)99 our_space_dir(char *dirname)
100 {
101 char *sname, sub[4];
102 if((sname = spacename(spaceof(WORLDGEOM))) == NULL)
103 return 0;
104 sprintf(sub, "%.3s", sname);
105 return (int)(long)strstr(dirname, sub);
106 }
107
108
109 Geom *unitsphere(int n, ColorA *color);
110
111 #define N 50
112
113 Geom *
unitsphere(int n,ColorA * color)114 unitsphere(int n, ColorA *color)
115 {
116 int i, j;
117 float si[N+1], ci[N+1], sj[N+1], cj[N+1];
118 Point3 verts[(N-2 + N)*N];
119 short vcounts[N-2 + N/2];
120 short ccounts[N-2 + N/2];
121 Point3 *p;
122 short *vc;
123 Geom *g;
124 float excess = 1. / cos(M_PI / n);
125
126 for(i = 0; i <= n; i++) {
127 float t = i * 2*M_PI / n;
128 sj[i] = sin(t) * excess;
129 cj[i] = cos(t) * excess;
130 t = i * M_PI / n;
131 si[i] = sin(t);
132 ci[i] = cos(t);
133 }
134
135 memset(ccounts, 0, sizeof(ccounts));
136 ccounts[0] = color ? 1 : 0;
137
138 /* Construct n-2 parallels */
139 p = verts;
140 vc = vcounts;
141 for(i = 1; i < n-1; i++) {
142 float y = ci[i];
143 float r = si[i];
144
145 *vc++ = -n; /* Closed line with n vertices */
146 for(j = 0; j < n; j++) {
147 p->x = r*cj[j];
148 p->y = y;
149 p->z = r*sj[j];
150 p++;
151 }
152 }
153 /* Construct n/2 meridians, each a full circle */
154 for(j = 0; j < n/2; j++) {
155 float s = sj[j];
156 float c = cj[j];
157
158 *vc++ = -2*n;
159 for(i = 0; i < n; i++) {
160 p->x = c*si[i];
161 p->y = ci[i];
162 p->z = s*si[i];
163 p++;
164 }
165 for(i = n; i > 0; i--) {
166 p->x = -c*si[i];
167 p->y = ci[i];
168 p->z = -s*si[i];
169 p++;
170 }
171 }
172 g = GeomCreate("vect",
173 CR_NVECT, n-2 + n/2,
174 CR_VECTC, vcounts,
175 CR_COLRC, ccounts,
176 CR_NVERT, (n-2 + n) * n,
177 CR_POINT, verts,
178 CR_NCOLR, color ? 1 : 0,
179 CR_COLOR, color,
180 CR_END);
181 return g;
182 }
183
184 void
set_hsphere_draw(int id,int draw)185 set_hsphere_draw(int id, int draw)
186 {
187 static ColorA color = { .3, .2, .15, .7 };
188 static Geom *hsphere = NULL;
189 DView *dv;
190
191 if (!ISCAM(id)) return;
192 dv = (DView*)drawer_get_object(id);
193
194 if (draw < 0) {
195 /* toggle */
196 draw = (dv->hsphere == NULL);
197 }
198
199 if (draw) {
200 if (!hsphere) {
201 hsphere = unitsphere(16, &color);
202 }
203 dv->hsphere = hsphere;
204 } else {
205 dv->hsphere = NULL;
206 }
207 ui_maybe_refresh(id);
208 }
209
210 NDcam *
NDcluster(char * name)211 NDcluster(char *name)
212 {
213 NDcam *c;
214
215 for(c = drawerstate.NDcams; c != NULL; c = c->next)
216 if(strcmp(c->name, name) == 0) return c;
217 return NULL;
218 }
219
220 NDcam *
NDnewcluster(char * name)221 NDnewcluster(char *name)
222 {
223 NDcam *c;
224
225 c = NDcluster(name);
226 if(c == NULL) {
227 c = OOGLNewE(NDcam, "NDcam cluster");
228 c->name = strdup(name);
229 c->C2W = TmNIdentity(TmNCreate(drawerstate.NDim,drawerstate.NDim,NULL));
230 c->W2C = NULL;
231 c->next = drawerstate.NDcams;
232 drawerstate.NDcams = c;
233 }
234 return c;
235 }
236
237 void
NDdeletecluster(NDcam * c)238 NDdeletecluster(NDcam *c)
239 { /* Add this someday. Note that there may be other references to this cluster. XXX */
240 }
241
242 LDEFINE(ND_axes, LLIST,
243 "(ND-axes CAMID [CLUSTERNAME [Xindex Yindex Zindex [Windex]]])\n\
244 In our model for N-D viewing (enabled by (dimension)), objects in\n\
245 N-space are viewed by N-dimensional \"camera clusters\".\n\
246 Each real camera window belongs to some cluster, and shows &\n\
247 manipulates a 3-D axis-aligned projected subspace of the N-space seen\n\
248 by its cluster. Moving one camera in a cluster affects its siblings.\n\
249 \n The ND-axes command configures all this. It specifies a camera's\n\
250 cluster membership, and the set of N-space axes which become the\n\
251 3-D camera's X, Y, and Z axes. Axes are specified by their indices,\n\
252 from 1 to N for an N-dimensional space. Cluster CLUSTERNAME is\n\
253 implicitly created if not previously known.\n\
254 In principle it is possible to map the homogeneous component\n\
255 of a conformal 4 point to some other index; this would be done\n\
256 by specifying 0 for one of Xindex, Yindex or Zindex and giving\n\
257 Windex some positive value. This is probably not useful because\n\
258 Geomview does not support non-Euclidean geometries for in higher\n\
259 dimensions.\n\
260 \n\
261 To read a camera's configuration, use \"(echo (ND-axes CAMID))\".\n\
262 The return value is an array of 4 integers, the last one should\n\
263 be 0.")
264 {
265 int axes[4];
266 char *camname, *clustername = NULL;
267 int i, cam;
268 DView *dv;
269
270 axes[0] = axes[1] = axes[2] = -1;
271 axes[3] = 0;
272 LDECLARE(("ND-axes", LBEGIN,
273 LSTRING, &camname,
274 LOPTIONAL,
275 LSTRING, &clustername,
276 LINT, &axes[0],
277 LINT, &axes[1],
278 LINT, &axes[2],
279 LINT, &axes[3],
280 LEND));
281 if((cam = drawer_idbyname(camname)) == NOID ||
282 (dv = (DView *)drawer_get_object(cam)) == NULL || !ISCAM(dv->id)) {
283 OOGLError(0, "ND-axes: unknown camera %s", camname);
284 return Lnil;
285 } else if(axes[0] < 0) {
286 /* return the current ND configuratino of cam */
287 LList *l = NULL;
288 if(dv->cluster) {
289 l = LListAppend(NULL, LTOOBJ(LSTRING)(&dv->cluster->name));
290 for(i = 0; i < 4; i++)
291 l = LListAppend(l, LNew( LINT, &dv->NDPerm[i] ));
292 }
293 return LNew( LLIST, &l );
294 } else {
295 /* possibly generate a new cluster, or add to an existing
296 * cluster. NOTE: we first reset the camera to its default state.
297 */
298 NDcam *c = NDnewcluster(clustername);
299
300 for(i = 0; i < 4; i++) {
301 if (axes[i] < 0) {
302 const char names[4] = { 'X', 'Y', 'Z', 'W' };
303 OOGLError(1,
304 "ERROR: bogus ND-axes specification (%d %d %d %d), %c-entry "
305 "must not be negative.\n",
306 axes[0], axes[1], axes[2], axes[3], names[i]);
307 return Lnil;
308 }
309 }
310 NDdeletecluster(dv->cluster); /* a no-op yet */
311 dv->cluster = c;
312 for(i = 0; i < 4; i++) {
313 dv->NDPerm[i] = axes[i];
314 }
315
316 CamReset(dv->cam);
317
318 dv->changed |= CH_GEOMETRY;
319 return Lt;
320 }
321 }
322
set_dimension(int d,TransformN ** Tp,TransformN ** Tinvp)323 static void set_dimension(int d, TransformN **Tp, TransformN **Tinvp)
324 {
325 if(Tinvp) {
326 TmNDelete(*Tinvp);
327 *Tinvp = NULL;
328 }
329 if(Tp == NULL) {
330 return;
331 }
332 if(d == 0) {
333 TmNDelete(*Tp);
334 *Tp = NULL;
335 } else {
336 if(TmNGetSize(*Tp, NULL,NULL) > d) {
337 TmNDelete(*Tp);
338 *Tp = NULL;
339 }
340 *Tp = TmNPad(*Tp, d, d, *Tp);
341 }
342 }
343
344 LDEFINE(dimension, LLIST,
345 "(dimension [N])\n\
346 Sets or reads the space dimension for N-dimensional viewing.\n\
347 (Since calculations are done using homogeneous coordinates,\n\
348 this means matrices are (N+1)x(N+1).)\n\
349 With no arguments, returns the current dimension, or 0 if\n\
350 N-dimensional viewing has not been enabled. Note that N has to be"
351 "at least 4 to enable ND-viewing, otherwise ND-viewing will be"
352 "disabled.")
353 {
354 int i, d = -1;
355
356 LDECLARE(("dimension", LBEGIN,
357 LOPTIONAL,
358 LINT, &d,
359 LEND));
360
361 if (d < 0) {
362 d = drawerstate.NDim-1;
363 if(d < 0) {
364 d = 0;
365 }
366 return LNew(LINT, &d);
367 } else {
368 NDcam *c;
369 if (d < 4) { /* we do not allow low-dimension ND-view */
370 d = 0;
371 for(i = 0; i < dview_max; i++) {
372 if(dview[i] && dview[i]->cluster) {
373 NDdeletecluster(dview[i]->cluster);
374 dview[i]->cluster = NULL;
375 }
376 }
377 } else {
378 d++; /* Include homogeneous coordinate drawerstate.NDim */
379 }
380 if(drawerstate.NDim != d) {
381 for(c = drawerstate.NDcams; c != NULL; c = c->next) {
382 set_dimension(d, &c->C2W, &c->W2C);
383 }
384 for(i = 0; i < dview_max; i++) {
385 if(dview[i]) {
386 dview[i]->changed = CH_GEOMETRY;
387 }
388 }
389 for(i = 0; i < dgeom_max; i++) {
390 if(dgeom[i]) {
391 set_dimension(d, &dgeom[i]->NDT, &dgeom[i]->NDTinv);
392 dgeom[i]->bboxvalid = false;
393 dgeom[i]->changed = CH_GEOMETRY;
394 }
395 }
396 drawerstate.changed = true;
397 drawerstate.NDim = d;
398 }
399 }
400 return Lt;
401 }
402
403 LDEFINE(ND_xform, LTRANSFORMN,
404 "(ND-xform OBJID [ntransform { idim odim ... }]\n"
405 "Concatenate the given ND-transform with the current "
406 "ND-transform of the object (apply the ND-transform to "
407 "object ID, as opposed to simply setting its ND-transform)."
408 "Note that ND-transforms have their homogeneous coordinate at "
409 "index 0, while 3D transform have it at index 3.")
410 {
411 int id;
412 TransformN *T = NULL;
413 TmNStruct *ts = NULL;
414 DObject *obj;
415 NDcam *cl = NULL;
416
417 LDECLARE(("ND-xform", LBEGIN,
418 LID, &id,
419 LTRANSFORMN, &ts,
420 LEND));
421
422 if((obj = drawer_get_object(id)) == NULL || drawerstate.NDim == 0)
423 return Lnil;
424
425 if(ISGEOM(obj->id)) {
426 T = ((DGeom *)obj)->NDT;
427 } else if(ISCAM(obj->id) && (cl = ((DView *)obj)->cluster)) {
428 T = cl->C2W;
429 }
430
431 if (!T) {
432 T = REFGET(TransformN, ts->tm);
433 } else {
434 TmNConcat(ts->tm, T, T);
435 }
436
437 if(ISGEOM(obj->id)) {
438 DGobj(obj)->NDT = T;
439 GeomSet(DGobj(obj)->Item, CR_NDAXIS, DGobj(obj)->NDT, CR_END);
440 obj->changed |= CH_GEOMETRY;
441 } else if(cl != NULL) {
442 cl->C2W = T;
443 drawerstate.changed = true;
444 }
445 TmIdentity(obj->Incr);
446 obj->redraw = true;
447 obj->moving = (obj->updateproc != NULL);
448 return Lt;
449 }
450
451 LDEFINE(ND_xform_set, LTRANSFORMN,
452 "(ND-xform-set OBJID [ntransform { idim odim ... }])\n\
453 Sets the N-D transform of the given object.\n\
454 In dimension N, this is an (N+1)x(N+1) matrix, so in that case\n\
455 idim and odim are expected to be both equal to (N+1). Note that\n\
456 all cameras in a camera-cluster have the same N-D transform.\n"
457 "Note that ND-transforms have their homogeneous coordinate at "
458 "index 0, while 3D transform have it at index 3.")
459 {
460 int id;
461 DObject *obj;
462 TmNStruct *ts = NULL;
463 TransformN *T = NULL;
464 NDcam *cl = NULL;
465
466 LDECLARE(("ND-xform-set", LBEGIN,
467 LID, &id,
468 LOPTIONAL,
469 LTRANSFORMN, &ts,
470 LEND));
471
472 if((obj = drawer_get_object(id)) == NULL || drawerstate.NDim == 0)
473 return Lnil;
474
475 if(ISGEOM(obj->id)) {
476 T = ((DGeom *)obj)->NDT;
477 } else if(ISCAM(obj->id) && (cl = ((DView *)obj)->cluster)) {
478 T = cl->C2W;
479 }
480
481 /* (ND-xform-set id transformn { ... }) -> set transform */
482 TmNDelete(T);
483 if(ISGEOM(obj->id)) {
484 ((DGeom *)obj)->NDT = REFGET(TransformN, ts->tm);
485 GeomSet(DGobj(obj)->Item, CR_NDAXIS, DGobj(obj)->NDT, CR_END);
486 obj->changed |= CH_GEOMETRY;
487 } else if(cl != NULL) {
488 cl->C2W = REFGET(TransformN, ts->tm);
489 drawerstate.changed = true;
490 }
491 return Lt;
492 }
493
494 LDEFINE(ND_xform_get, LTRANSFORMN,
495 "(ND-xform-get ID [from-ID])\n\
496 Returns the N-D transform of the given object in the coordinate\n\
497 system of from-ID (default \"universe\"), in the sense\n\
498 <point-in-ID-coords> * Transform = <point-in-from-ID-coords>"
499 "Note that ND-transforms have their homogeneous coordinate at "
500 "index 0, while 3D transform have it at index 3.")
501 {
502 int from_id;
503 int to_id = UNIVERSE;
504 TmNStruct *ts;
505 TransformN *tm;
506
507 extern TransformN *drawer_get_ND_transform(int from_id, int to_id);
508
509 LDECLARE(("ND-xform-get", LBEGIN,
510 LID, &from_id,
511 LOPTIONAL,
512 LID, &to_id,
513 LEND));
514 if((tm = drawer_get_ND_transform(from_id, to_id)) == NULL)
515 return Lnil;
516 ts = OOGLNewE(TmNStruct, "TmN");
517 ts->h = NULL;
518 ts->tm = tm;
519 return LNew(LTRANSFORMN, &ts);
520 }
521
522
523 LDEFINE(ND_color, LLIST,
524 "(ND-color CAMID [ (( [ID] (x1 x2 x3 ... xN) v r g b a v r g b a ... )\n\
525 ((x1 ... xN) v r g b a v r g b a ...) ...)] )\n\
526 Specifies a function, applied to each N-D vertex, which determines the\n\
527 colors of N-dimensional objects as shown in camera CAMID.\n\
528 Each coloring function is defined by a vector (in ID's coordinate system)\n\
529 [x1 x1 ... xN] and by a sequence of value (v)/color(r g b a) tuples,\n\
530 ordered by increasing v. The inner product v = P.[x] is linearly\n\
531 interpolated in this table to give a color.\n\
532 If ID is omitted, the (xi) vector is assumed in universe coordinates.\n\
533 The ND-color command specifies a list of such functions; each vertex\n\
534 is colored by their sum (so e.g. green intensity could indicate\n\
535 projection along one axis while red indicated another.\n\
536 An empty list, as in (ND-color CAMID ()), suppresses coloring.\n\
537 With no second argument, (ND-color CAMID) returns that camera's\n\
538 color-function list.\n\
539 Even when coloring is enabled, objects tagged with the \"keepcolor\"\n\
540 appearance attribute are shown in their natural colors.\n")
541 {
542 int i, j, k, id;
543 DView *dv;
544 LList noarg;
545 LList *l = &noarg, *ents;
546 cmap *cm;
547 cent *ce;
548 char *err;
549 int nents, nfields, ncolors;
550
551 LDECLARE(("ND-color", LBEGIN,
552 LID, &id,
553 LOPTIONAL,
554 LLITERAL,
555 LLIST, &l,
556 LEND));
557
558 if((dv = (DView *)drawer_get_object(id)) == NULL || !ISCAM(dv->id)) {
559 OOGLError(0, "ND-color: expected camera name");
560 return Lnil;
561 }
562
563 if(l == &noarg) {
564 ents = NULL;
565 cm = dv->NDcmap;
566 for(i = 0; i < dv->nNDcmap; i++, cm++) {
567 DObject *dobj = drawer_get_object(cm->coords);
568 char *dname = (dobj && dobj->name[1]) ? dobj->name[1] : "universe";
569
570 dname = strdup(dname);
571 l = LListAppend(NULL, LNew(LSTRING, (char *)&dname));
572 l = LListAppend(l, LMakeArray(LFLOAT,
573 (char *)(cm->axis->v+1), cm->axis->dim-1));
574 for(k = VVCOUNT(cm->cents), ce = VVEC(cm->cents, cent); --k > 0; ce++) {
575 l = LListAppend(l, LNew(LFLOAT, (char *)&ce->v));
576 l = LListAppend(l, LNew(LFLOAT, (char *)&ce->c.r));
577 l = LListAppend(l, LNew(LFLOAT, (char *)&ce->c.g));
578 l = LListAppend(l, LNew(LFLOAT, (char *)&ce->c.b));
579 l = LListAppend(l, LNew(LFLOAT, (char *)&ce->c.a));
580 }
581 ents = LListAppend(ents, LNew(LLIST, &l));
582 }
583 return LNew(LLIST, &ents);
584 }
585
586 ents = l;
587
588 nents = LListLength(ents);
589 if(nents > MAXCMAP) {
590 OOGLError(0, "Only %d colormaps allowed per camera; using first %d of %d",
591 MAXCMAP, MAXCMAP, nents);
592 nents = MAXCMAP;
593 }
594
595 cm = dv->NDcmap;
596 for(i = 1; i <= nents; i++, cm++) {
597
598 LObject *ent = LListEntry(ents, i);
599 LList *entlist, *axis;
600 int dim;
601 /*
602 * Each component of the 'ents' list looks like:
603 * LLIST --- v0,r0,g0,b0,a0, v1,r1,g1,b1,a1, ...
604 * x1,x2,x3,...
605 */
606 if(! LFROMOBJ(LLIST)(ent, &entlist)) {
607 err = "ND-color: expected list of lists";
608 goto no;
609 }
610
611 cm->coords = UNIVERSE;
612 if( entlist->car->type == LSTRING ) {
613 cm->coords = drawer_idbyname(LSTRINGVAL(entlist->car));
614 if(cm->coords == NOID) {
615 OOGLError(0, "ND-color: unknown coordinate system %s, using 'universe'",
616 LSTRINGVAL(entlist->car));
617 cm->coords = UNIVERSE;
618 }
619 entlist = entlist->cdr;
620 }
621
622 if(! LFROMOBJ(LLIST)(entlist->car, &axis)) {
623 err = "ND-color: expected N-D projection axis";
624 goto no;
625 }
626
627 dim = LListLength(axis);
628 cm->axis = cm->axis ? HPtNPad(cm->axis, dim + 1, cm->axis)
629 : HPtNCreate(dim+1, NULL);
630 /* The projection axis is a vector, so its homogeneous component is zero */
631 cm->axis->v[0] = 0;
632 /* Extract the real components */
633 for(j = 1; j < dim+1; j++) {
634 if(!LFROMOBJ(LFLOAT)(LListEntry(axis, j), &cm->axis->v[j])) {
635 err = "Non-numeric entry in projection axis?";
636 goto no;
637 }
638 }
639
640 entlist = entlist->cdr; /* Look at the remainder of the list */
641 nfields = LListLength(entlist);
642 ncolors = nfields / 5;
643 if(nfields % 5 != 0 || nfields == 0) {
644 err = "Each colormap should contain a multiple of 5 numbers: v0 r0 g0 b0 a0 v1 r1 g1 b1 a1 ...";
645 goto no;
646 }
647
648 vvneeds(&cm->cents, ncolors+1);
649 ce = VVEC(cm->cents, cent);
650
651
652 for(j = 1; j <= nfields; j += 5, ce++) {
653 ce->interp = 1;
654 if(!LFROMOBJ(LFLOAT)(LListEntry(entlist, j), &ce->v) ||
655 !LFROMOBJ(LFLOAT)(LListEntry(entlist, j+1), &ce->c.r) ||
656 !LFROMOBJ(LFLOAT)(LListEntry(entlist, j+2), &ce->c.g) ||
657 !LFROMOBJ(LFLOAT)(LListEntry(entlist, j+3), &ce->c.b) ||
658 !LFROMOBJ(LFLOAT)(LListEntry(entlist, j+4), &ce->c.a) ) {
659 err = "Non-numeric entry in colormap?";
660 goto no;
661 }
662 }
663 VVCOUNT(cm->cents) = ncolors+1;
664 ce = &VVEC(cm->cents, cent)[ncolors];
665 *ce = ce[-1];
666 ce->v = 1e20; /* Huge value terminates list */
667 }
668 dv->nNDcmap = nents;
669 dv->changed |= CH_GEOMETRY;
670 return Lt;
671
672 no:
673 OOGLError(0, err);
674 return Lnil;
675 }
676
677 /*
678 * Local Variables: ***
679 * c-basic-offset: 2 ***
680 * End: ***
681 */
682