/***********************************************************************/ /* Open Visualization Data Explorer */ /* (C) Copyright IBM Corp. 1989,1999 */ /* ALL RIGHTS RESERVED */ /* This code licensed under the */ /* "IBM PUBLIC LICENSE - Open Visualization Data Explorer" */ /***********************************************************************/ #include #include #include #include #include #include #define MIN(a, b) ((a)>(b) ? (b) : (a)) #define MAX(a, b) ((a)<(b) ? (b) : (a)) #define AMIN(a, b) ((fabs(a))>(fabs(b)) ? (b) : (a)) #define AMAX(a, b) ((fabs(a))<(fabs(b)) ? (b) : (a)) #define DEG2RAD(x) (x/180.0*M_PI) /* degrees to radians */ #define RAD2DEG(x) (x/M_PI*180.0) /* u guess */ #define IsEmpty(o) (DXGetObjectClass(o) == CLASS_FIELD) && \ (DXEmptyField((Field)o)) #define FUZZ ((float) 1e-37) /* for floating point number compares - this is * not MINFLOAT as defined in , * because it is 1e-45, which is denormalized * and will cause the i860 chip to trap. */ #define TOOBIG ((float) 1e37) /* similiar idea for large values. this is * smaller than DXD_MAX_FLOAT because it is a test * for something which will subsequently be * multiplied and divided further and there * needs to still be a little headroom. */ /* directions the autocamera can point */ #define D_UNDEFINED 0x00 #define D_OFF 0x80 #define D_RIGHT 0x01 #define D_OFFRIGHT 0x81 #define D_LEFT 0x02 #define D_OFFLEFT 0x82 #define D_TOP 0x03 #define D_OFFTOP 0x83 #define D_BOTTOM 0x04 #define D_OFFBOTTOM 0x84 #define D_DIAGONAL 0x05 #define D_OFFDIAGONAL 0x85 #define D_FRONT 0x06 #define D_OFFFRONT 0x86 #define D_BACK 0x07 #define D_OFFBACK 0x87 #define MAXDIR 16 /* longest acceptable direction string + 1 */ struct cameraparms { int isauto; /* camera or autocamera */ int isupdate; /* updating an existing camera */ int isobject; /* first parm is object */ Point bbox[8]; /* if object, bounding box */ int nobox; /* if set, object with no bbox */ Point t; /* to point (look-at point) */ Point f; /* from point or from direction vector */ float width; /* viewport width in user's units (ortho only) */ int hsize; /* horizontal width of image size in pixels */ float aspect; /* aspect ratio of image (height/width) */ Vector u; /* up vector */ int upset; /* set if up is specified by user */ int doperspective; /* see flags below */ float angle; /* perspective: view angle */ float fov; /* resulting field of view */ RGBColor color; /* background color */ }; /* doperspective flags */ #define C_ORTHOGRAPHIC 0 /* orthographic projection */ #define C_PERSP_ANGLE 1 /* perspective, specifying view angle */ #define C_PERSP_DIST 2 /* perspective, distance along view vector */ #define C_PERSP_35MM 3 /* perspective, 35mm camera lens */ /* default perspective angle */ #if 0 #define P_ANGLE 57.9 #define P_FOV 1.106338 /* 2 * tan(57.9/2) */ #else #define P_ANGLE 30.0 #define P_FOV 0.53590 /* 2 * tan(30.0/2) */ #endif /* prototypes */ static Camera single_camera(Object *in, int isauto, int isupdate); static Camera make_cam(struct cameraparms *p); static void default_camera(struct cameraparms *p, int isauto); static Error existing_camera(struct cameraparms *p, Camera c, int isauto); static Error parse_inputs(Object *in, struct cameraparms *p); static Error good_camera(struct cameraparms *p); static Error first_parms(Object *in, struct cameraparms *p); static Error second_parms(Object *in, struct cameraparms *p); static Error third_parms(Object *in, struct cameraparms *p); static int find_direction(char *); static Error set_direction(char *, struct cameraparms *p); extern Error DXColorNameToRGB(char *, RGBColor *); /* external module entry points */ int m_AutoCamera(Object *in, Object *out) { out[0] = (Object)single_camera(in, 1, 0); return (out[0] ? OK : ERROR); } int m_Camera(Object *in, Object *out) { out[0] = (Object)single_camera(in, 0, 0); return (out[0] ? OK : ERROR); } int m_UpdateCamera(Object *in, Object *out) { out[0] = (Object)single_camera(in, 0, 1); return (out[0] ? OK : ERROR); } /* internal routines */ static Camera single_camera(Object *in, int isauto, int isupdate) { struct cameraparms p; /* initialize the cameraparms structure */ if (isupdate) { if (!existing_camera(&p, (Camera)in[0], isauto)) return NULL; in++; /* space past initial camera */ } else default_camera(&p, isauto); /* compute camera parms from inputs */ if (!parse_inputs(in, &p)) return NULL; /* error checks */ if (!good_camera(&p)) return NULL; /* generate camera object and return */ return make_cam(&p); } /* */ static void default_camera(struct cameraparms *p, int isauto) { /* zero everything */ memset((char *)p, '\0', sizeof(struct cameraparms)); /* now set things which shouldn't default to zero */ p->isauto = isauto; p->f = DXPt(0.0, 0.0, 1.0); p->width = 100.0; p->hsize = 640; p->aspect = 0.75; p->u = DXPt(0.0, 1.0, 0.0); p->angle = P_ANGLE; p->fov = P_FOV; } /* */ static Error existing_camera(struct cameraparms *p, Camera c, int isauto) { /* zero everything */ memset((char *)p, '\0', sizeof(struct cameraparms)); if (!c || DXGetObjectClass((Object)c) != CLASS_CAMERA) { DXSetError(ERROR_INVALID_DATA, "#10660", "camera"); return ERROR; } p->isauto = isauto; p->isupdate = 1; /* extract defaults from existing camera */ DXGetView(c, &p->f, &p->t, &p->u); DXGetCameraResolution(c, &p->hsize, NULL); /* if initially an ortho camera, set the defaults for perspective * anyway in case they just toggle the perspective flag on without * setting the view angle. * if already a perspective camera, extract the existing fov and * invert it back to an angle because that's how the user specifies * the fov - the code which comes later expects to have to do the * angle-to-fov conversion. set width in case toggling back to ortho. */ if (DXGetOrthographic(c, &p->width, &p->aspect)) { p->angle = P_ANGLE; p->fov = P_FOV; } else { DXGetPerspective(c, &p->fov, &p->aspect); p->angle = RAD2DEG(atan(p->fov / 2.)) * 2.; p->width = 2 * DXLength(DXSub(p->f, p->t)) * tan(DEG2RAD(p->angle)); p->doperspective = 1; } DXGetBackgroundColor(c, &p->color); return OK; } /* */ static Error parse_inputs(Object *in, struct cameraparms *p) { /* the simple parms: resolution, aspect, up vector and perspective flag, * background color */ if (!first_parms(in, p)) return NULL; /* the more complicated ones: to point, from vector */ if (!second_parms(in, p)) return NULL; /* the most complicated ones: from point, width and angle */ if (!third_parms(in, p)) return NULL; return OK; } /* */ static Error first_parms(Object *in, struct cameraparms *p) { char *colorname; /* resolution (final image width in pixels) */ if (in[3] && (!DXExtractInteger(in[3], &p->hsize) || p->hsize <= 0)) { DXSetError(ERROR_BAD_PARAMETER, "#10020", "resolution"); return NULL; } /* aspect ratio = horizontal pixels / vertical pixels */ if (in[4] && (!DXExtractFloat(in[4], &p->aspect) || p->aspect <= 0.0)) { DXSetError(ERROR_BAD_PARAMETER, "#10090", "aspect"); return NULL; } /* up vector */ if (in[5]) { if (!DXExtractParameter(in[5], TYPE_FLOAT, 3, 1, (Pointer)&p->u)) { DXSetError(ERROR_BAD_PARAMETER, "#10230", "up", 3); return NULL; } if (DXLength(p->u) < FUZZ) { DXSetError(ERROR_BAD_PARAMETER, "#11822", "up"); return NULL; } p->upset++; } /* perspective flag */ if (in[6]) { if (!DXExtractInteger(in[6], &p->doperspective)) { DXSetError(ERROR_BAD_PARAMETER, "#10020", "perspective"); return NULL; } if (p->doperspective < 0 || p->doperspective > 1) { DXSetError(ERROR_BAD_PARAMETER, "#10040", "perspective", 0, 1); return NULL; } } /* background color */ if (in[8]) { /* color name */ if (DXExtractString(in[8], &colorname)) { if (!DXColorNameToRGB(colorname, &p->color)) { return NULL; } } /* R,G,B vector */ else if (DXExtractParameter(in[8], TYPE_FLOAT, 3, 1, (Pointer)&p->color)) ; /* error */ else { DXSetError(ERROR_BAD_PARAMETER, "#10510", "background"); return NULL; } } return OK; } /* */ static Error second_parms(Object *in, struct cameraparms *p) { char *direction; Point bbox2[8]; /* to: object or explicit point * can default for Camera, required for AutoCamera */ if (in[0]) { /* explicit point */ if (DXExtractParameter(in[0], TYPE_FLOAT, 3, 1, (Pointer)&p->t)) ; /* object */ else if (DXBoundingBox(in[0], p->bbox)) { p->t.x = (p->bbox[7].x + p->bbox[0].x) / 2; p->t.y = (p->bbox[7].y + p->bbox[0].y) / 2; p->t.z = (p->bbox[7].z + p->bbox[0].z) / 2; p->isobject++; } /* if the input is a genuine empty field, use the defaults * without a warning. */ else if (IsEmpty(in[0])) { DXResetError(); p->nobox++; /* anything else gets a warning */ } else { DXResetError(); DXWarning("#4020"); p->nobox++; } } /* autocamera can't default the to-point */ if (!in[0] && p->isauto && !p->isupdate) { DXSetError(ERROR_BAD_PARAMETER, "#10000", "object"); return NULL; } /* from: vector or string (AutoCamera) * vector or object (Camera) */ if (in[1]) { /* vector/point */ if (DXExtractParameter(in[1], TYPE_FLOAT, 3, 1, (Pointer)&p->f)) ; /* string */ else if (DXExtractString(in[1], &direction)) { if (p->isauto) { if (!set_direction(direction, p)) return NULL; } else { DXSetError(ERROR_BAD_PARAMETER, "#10460", "from"); return NULL; } } /* object */ else if (DXBoundingBox(in[1], bbox2)) { if (p->isauto) { DXSetError(ERROR_BAD_PARAMETER, "#10510", "`direction'"); return NULL; } p->f.x = (bbox2[7].x + bbox2[0].x) / 2; p->f.y = (bbox2[7].y + bbox2[0].y) / 2; p->f.z = (bbox2[7].z + bbox2[0].z) / 2; } /* error */ else { if (p->isauto) DXSetError(ERROR_BAD_PARAMETER, "#10510", "`direction'"); else DXSetError(ERROR_BAD_PARAMETER, "#10460", "from"); return NULL; } /* check for from == to */ if (p->isauto && DXLength(p->f) < FUZZ) { DXSetError(ERROR_BAD_PARAMETER, "#11822", "direction"); return NULL; } if (!p->isauto && DXLength(DXSub(p->f, p->t)) < FUZZ) { DXSetError(ERROR_BAD_PARAMETER, "#11010", "`from'", "`to'"); return NULL; } } /* for autocamera, default to front; for camera, from is already set */ else if (p->isauto && !p->isupdate) { if (!set_direction("front", p)) return NULL; } return OK; } /* */ static Error third_parms(Object *in, struct cameraparms *p) { float diag = 0.0; /* width: scalar or object for both camera and autocamera. */ if (in[2]) { if (!p->isauto && p->doperspective) { DXWarning("width ignored for perspective camera"); } else { /* explicit width */ if (DXExtractFloat(in[2], &p->width)) { if (p->width <= 0) { DXSetError(ERROR_BAD_PARAMETER, "#10090", "width"); return NULL; } } /* object */ else if(_dxf_BBoxDistance(in[2], &diag, BB_DIAGONAL)) { if (diag == 0) /* object is single point */ p->width = 1.0; else p->width = (p->aspect >= 1 ? diag : diag / p->aspect) * 1.01; } /* if empty field, * or object with no bounding box and first object had no bbox, * then use default width */ else if (IsEmpty(in[2]) || p->nobox) DXResetError(); /* error */ else { DXSetError(ERROR_BAD_PARAMETER, "#10560", "width"); return NULL; } } } /* camera default already set; autocamera can default if first parm * is an object. */ else if (p->isauto && !p->isupdate) { if (!p->isobject && !p->nobox) { DXSetError(ERROR_BAD_PARAMETER, "#10470", "width", "object"); return NULL; } diag = DXLength(DXSub(p->bbox[7], p->bbox[0])); if (diag == 0) p->width = 1.0; else p->width = (p->aspect >= 1 ? diag : diag / p->aspect) * 1.01; } /* if perspective flag is set, compute angle/distance */ switch (p->doperspective) { case 0: /* ortho */ break; case 1: /* view angle */ if (in[7]) { if (!DXExtractFloat(in[7], &p->angle) || p->angle < 0.0 || p->angle >= 180.0) { DXSetError(ERROR_BAD_PARAMETER, "#10110", "angle", 0, 180); return NULL; } /* allow user to turn off perspective with angle of zero */ if (p->angle < FUZZ) p->doperspective = 0; } if (p->doperspective) { p->fov = 2 * tan(DEG2RAD(p->angle/2)); if (p->fov < FUZZ) p->doperspective = 0; } break; } /* for autocamera, adjust the 'from' point to be a reasonable distance * along the given direction so it works for either ortho or perspective. * (the actual location of 'from' doesn't matter for ortho, and this * makes it easier on the Image macro under some circumstances.) */ if (p->isauto) { p->f = DXNormalize(p->f); /* turn off perspective if the field of view is too small and * would cause fp overflows in the normalize/multiply step. */ if (p->fov < FUZZ || ((double)p->width/p->fov) > TOOBIG) { p->doperspective = 0; /* if the length of the direction vector is too small compared * to the value of the to point, it will get lost when they * are added, and it will look like to and from are the same. * scale the length of the from vector so it is far enough away. */ if (DXLength(p->t) > 1.0) p->f = DXMul(p->f, DXLength(p->t)); } else /* adjust the from point so it is at the right distance to match * the angle and viewport width relative to the 'to' point. */ p->f = DXMul(p->f, p->width / p->fov); /* autocamera 'from' is relative to the 'to' point. the camera * calls want absolute points, so adjust 'from' here. */ p->f = DXAdd(p->f, p->t); } return OK; } #define SIN05 0.087 #define COS05 0.996 #define SIN15 0.259 #define COS15 0.966 #define SIN25 0.423 #define COS25 0.906 #define SIN35 0.574 #define SIN45 0.707 /* */ static Error set_direction(char *direction, struct cameraparms *p) { int direct; if ((direct = find_direction(direction)) == D_UNDEFINED) { DXSetError(ERROR_BAD_PARAMETER, "#10480", "direction"); return ERROR; } switch(direct) { case D_FRONT: /* deltas: x = 0, y = 0, z = ++ */ p->f = DXPt(0., 0., 1.); break; case D_OFFFRONT: /* deltas: x = +, y = +, z = ++ */ p->f = DXPt(SIN15, SIN15, COS15); break; case D_BACK: /* deltas: x = 0, y = 0, z = -- */ p->f = DXPt(0., 0., -1.); break; case D_OFFBACK: /* deltas: x = +, y = +, z = -- */ p->f = DXPt(SIN15, SIN15, -COS15); break; case D_TOP: /* deltas: x = 0, y = ++, z = 0; up = -z */ p->f = DXPt(0., 1., 0.); if (!p->upset) p->u = DXVec(0., 0., -1.); break; case D_OFFTOP: /* deltas: x = -, y = ++, z = - */ p->f = DXPt(-SIN15, COS15, -SIN15); if (!p->upset) p->u = DXVec(0., 0., -1.); break; case D_BOTTOM: /* deltas: x = 0, y = --, z = 0; up = +z */ p->f = DXPt(0., -COS15, 0.); if (!p->upset) p->u = DXVec(0., 0., 1.); break; case D_OFFBOTTOM: /* deltas: x = +, y = --, z = +; up = +z */ p->f = DXPt(SIN15, -COS15, SIN15); if (!p->upset) p->u = DXVec(0., 0., 1.); break; case D_RIGHT: /* deltas: x = ++, y = 0, z = 0 */ p->f = DXPt(1., 0., 0.); break; case D_OFFRIGHT: /* deltas: x = ++, y = +, z = + */ p->f = DXPt(COS05, SIN15, SIN15); break; case D_LEFT: /* deltas: x = --, y = 0, z = 0 */ p->f = DXPt(-1., 0., 0.); break; case D_OFFLEFT: /* deltas: x = --, y = +, z = + */ p->f = DXPt(-COS05, SIN15, SIN15); break; case D_DIAGONAL: /* deltas: x = +, y = +, z = + */ p->f = DXPt(SIN45, SIN45, SIN45); break; case D_OFFDIAGONAL: /* deltas: x = ++, y = ++, z = +++ */ p->f = DXPt(SIN35, SIN35, SIN45); break; default: DXSetError(ERROR_BAD_PARAMETER, "#10480", "direction"); return ERROR; } return OK; } /* */ static Error good_camera(struct cameraparms *p) { Vector x; /* try to catch bad cameras */ /* make sure from != to */ if (fabs(DXLength(DXSub(p->f, p->t))) < FUZZ) { if (p->isauto) DXSetError(ERROR_BAD_PARAMETER, "#11822", "direction"); else DXSetError(ERROR_BAD_PARAMETER, "#11010", "to", "from"); return NULL; } /* is there a bad up vector? */ x = DXCross(p->u, DXSub(p->f, p->t)); if (fabs(DXLength(x)) < FUZZ) { DXWarning("up vector in line with viewpoint."); p->u.z += 0.01; x = DXCross(p->u, DXSub(p->f, p->t)); if (fabs(DXLength(x)) < FUZZ) { p->u.z -= 0.01; p->u.x += 0.01; DXWarning("up.X changed to %f", p->u.x); } else DXWarning("up.Z changed to %f", p->u.z); } return OK; } /* libdx calls to actually build the camera. */ static Camera make_cam(struct cameraparms *p) { Camera cam = NULL; if (!(cam = DXNewCamera())) return NULL; if (p->doperspective) { if (!DXSetPerspective(cam, p->fov, p->aspect)) goto error; } else { if (!DXSetOrthographic(cam, p->width, p->aspect)) goto error; } if (!DXSetResolution(cam, p->hsize, 1.)) goto error; if (!DXSetView(cam, p->f, p->t, p->u)) goto error; if (!DXSetBackgroundColor(cam, p->color)) goto error; return cam; error: DXDelete((Object)cam); return NULL; } /* * match a string with a direction. case insensitive, and space, * dot, dash and underscore all match the "off" directions. */ static int find_direction(char *inval) { char dbuf[MAXDIR], *cp; int dir = D_UNDEFINED; /* make a copy and convert to lower case */ strncpy(dbuf, inval, MAXDIR-1); dbuf[MAXDIR-1] = '\0'; for (cp = dbuf; *cp; cp++) if (isupper(*cp)) *cp = tolower(*cp); cp = dbuf; again: switch(*cp) { case 'r': if (!strcmp(cp, "right")) return (dir | D_RIGHT); break; case 'l': if (!strcmp(cp, "left")) return (dir | D_LEFT); break; case 't': if (!strcmp(cp, "top")) return (dir | D_TOP); break; case 'b': if (!strcmp(cp, "back")) return (dir | D_BACK); if (!strcmp(cp, "bottom")) return (dir | D_BOTTOM); break; case 'f': if (!strcmp(cp, "front")) return (dir | D_FRONT); break; case 'd': if (!strcmp(cp, "diagonal")) return (dir | D_DIAGONAL); break; case 'o': if (strncmp(cp, "off", 3)) return D_UNDEFINED; cp += 3; switch(*cp) { /* valid things to skip as separators */ case '-': case '_': case ' ': cp++; /* and fall thru to accept offdirection with no sep */ case 'r': case 'l': case 't': case 'b': case 'f': case 'd': dir = D_OFF; goto again; default: break; } break; default: break; } return D_UNDEFINED; }