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