#include #include #include "plantsim.h" #include "plants.h" #include "database.h" #include "collIntMat.h" #include "testDB.h" #include "intersect.h" #include "polyPairTest.h" #include "matrix.h" #define LOOPMAX 50 #define ANGLEBOUND 0.342020143 /* acos (70) */ #define RANDIR ((Real)(rand()*2 - RAND_MAX) / (Real) RAND_MAX) static Boolean growVinePlant(VinePlant *, ConNode *, ObjDB *, Real); static int growVineSeg(VineSeg *, ConNode *, Real, Real); static int growLeaf(LeafNode *, ConNode *, Real, Real); static VineSeg *createVineSegs(VineSeg *, ObjDB *, Real); static LeafNode *createLeaf(VineSeg *, ObjDB *, Real); static VineSeg *testVineSeg(Tupple, Tupple, Tupple, ObjDB *, Real, VineSeg *, VineSeg *); static void calcVineTrans(VineSeg *); static Boolean updateVineSegTrans(Real, void *); static Boolean updateLeafTrans(Real, void *); static Boolean orthVec(Tupple, Tupple *); static void calcRotate(Tupple, Real, Transform *); static void updateCIM(Ulong, VinePlant *, CollIntMat *); static ObjNode canSeg; static ObjNode canLeaf; static Tupple translate; extern Boolean quiet; extern Boolean new_face; extern Real cylHeight; extern Real cylr; extern Boolean verbose; extern Real c1; extern Real c2; extern int c3; extern Real c4; extern Real c5; extern Real c6; extern Real c7; extern Real c8; extern Real c9; extern Real c10; extern Tupple lightSource; extern Tupple gravity; extern FILE *outFile; extern char *outName; extern OutFormat outType; extern Ulong lastEnvObj; extern Real leafWidth; extern int delay; extern int frameNum; extern int frameInc; extern int endFrame; extern int nextFrame; extern OutFunc outFunc; extern char *scriptName; extern char *mtlLibName; #ifdef STATS extern Ulong vineSegsGenerated; extern Ulong leavesGenerated; #endif /* * Plant growing function. Given a list of plants, and an environment * consisting of objects, grows them. * * Params database database of objects to grow around * plantList list of plants to grow * * Globals outFile output FILE descriptor * frameNum the current frame number * * Returns */ void growPlants(database, plantList) ObjDB *database; PlantNode *plantList; { Boolean finished = False; PlantNode *pl, *ptemp; ConNode *cn = NULL; Status st = Nothing; Real time = 1.0; while (!finished) { if (!quiet) fprintf(stderr, "Growing stage ...\n"); finished = True; for (pl=plantList; pl != NULL; pl=pl->next) { /* go through all the plants, updating the transformation matrices * and carry out other functions required by their growing */ if (pl->stopped) continue; finished = False; switch (pl->type) { case Vine: if (!growVinePlant((VinePlant *)pl->pl, cn, database, time)) { fprintf(stderr, "Warning, had problem growing a "); fprintf(stderr, "plant. Ignoring\n"); pl->stopped = True; } if (((VinePlant *)pl->pl)->gsegs == NULL && ((VinePlant *)pl->pl)->gleaves == NULL && ((VinePlant *)pl->pl)->potHead == NULL) pl->stopped = True; break; default: break; } } if (cn != NULL) { destroyConNodeList(cn); cn = NULL; } if (!quiet) fprintf(stderr, "Collision Detection stage ...\n"); st = Nothing; /* Now test if the growth causes any intersections */ cn = collisionDetect(*database, &st, &time); /* * output only when nextFrame == frameNum. Also stop once we go past * the last requested frame. */ if (frameNum > endFrame) return; /* check to see if we need to output the current state of the database */ if (nextFrame == frameNum) { nextFrame += frameInc; outFunc(database->objects, plantList); } /* increment the frame count */ frameNum++; } outFunc(database->objects, plantList); } /* * Sets up the canonical vine segment cylinder which all the vine plant * segments are based upon. Height is along the z-axis of its local ref * frame, and the end faces lie in the x-y plane. * * Params n number of faces * radius radius of cylinder * * Globals canSeg place where created cylinder object is stored * cylHeight height of cylinder * translate used to store the values that enable the cylinder * to be translated to the origin * new_face Used when setting up the polygons * * Returns the global canSeg will contain the canonical vine segment */ void createCanonicalVineSeg(n, radius) int n; Real radius; { Real theta, st, ct; Tupple *p; int i, j, k; /* initialize canSeg */ canSeg.va.total = 0; canSeg.va.v = NULL; canSeg.vn.total = 0; canSeg.vn.v = NULL; canSeg.polys.total = 0; canSeg.polys.p = NULL; canSeg.min.i = 0.0; canSeg.min.j = 0.0; canSeg.min.k = 0.0; canSeg.max = canSeg.min; canSeg.gmin = canSeg.min; canSeg.gmax = canSeg.min; canSeg.seads = NULL; canSeg.loc2glob = NULL; canSeg.glob2loc = NULL; canSeg.ctrans = NULL; canSeg.info = NULL; canSeg.next = NULL; canSeg.valid = False; p = (Tupple *)malloc(sizeof(Tupple) * (2 * n + 1)); if (p == NULL) { fprintf(stderr, "Error: couldn't malloc Tupple Array\n"); exit(1); } theta = 2.0 * M_PI / (Real) n; st = sin(theta); ct = cos(theta); /* set up first points */ p[1].i = radius; p[1].j = 0.0; p[1].k = 0.0; p[i=n+1].i = radius; p[i].j = 0.0; p[i].k = cylHeight; /* calculate rest of points by rotating previous point by theta degrees * about the z-axis */ for (i=2; i <= n; i++) { j = i - 1; p[i].i = p[j].i * ct + p[j].j * st; p[i].j = p[j].i * -st + p[j].j * ct; p[i].k = 0.0; k = i + n; p[k].i = p[i].i; p[k].j = p[i].j; p[k].k = cylHeight; } canSeg.va.total = 2 * n; canSeg.va.v = p; /* set up the polygons */ /* end faces */ new_face = True; for (i=n; i >= 1; i--) obj_face(&canSeg, i, 0); new_face = True; for (i=n+1; i <= n+n; i++) obj_face(&canSeg, i, 0); /* set up side faces */ for (i=1; i <= n; i++) { new_face = True; obj_face(&canSeg, i, 0); if ((j=i+1) > n) j = 1; obj_face(&canSeg, j, 0); obj_face(&canSeg, j+n, 0); obj_face(&canSeg, i+n, 0); } zeroObject(&canSeg, &translate); translate.i = 0.0 - translate.i; translate.j = 0.0 - translate.j; translate.k = 0.0 - translate.k; calcObjInfo(&canSeg); } /* * Sets up the canonical leaf, in a similar vein to the canonical segment. * Consists of 2 polygons, back to back, lying in the x-y plane. * * Params x1, x2, y these params are used to set up the 4 points: * x1, 2*y * 0,y x2,y * x1,0 * * Globals canLeaf the canonical leaf object * new_face used to help set up the polygons * * Returns the canLeaf is set up */ void createCanonicalLeaf(x1, x2, y) Real x1, x2, y; { Tupple *p; int i; /* initialize canLeaf */ canLeaf.va.total = 4; canLeaf.va.v = NULL; canLeaf.vn.total = 0; canLeaf.vn.v = NULL; canLeaf.polys.total = 0; canLeaf.polys.p = NULL; canLeaf.min.i = 0.0; canLeaf.min.j = 0.0; canLeaf.min.k = 0.0; canLeaf.max = canLeaf.min; canLeaf.gmin = canLeaf.min; canLeaf.gmax = canLeaf.min; canLeaf.seads = NULL; canLeaf.loc2glob = NULL; canLeaf.glob2loc = NULL; canLeaf.ctrans = NULL; canLeaf.info = NULL; canLeaf.next = NULL; canLeaf.valid = False; p = (Tupple *)malloc(sizeof(Tupple) * 5); if (p == NULL) { fprintf(stderr, "Error: couldn't malloc Tupple array\n"); exit(1); } /* set up 4 points */ p[1].i = 0.0; p[1].j = y; p[1].k = 0.0; p[2].i = x1; p[2].j = 2.0 * y; p[2].k = 0.0; p[3].i = x2; p[3].j = y; p[3].k = 0.0; p[4].i = x1; p[4].j = 0.0; p[4].k = 0.0; canLeaf.va.v = p; new_face = True; for (i=1; i <= 4; i++) obj_face(&canLeaf, i, 0); new_face = True; for (i=4; i >= 1; i--) obj_face(&canLeaf, i, 0); calcObjInfo(&canLeaf); } /* * Given a vine plant, will take care of updating all its segments and leaves, * and adding new segments and leaves. * Passes the contact list to vine segment and leaf growing functions, to test * to see whether any of the units (vine segs or leaves) are involved in * any collisions. * time ( 0 <= time <= 1) indicates how far from the last frame things have * progressed. If there were collisions, t will be <= 1. * * Params vp vine plant to grow * cn contact list * database database of objects * time time since last frame * * Globals lastEnvObj id of the last enviroment object * delay the count before a leaf starts to grow/is created * * Returns True upon successful completion * False if there were any problems */ static Boolean growVinePlant(vp, cn, database, time) VinePlant *vp; ConNode *cn; ObjDB *database; Real time; { VineSeg *vs, *vprev, *vtemp; LeafNode *lf, *lprev, *ltemp; ObjNode *obj; Ulong ui; MaybeLeafNode *ml, *mtemp; /* grow the leaves first */ lprev = NULL; lf = vp->gleaves; while (lf != NULL) { if (!growLeaf(lf, cn, vp->growthInc, time)) { /* this leaf has stopped growing, so move it to the stopped list */ if (lprev != NULL) lprev->next = lf->next; ltemp = lf; lf = lf->next; if (ltemp == vp->gleaves) vp->gleaves = lf; ltemp->next = vp->sleaves; vp->sleaves = ltemp; /* make ctrans and info NULL which indicates loc2glob doesn't * change and free the base transform (no longer needed) */ obj = ltemp->obj; obj->ctrans = NULL; obj->info = NULL; if (ltemp->tinfo != NULL) { free(ltemp->tinfo); ltemp->tinfo = NULL; } updateCIM(obj->id, vp, database->cim); } else { lprev = lf; lf = lf->next; } } vprev = NULL; vs = vp->gsegs; while (vs != NULL) { if (vs->initial) { /* segment is an initial segment, so grow some real segments */ vtemp = createVineSegs(vs, database, vp->growthInc); /* add to list of growing segments */ if (vtemp != NULL) { if (vprev == NULL) { vp->gsegs = vtemp; } else { vprev->next = vtemp; } if (vtemp->next == NULL) { vtemp->next = vs; vprev = vtemp; } else { vtemp->next->next = vs; vprev = vtemp->next; } } /* remove this initial vine segment from the list of growing * segments and discard it */ if (vprev != NULL) vprev->next = vs->next; vtemp = vs; vs = vs->next; if (vtemp == vp->gsegs) vp->gsegs = vs; free (vtemp); } else if (!growVineSeg(vs, cn, vp->growthInc, time)) { /* segment has stopped growing, so create next segment/s * if it isn't dead, and start growing a leaf. */ if (!(vs->end)) { vtemp = createVineSegs(vs, database, vp->growthInc); /* add to list of growing segments */ if (vtemp != NULL) { if (vprev == NULL) { vp->gsegs = vtemp; } else { vprev->next = vtemp; } if (vtemp->next == NULL) { vtemp->next = vs; vprev = vtemp; } else { vtemp->next->next = vs; vprev = vtemp->next; } } /* add an element to the potential leaf list */ ml = (MaybeLeafNode *)malloc(sizeof(MaybeLeafNode)); if (ml == NULL) { fprintf(stderr, "Error: couldn't malloc MaybeLeafNode\n"); exit(1); } ml->count = 0; ml->vs = vs; ml->next = NULL; if (vp->potHead == NULL) vp->potHead = ml; if (vp->potTail != NULL) { vp->potTail->next = ml; } vp->potTail = ml; } /* remove the stopped vine segment from the list of growing * segments and place it on the list of stopped segments */ if (vprev != NULL) vprev->next = vs->next; vtemp = vs; vs = vs->next; if (vtemp == vp->gsegs) vp->gsegs = vs; vtemp->next = vp->ssegs; vp->ssegs = vtemp; /* make ctrans and info NULL which indicates loc2glob doesn't * change */ obj = vtemp->obj; obj->ctrans = NULL; obj->info = NULL; updateCIM(obj->id, vp, database->cim); } else { /* no problem growing this vine segment, move onto next */ vprev = vs; vs = vs->next; } } /* increment the counts of the elements in the list of potential leaves * and check to see if any are ready to be created/start growing */ for (mtemp=NULL, ml=vp->potHead; ml != NULL; ml=ml->next) { if (mtemp != NULL) { free (mtemp); mtemp = NULL; } if(++(ml->count) > delay) { ltemp = createLeaf(ml->vs, database, vp->growthInc); /* add to list of growing leaves */ if (ltemp != NULL) { ltemp->next = vp->gleaves; vp->gleaves = ltemp; } /* remove the maybeLeafNode from the list. New items are inserted * at the tail, so the items that are ready (if any are) are at * the start of the list. */ mtemp = ml; vp->potHead = ml->next; if (vp->potHead == NULL) vp->potTail = NULL; } } return True; } /* * First checks to see if the vine segment is involved in any contacts as * contained in the contact list. If it is, (or scale factor end is >= 1) * then its growth is over and return 0. scFacStart and scFacEnd define the * amount of growth the segment will go through during the next time step. * scFacStart = old scFacStart + time * inc * scFacEnd = scFacStart + inc * * If the segment has stopped because of a crash, re-adjust the end point * "p2" so that it matches the end of the stopped cylinder (which didn't get * to reach the original "p2" because of the crash). * * Params vs vine segement * cn contact list * inc the amount to increase the scale factor by * time the amount of time that has passed since last frame * * Globals cylHeight height of the canonical vine segment cylinder * cylr radius of the canonical vine segment cylinder * * Returns 1 if still growing, 0 if stopped. Sets up the loc2glob transform, * and ctrans and info, and deletes the glob2loc transform. */ static int growVineSeg(vs, cn, inc, time) VineSeg *vs; ConNode *cn; Real inc, time; { ConNode *cp; ObjNode *obj = vs->obj; Tupple temp; temp.i = cylr; temp.j = cylr; temp.k = cylHeight; if (vs->scFacEnd >= 1.0) { applyTransform(vs->obj->loc2glob, temp, &(vs->p2)); return 0; } for (cp=cn; cp != NULL; cp=cp->next) { if (vs->obj->id == cp->objs[0] || vs->obj->id == cp->objs[1]) { /* this segment was involved in a crash so stop it from growing * and move the end point p2 to the point of stoppage */ applyTransform(vs->obj->loc2glob, temp, &(vs->p2)); /* since p2 is no longer the original point, the vs->norm is * not really valid, strictly speaking in terms of the ESA * algorthim, but it doesn't matter that much */ return 0; } } /* increase the scale factor */ vs->scFacStart = vs->scFacStart + inc * time; vs->scFacEnd = vs->scFacStart + inc; /* set up the info to allow changing of loc2glob as t varies */ obj->ctrans = updateVineSegTrans; obj->info = (void *) vs; /* set up the loc2glob transform for the end of the next step (t = 1 ) */ updateVineSegTrans(1.0, (void *) vs); if (obj->glob2loc != NULL) { free (obj->glob2loc); obj->glob2loc = NULL; } calcBoundingBox(obj); return 1; } /* * First checks to see if the leaf is involved in any contacts as * contained in the contact list. If it is, (or scale factor end is >= 1) * then its growth is over and return 0. scFacStart and scFacEnd define the * amount of growth the segment will go through during the next time step. * scFacStart = old scFacStart + time * inc * scFacEnd = scFacStart + inc * * Params lf leaf * cn contact list * inc the amount to increase the scale factor by * time the amount of time that has passed since last frame * * Globals none * * Returns 1 if still growing, 0 if stopped. Sets up the loc2glob transform, * and ctrans and info, and deletes the glob2loc transform. */ static int growLeaf(lf, cn, inc, time) LeafNode *lf; ConNode *cn; Real inc, time; { ConNode *cp; ObjNode *obj = lf->obj; if (lf->scFacEnd >= 1.0) { return 0; } for (cp=cn; cp != NULL; cp=cp->next) { if (obj->id == cp->objs[0] || obj->id == cp->objs[1]) { /* this leaf was involved in a crash so stop it from growing */ return 0; } } /* increase the scale factor */ lf->scFacStart = lf->scFacStart + inc * time; lf->scFacEnd = lf->scFacStart + inc; /* set up the info to allow changing of loc2glob as t varies */ obj->ctrans = updateLeafTrans; obj->info = (void *) lf; /* set up the loc2glob transform for the end of the next step (t = 1 ) */ updateLeafTrans(1.0, (void *) lf); if (obj->glob2loc != NULL) { free (obj->glob2loc); obj->glob2loc = NULL; } calcBoundingBox(obj); return 1; } /* * This algorithm is based upon that described in the paper "Modeling Plants * with Environment-Sensitive Automata" by James Arvo and David Kirk. * * Given the point to grow from, the normal of the polygon this point lies * closest to, and the list of objects to grow around, creates the next vine * segment/s. * * Have added biasing to get plant to grow in certain directions * - won't create segments that have angles > MAXANGLE degrees to previous * - growth biased towards light source, and against direction of gravity * * Params parent parent vine segment * objs objs to grow around * inc growth increment for a segment * * Globals c1 distance of new points from surface * c2 length of q vector * c3 number of times to look for a nearby surface * c4 max distance from q to surface * c5 probability of quiting * c6 probability of branching * c7 weighting of distance in calculating score * c8 weighting of lightSource in contribution to score * c9 weighting of gravity contribution to score * lightSource location of lightSource * gravity vector defining direction of gravity * quiet quiet mode? * verbose verbose mode? * cylHeight height of canonical cylinder * canSeg canonical vine segment * * Returns new VineSeg/s ready to be grown */ static VineSeg *createVineSegs(parent, database, inc) VineSeg *parent; ObjDB *database; Real inc; { Tupple prevDir, t, q, pkTemp, nDir, tempDir, n1, pk, nk1, pk1; Real d, d1, score, bestScore, lightAngle, gravAngle; int i, j, limit, count1, count2, count3; ObjNode *objs = database->objects; VineSeg *new, *vt; Boolean inter; Ray r; new = NULL; prevDir.i = parent->p2.i - parent->p1.i; prevDir.j = parent->p2.j - parent->p1.j; prevDir.k = parent->p2.k - parent->p1.k; normalize(&prevDir); pk = parent->p2; tempDir.i = lightSource.i - pk.i; tempDir.j = lightSource.j - pk.j; tempDir.k = lightSource.k - pk.k; if(!normalize(&tempDir)) return NULL; /* if branching, create two new segments, else create only one */ if (parent->branch) limit = 2; else limit = 1; for (j=0; j < limit; j++) { count1 = 0; label2: if (++count1 == LOOPMAX) { if (verbose) fprintf(stderr, "hit max loop 1 limit\n"); return new; } count2 = 0; do { /* prevent ESA from getting stuck by limiting attempts at * finding suitable vector t */ if (++count2 == LOOPMAX) { if (verbose) fprintf(stderr, "hit max loop 2 limit\n"); return new; } /* create random vector t orthogonal to n */ if (!orthVec(parent->n, &t)) { fprintf(stderr, "Error couldn't create orthogonal vector\n"); exit(2); } q.i = pk.i + t.i * c2; q.j = pk.j + t.j * c2; q.k = pk.k + t.k * c2; /* check for interference with environment */ inter = interfere(pk, q, objs); } while (inter); count3 = 0; r.origin = q; label3: bestScore = HUGE_VAL; d = HUGE_VAL; /* prevent ESA from getting stuck */ if (++count3 == LOOPMAX) { if (verbose) fprintf(stderr, "hit max loop 3 limit\n"); return new; } for (i=0; i <= c3; i++) { /* create random ray at q */ r.dir.i = RANDIR; r.dir.j = RANDIR; r.dir.k = RANDIR; if (!normalize(&r.dir)) continue; /* r.dir = 0,0,0 */ d1 = intersect(r, objs, &n1); /* at this point, could test the type of material the ray has * intersected with, and whether it is an appropriate type to * grow on (but currently we don't) */ if (d1 == HUGE_VAL) continue; /* calculate potential pk1 */ pkTemp.i = q.i + d1 * r.dir.i + c1 * n1.i; pkTemp.j = q.j + d1 * r.dir.j + c1 * n1.j; pkTemp.k = q.k + d1 * r.dir.k + c1 * n1.k; /* check to see pkTemp isn't p2 which we are growing from */ if (comparePoints(pk, pkTemp)) { if (verbose) fprintf(stderr, "pkTemp == pk\n"); continue; } /* calculate direction vector for p2->pkTemp */ nDir.i = pkTemp.i - pk.i; nDir.j = pkTemp.j - pk.j; nDir.k = pkTemp.k - pk.k; normalize(&nDir); /* prevDir . nDir must be >= ANGLEBOUND which insures the angle * between the two vectors is <= MAXANGLE * (this test isn't used when the parent is an initial segment). */ if (!(parent->initial)) { if ((prevDir.i * nDir.i + prevDir.j * nDir.j + prevDir.k * nDir.k) < ANGLEBOUND) continue; } /* calculate score */ /* angle b/w pk->lightSource and pk->pkTemp */ lightAngle = acos (tempDir.i * nDir.i + tempDir.j * nDir.j + tempDir.k * nDir.k); /* angle between pk->pkTemp and antigrav vector = -gravity */ gravAngle = acos (- gravity.i * nDir.i - gravity.j * nDir.j - gravity.k * nDir.k); score = c7 * d1 + c8 * lightAngle + c9 * gravAngle; if (score < bestScore) { bestScore = score; d = d1; nk1 = n1; pk1 = pkTemp; } } if (d > c4) goto label2; if (interfere(pk, pk1, objs)) goto label3; vt = testVineSeg(pk, pk1, nk1, database, inc, parent, new) ; if (vt == NULL) goto label3; if (!quiet) fprintf(stderr, "pk: %f %f %f pk1: %f %f %f\n", pk.i, pk.j, pk.k, pk1.i, pk1.j, pk1.k); /* quit with probability c5 */ if (((Real)rand() / (Real)RAND_MAX) <= c5) vt->end = True; else vt->end = False; /* branch with probability c6 */ if (((Real)rand() / (Real)RAND_MAX) <= c6) vt->branch = True; else vt->branch = False; vt->next = NULL; if (new == NULL) new = vt; else { /* already have a segment, so add this one */ new->next = vt; } } return new; } /* * Given a vine segment, sets up the basic transformation matrix from which * the loc2glob matrix is calculated for each time step. * * Params vs pointer to the vine segment in question * * Globals translate how to move the canonical cylinder so it is centred * at the origin. * * Returns the basic transformation info in vs->t */ static void calcVineTrans(vs) VineSeg *vs; { Tupple u, v; Real angle, s, c, t, tx; u.i = vs->p2.i - vs->p1.i; u.j = vs->p2.j - vs->p1.j; u.k = vs->p2.k - vs->p1.k; normalize(&u); /* create vector v to rotate about. v = k X u */ v.i = u.j; v.j = - u.i; v.k = 0.0; /* find angle between u and k */ angle = acos(u.k); c = u.k; s = sin(angle); t = 1.0 - c; tx = t * v.i; vs->t[0][0] = tx * v.i + c; vs->t[0][1] = tx * v.j + s * v.k; vs->t[0][2] = tx * v.k - s * v.j; vs->t[0][3] = vs->t[0][0] * translate.i; vs->t[0][4] = vs->t[0][1] * translate.j; vs->t[0][5] = vs->t[0][2] * translate.k; vs->t[1][0] = tx * v.j - s * v.k; vs->t[1][1] = t * SQR(v.j) + c; vs->t[1][2] = t * v.j * v.k + s * v.i; vs->t[1][3] = vs->t[1][0] * translate.i; vs->t[1][4] = vs->t[1][1] * translate.j; vs->t[1][5] = vs->t[1][2] * translate.k; vs->t[2][0] = tx * v.k + s * v.j; vs->t[2][1] = t * v.j * v.k - s * v.i; vs->t[2][2] = t * SQR(v.k) + c; vs->t[2][3] = vs->t[2][0] * translate.i; vs->t[2][4] = vs->t[2][1] * translate.j; vs->t[2][5] = vs->t[2][2] * translate.k; } /* * Given a vector v, creates a random vector o that is orthogonal to v. * * Params v vector * o return orthogonal vector in this * * Globals none * * Returns True upon successful completion, False otherwise */ static Boolean orthVec(v, o) Tupple v, *o; { Real a, b; a = RANDIR; b = RANDIR; if (v.i != 0.0) { o->j = a; o->k = b; o->i = -(a * v.j + b * v.k) / v.i; } else if (v.j != 0.0) { o->i = a; o->k = b; o->j = -(a * v.i + b * v.k) / v.j; } else { o->i = a; o->j = b; o->k = -(a * v.i + b * v.j) / v.k; } if (!normalize(o)) return False; else return True; } /* * given the t value (0 <= t <= 1), sets up the loc2glob transform matrix * for this value of t. The final length of the vine segment is vs->scale. * The current size is vs->scale * scale Factor, where the current scale * Factor = vs->scFacStart + t * (vs->scFacEnd - vs->scFacStart) * The radius is simply the radius as specified by the global variable cylr * multiplied by the scale Factor. * * Params t time * vs vine segment * * Globals none * * Returns True upon successful completion, and also loc2glob contains the * correct transformation matrix for t */ static Boolean updateVineSegTrans(t, vs) Real t; void *vs; { VineSeg *vsp = (VineSeg *) vs; ObjNode *obj = vsp->obj; Real sx, sy, sz, dif; int i; dif = vsp->scFacEnd - vsp->scFacStart; sz = (vsp->scFacStart + dif * t) * vsp->scale; sx = vsp->scFacStart + dif * t; sy = vsp->scFacStart + dif * t; for (i=0; i < 3; i++) { obj->loc2glob->m[i][0] = sx * vsp->t[i][0]; obj->loc2glob->m[i][1] = sy * vsp->t[i][1]; obj->loc2glob->m[i][2] = sz * vsp->t[i][2]; obj->loc2glob->m[i][3] = sx * vsp->t[i][3] + sy * vsp->t[i][4] + sz * vsp->t[i][5]; } obj->loc2glob->m[0][3] += vsp->p1.i; obj->loc2glob->m[1][3] += vsp->p1.j; obj->loc2glob->m[2][3] += vsp->p1.k; return True; } /* * given the t value (0 <= t <= 1), sets up the loc2glob transform matrix * for this value of t. The final length of the leaf is the global leafLength. * The current size is scale Factor, where the current scale * Factor = lf->scFacStart + t * (lf->scFacEnd - lf->scFacStart) * * Params t time * lf leaf * * Globals none * * Returns True upon successful completion, and also loc2glob contains the * correct transformation matrix for t */ static Boolean updateLeafTrans(t, lf) Real t; void *lf; { LeafNode *lfp = (LeafNode *) lf; ObjNode *obj = lfp->obj; Real s, dif; int i; dif = lfp->scFacEnd - lfp->scFacStart; s = lfp->scFacStart + dif * t; for (i=0; i < 3; i++) { obj->loc2glob->m[i][0] = s * lfp->tinfo->m[i][0]; obj->loc2glob->m[i][1] = s * lfp->tinfo->m[i][1]; obj->loc2glob->m[i][2] = s * lfp->tinfo->m[i][2]; obj->loc2glob->m[i][3] = s * lfp->tinfo->m[i][3]; } obj->loc2glob->m[0][3] += lfp->orig.i; obj->loc2glob->m[1][3] += lfp->orig.j; obj->loc2glob->m[2][3] += lfp->orig.k; return True; } /* * Given the two points the segment will grow between, initialize the segment * and test to see if it's addition will cause any intersections with any * of the other objects in the database. If not, set it up for the next * round of collision detection, and set up the CIM so that it isn't tested * against its parent or its sibling (if any). * * Params p1 starting point * p2 finishing point * n normal of surface p2 was selected from * database objects to test for collision with * inc growth increment for the segment * parent parent segment * sib sibling segment (NULL if none) * * Globals cylHeight height of the canonical vine segment cylinder * * Returns the new segment with the loc2glob transform setup (obj->ctrans = * NULL) ready for collision detection stage. database->cim updated. * Else returns NULL if the segment would have caused an intersection */ static VineSeg *testVineSeg(p1, p2, n, database, inc, parent, sib) Tupple p1, p2, n; ObjDB *database; Real inc; VineSeg *parent, *sib; { VineSeg *vt; ObjNode *objs; Tupple t; ConNode *cp = NULL; ConNode *ctemp; Status s = Nothing; Ulong a, numObjs; if ((vt=(VineSeg *)malloc(sizeof(VineSeg))) == NULL) { fprintf(stderr, "Error: couldn't malloc VineSeg\n"); exit(1); } /* add new object */ database->objects = addEmptyObjNode(database->objects); objs = database->objects; vt->obj = objs; /* set up the new object */ vt->obj->type = Polygonal; vt->obj->va.total = canSeg.va.total; vt->obj->va.v = canSeg.va.v; vt->obj->vn.total = canSeg.vn.total; vt->obj->vn.v = canSeg.vn.v; vt->obj->polys.total = canSeg.polys.total; vt->obj->polys.p = canSeg.polys.p; vt->obj->min = canSeg.min; vt->obj->max = canSeg.max; vt->obj->seads = canSeg.seads; vt->p1 = p1; vt->p2 = p2; vt->n = n; t.i = p2.i - p1.i; t.j = p2.j - p1.j; t.k = p2.k - p1.k; vt->scale = (SQR(t.i) + SQR(t.j) + SQR(t.k)) / SQR(cylHeight); vt->scFacStart = 0.0; vt->scFacEnd = 1.0; vt->initial = False; vt->next = NULL; /* setup the transform array */ calcVineTrans(vt); /* set up the segment at full growth */ if ((vt->obj->loc2glob = (Transform *)malloc(sizeof(Transform))) == NULL) { fprintf(stderr, "Error: couldn't malloc Transform\n"); exit(1); } updateVineSegTrans(1.0, vt); calcBoundingBox(vt->obj); /* setup CIM so this branch and parent aren't tested against each * other, but it is tested against everything else. */ for (a=0,numObjs=vt->obj->id; a < numObjs; a++) { if (!setCollIntMatrix(a, numObjs, database->cim, 1)) { fprintf(stderr, "Error: couldn't set coll. interest matrix\n"); exit(2); } } if (!(parent->initial)) { if (!setCollIntMatrix(numObjs, parent->obj->id, database->cim, 0)) { fprintf(stderr, "Error: couldn't set coll. interest matrix\n"); exit(2); } } /* if there exists a sibling, don't test against it either */ if (sib != NULL) { if (!setCollIntMatrix(numObjs, sib->obj->id, database->cim, 0)) { fprintf(stderr, "Error: couldn't set coll. interest matrix\n"); exit(1); } } if (verbose) { fprintf(stderr, "Testing new seg %u", vt->obj->id); if (!(parent->initial)) fprintf(stderr, " parent %u", parent->obj->id); fprintf(stderr, "\n"); } /* test to see if the addition of this segmemt will cause any intersections */ cp = test4Intersection(*database, &s); if (s == Intersection) { /* check to see if this is one of the objects intersecting */ for (ctemp=cp; ctemp != NULL; ctemp=ctemp->next) { if (ctemp->statusType != Intersection) continue; if (ctemp->objs[0] == vt->obj->id || ctemp->objs[1] == vt->obj->id) { if (database->objects->loc2glob != NULL) free(database->objects->loc2glob); if (database->objects->glob2loc != NULL) free(database->objects->glob2loc); database->objects = removeFirstObjNode(database->objects); free (vt); destroyConNodeList(cp); /* free (testDB.cim->matrix); free (testDB.cim); */ if (verbose) fprintf(stderr, "New seg bad\n"); return NULL; } } } if (cp != NULL) destroyConNodeList(cp); if (verbose) fprintf(stderr, "New seg ok\n"); #ifdef STATS vineSegsGenerated++; #endif /* adding this segment won't cause any problems, so set it up as a * constant object for its initial growth period */ vt->scFacEnd = inc; updateVineSegTrans(1.0, vt); vt->obj->ctrans = NULL; vt->obj->info = NULL; calcBoundingBox(vt->obj); return vt; } /* * Given a vine segment to grow a leaf from, will attempt to find a suitable * position for the leaf. The leaf will be orientated so that it is first * perpendicular to a randomly chosen face of the vine segment, then rotated * so that its normal points towards the global light source. If this results * in intereference, try rotating it more or less to see if this produces * acceptable results. If not, then go back and try a different face, then * go through the process again. * * Params vs vine segment this leaf will grow from * database the environment to test against * inc the growth increment of a leaf per step * * Globals lightSource location of the light source * c10 number of times to attempt to place a leaf on this seg * canLeaf canonical leaf * leafWidth width of the leaf * * Returns leaf ready for a constant first step (ready for collision * detection), and then able to be grown. Otherwise returns NULL. */ static LeafNode *createLeaf(vs, database, inc) VineSeg *vs; ObjDB *database; Real inc; { Tupple ax1, ax2, norm, move, lightDir, leafNorm, ttemp; ObjNode *obj = vs->obj; ObjNode *lfobj; LeafNode *lf; PolyNode *pp; int i, index, count; Real angle1, angle2, angle3, yshift; Transform trans1, trans2, trans3; ConNode *cp, *ct; Boolean ok = True; Ulong ui, uj, ulimit; Status s = Nothing; if ((lf = (LeafNode *)malloc(sizeof(LeafNode))) == NULL) { fprintf(stderr, "Error: couldn't malloc LeafNode\n"); exit(1); } if ((lf->tinfo = (Transform *)malloc(sizeof(Transform))) == NULL) { fprintf(stderr, "Error: couldn't malloc Transform\n"); exit(1); } yshift = -leafWidth / 2.0; ttemp.i = 0.0; ttemp.j = 0.0; ttemp.k = 1.0; /* add an ObjNode */ database->objects = addEmptyObjNode(database->objects); lfobj = database->objects; lf->obj = lfobj; /* set it up */ lfobj->type = Polygonal; lfobj->va.total = canLeaf.va.total; lfobj->va.v = canLeaf.va.v; lfobj->vn.total = canLeaf.vn.total; lfobj->vn.v = canLeaf.vn.v; lfobj->polys.total = canLeaf.polys.total; lfobj->polys.p = canLeaf.polys.p; lfobj->min = canLeaf.min; lfobj->max = canLeaf.max; lfobj->seads = canLeaf.seads; lfobj->loc2glob = NULL; lfobj->glob2loc = NULL; lfobj->ctrans = NULL; lfobj->info = NULL; lfobj->loc2glob = (Transform *)malloc(sizeof(Transform)); if (lfobj->loc2glob == NULL) { fprintf(stderr, "Error: couldn't malloc Transform\n"); exit(1); } /* check to see if the segment has the glob2loc Transform, and create it * if not */ if (obj->glob2loc == NULL) { obj->glob2loc = (Transform *)malloc(sizeof(Transform)); if (obj->glob2loc == NULL) { fprintf(stderr, "Error: couldn't malloc Transform\n"); exit(1); } if (!matrixInvert(obj->loc2glob, obj->glob2loc)) { fprintf(stderr, "Error: couldn't invert transform matrix\n"); exit(2); } } /* set up the CIM */ for (ui=0, ulimit=lfobj->id; ui < ulimit; ui++) if (!setCollIntMatrix(ulimit, ui, database->cim, 1)) { fprintf(stderr, "Error: couldn't set CIM\n"); exit(2); } cp = NULL; for (i=0; i < c10; i++) { /* first randomly choose a face of the cylinder */ index = (int) ((Real)((obj->polys.total-2) * rand()) / (Real) RAND_MAX) + 2; if (index >= obj->polys.total) index = obj->polys.total - 1; pp = &(obj->polys.p[index]); /* find the global normal of this face */ transformNormal(obj->glob2loc, pp->normal, &norm); /* find the axis to rotate the leaf vector around so that it is * parallel to the normal of this face * i X norm */ ax1.i = 0.0; ax1.j = -norm.k; ax1.k = norm.j; normalize (&ax1); /* find the required angle and then set up the rotation matrix */ angle1 = acos(norm.i); calcRotate(ax1, angle1, &trans1); /* leaf has to be shifted down -leafWidth/2 y units (before rotation) */ trans1.m[0][3] = trans1.m[0][1] * yshift; trans1.m[1][3] = trans1.m[1][1] * yshift; trans1.m[2][3] = trans1.m[2][1] * yshift; /* calculate new direction of the leaf normal */ applyTransform(&trans1, ttemp, &leafNorm); normalize (&leafNorm); /* find the point where this leaf will be attached in the global ref * frame */ applyTransform(obj->loc2glob, obj->va.v[pp->vertices[3]], &move); /* light direction vector measured from the point of attachment of the * leaf to the vine seg, and the global lightSource */ lightDir.i = lightSource.i - move.i; lightDir.j = lightSource.j - move.j; lightDir.k = lightSource.k - move.k; normalize (&lightDir); /* create the axis around which to rotate the normal of the leaf so * that it is best orientated toward the light source: * leafNorm X lightSource */ ax2.i = leafNorm.j * lightDir.k - lightDir.j * leafNorm.k; ax2.j = lightDir.i * leafNorm.k - leafNorm.i * lightDir.k; ax2.k = leafNorm.i * lightDir.j - lightDir.i * leafNorm.j; normalize (&ax2); /* find the angle of rotation needed */ angle2 = acos (leafNorm.i * lightDir.i + leafNorm.j * lightDir.j + leafNorm.k * lightDir.k); /* final transform matrix: * shift leaf * rotate till parallel to normal of vine seg face * rotate until aimed toward light * shift to final location in global space * * Because this position may result in interference with the other * objects in the environment, try a couple of times with different * angles. */ for (angle3=angle2,count=0; count < c10; count++) { calcRotate(ax2, angle3, &trans2); /* set up the loc2glob transform for this position */ matrixMult(&trans2, &trans1, lfobj->loc2glob); /* add the final translation to the matrix */ lfobj->loc2glob->m[0][3] += move.i; lfobj->loc2glob->m[1][3] += move.j; lfobj->loc2glob->m[2][3] += move.k; /* now have the transformation matrix for this object, see if its * addition causes problems or not */ calcBoundingBox(lfobj); /* cp = test4Intersection(testDB, &s); */ cp = test4Intersection(*database, &s); if (s == Intersection) { /* check to see if this is one of the objects intersecting */ for (ct=cp; ct != NULL; ct=ct->next) { if (ct->statusType != Intersection) continue; if (ct->objs[0] == lfobj->id || ct->objs[1] == lfobj->id) { /* this object caused intersection, so it is no good in * this position */ ok = False; /* change the angle to try */ angle3 = angle2 * (1.5 - rand() / RAND_MAX); } } } else { ok = True; } if (cp != NULL) { destroyConNodeList(cp); cp = NULL; } if (ok) break; } if (ok) break; } if (!ok) { if (verbose) fprintf(stderr, "Couldn't find a place for a leaf\n"); /* couldn't find a good place for this leaf */ if (lfobj->loc2glob != NULL) free (lfobj->loc2glob); if (lfobj->glob2loc != NULL) free (lfobj->loc2glob); database->objects = removeFirstObjNode(database->objects); free (lf->tinfo); free (lf); return NULL; } /* have found a good position for this leaf, so set up the basic transform * info, and the first step of growth. */ matrixMult(&trans2, &trans1, lf->tinfo); lf->scFacStart = 0.0; lf->scFacEnd = inc; lf->orig.i = move.i; lf->orig.j = move.j; lf->orig.k = move.k; lf->vs = vs; lf->next = NULL; updateLeafTrans(1.0, (void *) lf); if (!setCollIntMatrix(lfobj->id, vs->obj->id, database->cim, 0)) { /* don't check against the vine segment it is growing from -- will always * be in contact. */ fprintf(stderr, "Error: couldn't set CIM\n"); exit(2); } /* mark the next time step as this leafs first one -- it stays constant */ lf->obj->ctrans = NULL; lf->obj->info = NULL; #ifdef STATS leavesGenerated++; #endif if (verbose) fprintf(stderr, "Set up a new leaf\n"); return lf; } /* * given a unit vector to rotate around, and the angle rotated, will set up * a transform to carry this rotation out. * * Params axis unit vector about which rotation takes place * a angle to rotate * t where to store the result * * Globals none * * Returns the required transform set up in t */ static void calcRotate(axis, a, t) Tupple axis; Real a; Transform *t; { Real s, c, tt, tx; s = sin(a); c = cos(a); tt = 1.0 - c; tx = tt * axis.i; t->m[0][0] = tx * axis.i + c; t->m[0][1] = tx * axis.j + s * axis.k; t->m[0][2] = tx * axis.k - s * axis.j; t->m[0][3] = 0.0; t->m[1][0] = tx * axis.j - s * axis.k; t->m[1][1] = tt * SQR(axis.j) + c; t->m[1][2] = tt * axis.j * axis.k + s * axis.i; t->m[1][3] = 0.0; t->m[2][0] = tx * axis.k + s * axis.j; t->m[2][1] = tt * axis.j * axis.k - s * axis.i; t->m[2][2] = tt * SQR(axis.k) + c; t->m[2][3] = 0.0; } /* * once a leaf or vine segment has stopped growing, no longer need to test * for collisions between it and environment objects, and other parts of the * plant that aren't growing. Probably should have some global list of * stopped objects (so we can update the CIM with all stopped objects, not just * this plant) but ... * * Params id id of the object in question * plant the vine plant it belongs to * cim collision interest matrix * * Globals lastEnvObj all environment objects have ids 0 <= id < lastEnvObj * * Returns nothing. CIM is updated. */ static void updateCIM(id, plant, cim) Ulong id; VinePlant *plant; CollIntMat *cim; { VineSeg *vs; LeafNode *lf; Ulong ui, uj; /* disable checking against environment objects */ for (ui=0; ui <= lastEnvObj; ui++) if (!setCollIntMatrix(id, ui, cim, 0)) { fprintf(stderr, "Error: couldn't set the CIM\n"); exit(2); } /* diable checking against stopped vine segments */ for (vs=plant->ssegs; vs != NULL; vs=vs->next) { if (vs->initial) continue; if (!setCollIntMatrix(id, vs->obj->id, cim, 0)) { fprintf(stderr, "Error: couldn't set the CIM\n"); exit(2); } } /* disable checking against stopped leaves */ for (lf=plant->sleaves; lf != NULL; lf=lf->next) if (!setCollIntMatrix(id, lf->obj->id, cim, 0)) { fprintf(stderr, "Error: couldn't set the CIM\n"); exit(2); } } /* * Outputs the plant objects in the list of objects pointed to by obj, * in Showpoly format. Won't output the environmental objects. * * Params obj pointer to list of objects * plants list of plants * * Globals lastEnvObj id of last environmental object. * outFile output file name * * Returns nothing */ void showPolyOutputPlants(obj, plants) ObjNode *obj; PlantNode *plants; { fprintf(outFile, "Frame\n"); for (;obj != NULL; obj=obj->next) { if (!(obj->valid)) continue; if (obj->id == lastEnvObj) return; showPolyOutputObj(obj); } } /* * Outputs the objects that make up the plants in wavefront format. * Calls the script in scriptName * * Params obj pointer to list of objects * plants list of plants * * Globals outFile output file * outName output name * scriptName the script to be used * frameNum which frame we are outputting * mtlLibName material library name * * Returns nothing */ void waveFrontOutputPlants(obj, plants) ObjNode *obj; PlantNode *plants; { char buf[MAXSTRING]; int i, j, vaOffset, vnOffset, limit; Tupple *va, *vn; VinePlant *vp; VineSeg *vs; LeafNode *lf; vaOffset = 0; vnOffset = 0; if (outFile == NULL) { if ((outFile = fopen(outName, "w")) == NULL) { fprintf(stderr, "Error: couldn't create output file %s\n", outName); exit(EX_CANTCREAT); } } outputInfo(outFile); fprintf(outFile, "mtllib %s\n", mtlLibName); for (; plants != NULL; plants=plants->next) { switch (plants->type) { case Vine: vp = (VinePlant *)plants->pl; /* first output the vine segments */ fprintf(outFile, "usemtl vine\n"); for (vs=vp->ssegs; vs != NULL; vs=vs->next) { if (vs->initial) continue; waveFrontOutputObj(vs->obj, &vaOffset, &vnOffset); } for (vs=vp->gsegs; vs != NULL; vs=vs->next) { if (vs->initial) continue; waveFrontOutputObj(vs->obj, &vaOffset, &vnOffset); } /* next output the leaves */ fprintf(outFile, "usemtl leaf\n"); for (lf=vp->sleaves; lf != NULL; lf=lf->next) waveFrontOutputObj(lf->obj, &vaOffset, &vnOffset); for (lf=vp->gleaves; lf != NULL; lf=lf->next) waveFrontOutputObj(lf->obj, &vaOffset, &vnOffset); break; default: break; } } fflush(outFile); sprintf(buf, "%s %d", scriptName, frameNum); system(buf); fclose (outFile); outFile = NULL; }