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 /* Authors: Charlie Gunn, Stuart Levy, Tamara Munzner, Mark Phillips */
32
33 #include <math.h>
34 #include "geomclass.h"
35 #include "cameraP.h"
36 #include "mg.h"
37 #include "reference.h"
38
39 Camera * _CamSet(Camera *cam, int attr, va_list *a_list);
40
41 #define SETFLAG(flag, bit, value) \
42 if (value) flag |= bit; \
43 else flag &= ~bit
44
45 #define GETFLAG(flag, bit) ( (flag & bit) != 0 )
46
47 static float GetHalfField( Camera *cam );
48 static void SetHalfField( Camera *cam, float halffield );
49 static void CamStereoCompute( Camera *cam );
50
51 Camera *
CamCreate(int a1,...)52 CamCreate(int a1, ...)
53 {
54 Camera *thiscam;
55 va_list a_list;
56
57 thiscam = OOGLNewE(Camera, "CamCreate: unable to allocate camera\n");
58 memset(thiscam, 0, sizeof(Camera));
59 if (thiscam == NULL) return(NULL);
60
61 RefInit((Ref *)thiscam, CAMMAGIC);
62
63 CamDefault(thiscam);
64 thiscam->changed = 0;
65
66 va_start( a_list, a1 );
67 _CamSet(thiscam, a1, &a_list);
68 va_end(a_list);
69 return thiscam;
70 }
71
72 void
CamDefault(Camera * cam)73 CamDefault(Camera *cam)
74 {
75 cam->flag = CAMF_PERSP;
76 cam->frameaspect = 4.0/3.0;
77 cam->focus = 3.0;
78 cam->stereo_sep = 0.5;
79 cam->stereo_angle = .08;
80 cam->c2whandle = NULL;
81 cam->w2chandle = NULL;
82 cam->sterhandle[0] = NULL;
83 cam->sterhandle[1] = NULL;
84 CamStereoCompute(cam);
85 cam->whicheye = 0; /* only applies to stereo */
86 cam->space = TM_EUCLIDEAN;
87 cam->bgcolor.r = cam->bgcolor.g = cam->bgcolor.b = 1.0/3.0;
88 cam->bgcolor.a = 1.0;
89 cam->bgimage = NULL;
90 cam->bgimghandle = NULL;
91 CamReset( cam );
92 }
93
94 Camera *
CamSet(Camera * cam,int a1,...)95 CamSet(Camera *cam, int a1, ...)
96 {
97 va_list a_list;
98
99 va_start(a_list, a1);
100 return ( _CamSet(cam, a1, &a_list) );
101 }
102
103 Camera *
_CamSet(Camera * cam,int attr,va_list * alist)104 _CamSet(Camera *cam, int attr, va_list *alist)
105 {
106 TransformPtr tt;
107 int sethalffield = 0, setaspect = 0, setstereogeom = 0;
108 double halffield = 0, v;
109 Handle *h;
110 int bit, unbit;
111 char **ablock = NULL;
112
113 #define NEXT(type) OOGL_VA_ARG(type,alist,ablock)
114
115 while (attr != CAM_END) {
116 bit = unbit = 0;
117 switch(attr) {
118 case CAM_ABLOCK:
119 ablock = NEXT(char**);
120 break;
121 case CAM_C2W:
122 tt = NEXT(TransformPtr);
123 bit = CAMF_NEWC2W, unbit = CAMF_W2C;
124 TmCopy(tt, cam->camtoworld);
125 TmInvert(cam->camtoworld, cam->worldtocam);
126 break;
127 case CAM_W2C:
128 tt = NEXT(TransformPtr);
129 bit = CAMF_W2C, unbit = CAMF_NEWC2W;
130 TmCopy(tt, cam->worldtocam);
131 TmInvert(cam->worldtocam, cam->camtoworld);
132 break;
133 case CAM_FOV:
134 v = NEXT(double) / 2;
135 bit = CAMF_FOV;
136 if(cam->flag & CAMF_PERSP) {
137 if(v >= 180/2) v = 120/2;
138 v = tan( RADIANS(v) );
139 }
140 halffield = v;
141 sethalffield = 1;
142 break;
143 case CAM_HALFYFIELD:
144 cam->halfyfield = NEXT(double);
145 if(cam->flag & CAMF_PERSP)
146 cam->halfyfield *= cam->focus;
147 bit = CAMF_FOV;
148 break;
149 case CAM_HALFFIELD:
150 halffield = NEXT(double);
151 sethalffield = 1;
152 bit = CAMF_FOV;
153 break;
154 case CAM_ASPECT:
155 if((v = NEXT(double)) > 0.) {
156 if (!sethalffield)
157 halffield = GetHalfField(cam);
158 cam->frameaspect = v;
159 bit = CAMF_ASPECT;
160 setaspect = 1;
161 }
162 break;
163 case CAM_FOCUS:
164 if((v = NEXT(double)) > 0) {
165 if(cam->flag & CAMF_PERSP)
166 cam->halfyfield *= v / cam->focus;
167 cam->focus = v;
168 bit = CAMF_FOCUS;
169 }
170 break;
171 case CAM_NEAR:
172 cam->cnear = NEXT(double);
173 bit = CAMF_NEAR;
174 break;
175 case CAM_FAR:
176 cam->cfar = NEXT(double);
177 bit = CAMF_FAR;
178 break;
179 case CAM_STEREOSEP:
180 cam->stereo_sep = NEXT(double);
181 bit = CAMF_STEREOGEOM, unbit = CAMF_STEREOXFORM;
182 setstereogeom = 1;
183 break;
184 case CAM_STEREOANGLE:
185 cam->stereo_angle = NEXT(double);
186 bit = CAMF_STEREOGEOM, unbit = CAMF_STEREOXFORM;
187 setstereogeom = 1;
188 break;
189 case CAM_STEREOEYE:
190 cam->whicheye = NEXT(int);
191 bit = CAMF_EYE;
192 break;
193 case CAM_PERSPECTIVE: bit = CAMF_PERSP; goto flagbit;
194 case CAM_STEREO: bit = CAMF_STEREO; goto flagbit;
195 flagbit:
196 SETFLAG(cam->flag, bit, NEXT(int));
197 break;
198 case CAM_STEREYES:
199 memcpy(cam->stereyes, NEXT(TransformPtr), 2*sizeof(Transform));
200 bit = CAMF_STEREOXFORM, unbit = CAMF_STEREOGEOM;
201 break;
202 case CAM_STERHANDLES:
203 memcpy(cam->sterhandle, NEXT(Handle **), 2*sizeof(Handle *));
204 bit = CAMF_STEREOXFORM, unbit = CAMF_STEREOGEOM;
205 break;
206 case CAM_C2WHANDLE:
207 h = NEXT(Handle *);
208 if (cam->c2whandle) {
209 HandlePDelete(&cam->c2whandle);
210 }
211 cam->c2whandle = REFGET(Handle, h);
212 if (h) {
213 HandleRegister(&cam->c2whandle,
214 (Ref *)cam, cam->camtoworld, CamTransUpdate);
215 }
216 bit = CAMF_NEWC2W, unbit = CAMF_W2C;
217 break;
218
219 case CAM_W2CHANDLE:
220 h = NEXT(Handle *);
221 if (cam->w2chandle) {
222 HandlePDelete(&cam->w2chandle);
223 }
224 cam->w2chandle = REFGET(Handle, h);
225 if (h) {
226 HandleRegister(&cam->w2chandle,
227 (Ref *)cam, cam->worldtocam, CamTransUpdate);
228 }
229 bit = CAMF_W2C, unbit = CAMF_NEWC2W;
230 break;
231 case CAM_SPACE:
232 {
233 int space = NEXT(int);
234 if ( space != TM_EUCLIDEAN
235 && space != TM_HYPERBOLIC
236 && space != TM_SPHERICAL) {
237 OOGLError(0,"illegal space value %1d\n", space);
238 } else {
239 cam->space = space;
240 bit = CAMF_SPACE;
241 }
242 }
243 break;
244 case CAM_BGCOLOR:
245 cam->bgcolor = *NEXT(ColorA *);
246 break;
247 case CAM_BGIMAGE:
248 if (cam->bgimghandle) {
249 HandlePDelete(&cam->bgimghandle);
250 }
251 if (cam->bgimage) {
252 ImgDelete(cam->bgimage);
253 }
254 cam->bgimage = REFGET(Image, NEXT(Image *));
255 break;
256 case CAM_BGIMGHANDLE:
257 if (cam->bgimghandle) {
258 HandlePDelete(&cam->bgimghandle);
259 }
260 cam->bgimghandle = REFGET(Handle, NEXT(Handle *));
261 if (cam->bgimghandle) {
262 HandleRegister(&cam->bgimghandle,
263 (Ref *)cam, &cam->bgimage, HandleUpdRef);
264 }
265 break;
266 default:
267 OOGLError (0, "CamSet: Undefined attribute: %d", attr);
268 return NULL;
269 }
270 cam->changed &= ~unbit;
271 cam->changed |= bit;
272 attr = NEXT(int);
273 }
274
275 /*
276 (sethalffield) means we have a new halffield value, stored in local
277 var "halffield". (setaspect) means we have a new aspect ratio, and the
278 halffield must be updated in accordance with this. In this case,
279 "halffield" holds either the original halffield value, if a new one
280 hasn't been explicitly set with CAM_HALFFIELD or CAM_FOV, or the
281 new value, if it was explicitly set. All of these cases are dealt with
282 by the following call to SetHalfField.
283 */
284 if (setaspect || sethalffield)
285 SetHalfField(cam, halffield);
286
287 /* following works since the only way to change stereo parameters is
288 by using this routine */
289 if (setstereogeom)
290 CamStereoCompute(cam);
291
292 return cam;
293
294 #undef NEXT
295
296 }
297
298
299 /*-----------------------------------------------------------------------
300 * Function: CamGet
301 * Description: query a camera
302 * Args: *cam: the camera to query
303 * attr: the attribute to query
304 * value: attr's value is written here
305 * Returns: 1: attr is valid and value has been written
306 * 0: attr is valid but currently does not
307 * have a value
308 * -1: invalid attr
309 * Author: mbp
310 * Date: Thu Aug 8 10:09:10 1991
311 * Notes: At present, there are no camera attr's that might
312 * not be set, so 0 is never returned. This might change
313 * in the future.
314 */
315 int
CamGet(Camera * cam,int attr,void * value)316 CamGet(Camera *cam, int attr, void *value)
317 {
318 #define VALUE(type) ((type*)value)
319
320 switch (attr) {
321
322 case CAM_PERSPECTIVE:
323 *VALUE(int) = GETFLAG(cam->flag, CAMF_PERSP);
324 break;
325
326 case CAM_STEREO:
327 *VALUE(int) = GETFLAG(cam->flag, CAMF_STEREO);
328 break;
329
330 case CAM_C2W:
331 /* camtoworld is always up to date, so just copy */
332 TmCopy(cam->camtoworld, (TransformPtr)value);
333 break;
334
335 case CAM_W2C:
336 /* worldtocam is not always up to date, so update if necessary ... */
337 if (cam->flag & CAMF_NEWC2W ) {
338 TmInvert( cam->camtoworld, cam->worldtocam );
339 cam->flag &= ~CAMF_NEWC2W;
340 }
341 /* ... then copy */
342 TmCopy( cam->worldtocam, (TransformPtr)value );
343 break;
344
345 case CAM_FOV:
346 *VALUE(float) = 2 * ( (cam->flag & CAMF_PERSP)
347 ? DEGREES( atan( (double)(GetHalfField(cam)) ) )
348 : GetHalfField(cam));
349 break;
350
351 case CAM_HALFYFIELD:
352 *VALUE(float) = (cam->flag & CAMF_PERSP) ? cam->halfyfield / cam->focus
353 : cam->halfyfield;
354 break;
355
356 case CAM_HALFFIELD:
357 *VALUE(float) = GetHalfField(cam);
358 break;
359
360 case CAM_ASPECT:
361 *VALUE(float) = cam->frameaspect;
362 break;
363
364 case CAM_FOCUS:
365 *VALUE(float) = cam->focus;
366 break;
367
368 case CAM_NEAR:
369 *VALUE(float) = cam->cnear;
370 break;
371
372 case CAM_FAR:
373 *VALUE(float) = cam->cfar;
374 break;
375
376 case CAM_STEREOSEP:
377 *VALUE(float) = cam->stereo_sep;
378 break;
379
380 case CAM_STEREOANGLE:
381 *VALUE(float) = cam->stereo_angle;
382 break;
383
384 case CAM_STEREOEYE:
385 *VALUE(int) = cam->whicheye;
386 break;
387
388 case CAM_C2WHANDLE:
389 *VALUE(Handle *) = cam->c2whandle;
390 break;
391
392 case CAM_W2CHANDLE:
393 *VALUE(Handle *) = cam->w2chandle;
394 break;
395
396 case CAM_STEREYES:
397 memcpy(value, cam->stereyes, 2*sizeof(Transform));
398 break;
399
400 case CAM_STERHANDLES:
401 memcpy(value, cam->sterhandle, 2*sizeof(Handle *));
402 break;
403
404 case CAM_SPACE:
405 *VALUE(int) = cam->space;
406 break;
407
408 case CAM_BGCOLOR:
409 *VALUE(ColorA) = cam->bgcolor;
410 break;
411
412 case CAM_BGIMAGE:
413 *VALUE(Image *) = cam->bgimage;
414 break;
415
416 case CAM_BGIMGHANDLE:
417 *VALUE(Handle *) = cam->bgimghandle;
418 break;
419
420 default:
421 return -1;
422 break;
423 }
424 return 1;
425
426 #undef VALUE
427 }
428
429 void
CamDelete(Camera * cam)430 CamDelete( Camera *cam )
431 {
432 if(cam == NULL)
433 return;
434 if(cam->magic != CAMMAGIC) {
435 OOGLWarn("Internal warning: trying to CamDelete non-Camera %x (%x != %x)",
436 cam, cam->magic, CAMMAGIC);
437 return;
438 }
439 if(RefDecr((Ref *)cam) <= 0) {
440 cam->magic ^= 0x80000000; /* Invalidate */
441 if(cam->c2whandle) HandlePDelete( &cam->c2whandle );
442 if(cam->w2chandle) HandlePDelete( &cam->w2chandle );
443 if(cam->sterhandle[0]) HandlePDelete( &cam->sterhandle[0] );
444 if(cam->sterhandle[1]) HandlePDelete( &cam->sterhandle[1] );
445 if (cam->bgimghandle) HandlePDelete(&cam->bgimghandle);
446 if (cam->bgimage) ImgDelete(cam->bgimage);
447 OOGLFree(cam);
448 }
449 }
450
451 Camera *
CamCopy(Camera * src,Camera * dst)452 CamCopy( Camera *src, Camera *dst )
453 {
454 if (src == NULL) {
455 return NULL;
456 }
457 if (dst == NULL) {
458 dst = OOGLNewE(Camera, "CamCopy Camera");
459 }
460 #if 0
461 else
462 HandleDelete(dst->handle);
463 #endif
464 *dst = *src;
465 dst->ref_count = 1;
466 /*dst->handle = NULL;*/
467 return dst;
468 }
469
470 void
CamReset(Camera * cam)471 CamReset( Camera *cam )
472 {
473 Transform T;
474 int persp;
475
476 CamGet(cam, CAM_PERSPECTIVE, &persp);
477
478 switch (cam->space) {
479
480 case TM_EUCLIDEAN:
481 CamSet( cam,
482 CAM_NEAR, .07,
483 CAM_FAR, 100.0,
484 CAM_FOCUS, 3.0,
485 CAM_FOV, persp ? 40.0 : 2.2,
486 CAM_END);
487 break;
488
489 case TM_HYPERBOLIC:
490 CamSet( cam,
491 CAM_NEAR, .07,
492 CAM_FAR, 100.0,
493 CAM_FOCUS, 2.5,
494 CAM_FOV, persp ? 40.0 : 2.2,
495 CAM_END);
496 break;
497
498 case TM_SPHERICAL:
499 CamSet( cam,
500 CAM_NEAR, .05,
501 CAM_FAR, -.05,
502 CAM_FOCUS, 0.5,
503 CAM_FOV, persp ? 90.0 : 2.2,
504 CAM_END);
505 break;
506 }
507
508 TmSpaceTranslate( T, 0.0, 0.0, cam->focus, cam->space );
509 CamSet(cam, CAM_C2W, T, CAM_END);
510 }
511
512 /*
513 * Return camera's projection transform in proj.
514 * See CamView below for the range of the projection.
515 */
516 void
CamViewProjection(Camera * cam,Transform proj)517 CamViewProjection( Camera *cam, Transform proj )
518 {
519 float y;
520 float x;
521
522 y = cam->halfyfield;
523 if(cam->flag & CAMF_PERSP)
524 y *= cam->cnear / cam->focus;
525 x = cam->frameaspect * y;
526
527 if(cam->flag & CAMF_PERSP) {
528 TmPerspective( proj, -x, x, -y, y, cam->cnear, cam->cfar );
529 } else {
530 TmOrthographic( proj, -x, x, -y, y, cam->cnear, cam->cfar );
531 }
532 if (cam->flag & CAMF_STEREO)
533 TmConcat( cam->stereyes[cam->whicheye], proj, proj );
534 }
535
536 /*
537 * Computes complete transformation from world -> projected coordinates
538 * and leaves it in T.
539 * Projected coordinates map the visible world into -1 <= {X,Y,Z} <= 1,
540 * with Z = -1 at the near plane and Z = +1 at the far plane.
541 */
542 void
CamView(Camera * cam,Transform T)543 CamView( Camera *cam, Transform T )
544 {
545 Transform t;
546
547 CamViewProjection( cam, t );
548 if(cam->flag & CAMF_NEWC2W) {
549 TmInvert( cam->camtoworld, cam->worldtocam );
550 cam->flag &= ~CAMF_NEWC2W;
551 }
552 TmConcat( cam->worldtocam, t, T );
553 }
554
555 void
CamRotateX(Camera * cam,float angle)556 CamRotateX( Camera *cam, float angle )
557 {
558 CtmRotateX( cam->camtoworld, angle );
559 cam->flag |= CAMF_NEWC2W;
560 }
561
562 void
CamRotateY(Camera * cam,float angle)563 CamRotateY( Camera *cam, float angle )
564 {
565 CtmRotateY( cam->camtoworld, angle );
566 cam->flag |= CAMF_NEWC2W;
567 }
568
569 void
CamRotateZ(Camera * cam,float angle)570 CamRotateZ( Camera *cam, float angle )
571 {
572 CtmRotateZ( cam->camtoworld, angle );
573 cam->flag |= CAMF_NEWC2W;
574 }
575
576 /* translate the camera, using the camera's notion of what space it
577 is in */
578 void
CamTranslate(Camera * cam,float tx,float ty,float tz)579 CamTranslate( Camera *cam, float tx, float ty, float tz )
580 {
581 Transform T;
582
583 TmSpaceTranslate( T, tx, ty, tz, cam->space );
584 TmConcat(T, cam->camtoworld, cam->camtoworld);
585 cam->flag |= CAMF_NEWC2W;
586 }
587
588 /* CamScale is a noop if the camera is not in Euclidean space */
589 void
CamScale(Camera * cam,float sx,float sy,float sz)590 CamScale( Camera *cam, float sx, float sy, float sz )
591 {
592 if (cam->space == TM_EUCLIDEAN) {
593 CtmScale( cam->camtoworld, sx, sy, sz );
594 cam->flag |= CAMF_NEWC2W;
595 }
596 }
597
598 void
CamAlignZ(Camera * cam,float x,float y,float z)599 CamAlignZ( Camera *cam, float x, float y, float z )
600 {
601 Point3 axis;
602
603 axis.x = x;
604 axis.y = y;
605 axis.z = z;
606 CtmAlignZ( cam->camtoworld, &axis );
607 cam->flag |= CAMF_NEWC2W;
608 }
609
610 /*
611 * Apply T to camera as seen by world (== T^-1 to world, as seen by camera)
612 */
613 void
CamTransform(Camera * cam,Transform T)614 CamTransform( Camera *cam, Transform T )
615 {
616 TmConcat(T, cam->camtoworld, cam->camtoworld);
617 cam->flag |= CAMF_NEWC2W;
618 }
619
620 static void
CamStereoCompute(Camera * cam)621 CamStereoCompute( Camera *cam )
622 {
623 float tanconv = tan(cam->stereo_angle);
624 TmTranslate( cam->stereyes[CAM_RIGHT], cam->stereo_sep, 0., 0. );
625 TmTranslate( cam->stereyes[CAM_LEFT], -cam->stereo_sep, 0., 0. );
626 cam->stereyes[CAM_RIGHT][TMZ][TMX] = -tanconv;
627 cam->stereyes[CAM_LEFT][TMZ][TMX] = tanconv;
628 }
629
630 /*-----------------------------------------------------------------------
631 * Function: SetHalfField
632 * Description: set camera's "halffield" value
633 * Args: *cam: the camera
634 * halffield: the halffied value to set to
635 * Returns: nothing
636 * Author: mbp
637 * Date: Wed Aug 21 14:26:43 1991
638 * Notes: This procedure modifies the halfyfield member of cam
639 * in such a way as to guarantee that the min half-width
640 * of the view window is halffield. This depends on the
641 * camera's current aspect ratio.
642 */
643 static void
SetHalfField(Camera * cam,float halffield)644 SetHalfField( Camera *cam, float halffield )
645 {
646 cam->halfyfield =
647 (cam->frameaspect < 1 && cam->frameaspect > 0)
648 ? halffield / cam->frameaspect
649 : halffield;
650 if(cam->flag & CAMF_PERSP)
651 cam->halfyfield *= cam->focus;
652 }
653
654 /*-----------------------------------------------------------------------
655 * Function: GetHalfField
656 * Description: return camera's "halffield" value
657 * Args: *cam: the camera
658 * Returns: the halffield value
659 * Author: mbp
660 * Date: Wed Aug 21 14:29:31 1991
661 * Notes: the "halffield" is the min half-width of the view
662 * window. If the aspect ratio is >= 1, this is
663 * the vertical half-width (halfyfield). If the aspect
664 * ratio is < 1, this is the horizontal half-width.
665 */
666 static float
GetHalfField(Camera * cam)667 GetHalfField( Camera *cam )
668 {
669 float v = cam->halfyfield;
670 if(cam->frameaspect < 1) v *= cam->frameaspect;
671 if(cam->flag & CAMF_PERSP) v /= cam->focus;
672 return v;
673 }
674
675 /*
676 * Merge one Camera's changed values into another Camera
677 */
678 Camera *
CamMerge(Camera * src,Camera * dst)679 CamMerge(Camera *src, Camera *dst)
680 {
681 int chg;
682 float fov;
683
684 if(src == NULL) return dst;
685 if(dst == NULL) return NULL;
686
687 chg = src->changed;
688
689 if(chg & CAMF_NEWC2W)
690 CamSet(dst, CAM_C2WHANDLE, src->c2whandle, CAM_C2W, src->camtoworld, CAM_END);
691 if(chg & CAMF_STEREOGEOM)
692 CamSet(dst, CAM_STEREOSEP, src->stereo_sep,
693 CAM_STEREOANGLE, src->stereo_angle, CAM_END);
694 if(chg & CAMF_STEREOXFORM)
695 CamSet(dst, CAM_STEREYES, src->stereyes,
696 CAM_STERHANDLES, src->sterhandle, CAM_END);
697 if(chg & CAMF_W2C)
698 CamSet(dst, CAM_W2CHANDLE, src->w2chandle,
699 CAM_W2C, src->worldtocam, CAM_END);
700 CamGet(src, CAM_FOV, &fov);
701 if(chg & CAMF_FOCUS) CamSet(dst, CAM_FOCUS, src->focus, CAM_END);
702 if(chg & CAMF_PERSP) CamSet(dst,CAM_PERSPECTIVE,src->flag&CAMF_PERSP,CAM_END);
703 if(chg & CAMF_FOV) CamSet(dst, CAM_FOV, fov, CAM_END);
704 if(chg & CAMF_ASPECT) CamSet(dst, CAM_ASPECT, src->frameaspect, CAM_END);
705 if(chg & CAMF_NEAR) dst->cnear = src->cnear;
706 if(chg & CAMF_FAR) dst->cfar = src->cfar;
707 if(chg & CAMF_EYE) dst->whicheye = src->whicheye;
708 if(chg & CAMF_STEREO) CamSet(dst,CAM_STEREO,src->flag&CAMF_STEREO,CAM_END);
709 if(chg & CAMF_SPACE) dst->space = src->space;
710 return dst;
711 }
712
713 /*
714 * Local Variables: ***
715 * mode: c ***
716 * c-basic-offset: 2 ***
717 * End: ***
718 */
719
720