#include "TxsFace.h" using namespace TXS; void pixelLocn::setRGB(const NormRGB &rgb) { TiffImage *img = this->face->getPyramid()->getLevel(this->level); img->setRGB(this->x, this->y, rgb); } Plane TxsFace::calcEdgePlane(int iv) { __LOG__("CALCing edge plane " << iv << " for face " << this); int ia = iv; int ib = (iv + 1) % getNumVerts(); Point va = this->verts[ia]->getPoint(); Point vb = this->verts[ib]->getPoint(); Vector ccw = vb - va; Vector inward = this->normal * ccw; Plane p; p.Set(inward, va); __LOG__(p); return p; } void TxsFace::calcEverything(TXS_REAL unitsPerPixel) { // I decided to put all the code in this one big function, cuz // order is very important. // if(this->verts.size() != 3) { __ERROR__("TxsFace used for non-triangle! can't do that"); exit(1); } // Calculate plane vectors { // Calculate the normal const Point v0 = verts[0]->getPoint(); const Point v1 = verts[1]->getPoint(); const Point v2 = verts[2]->getPoint(); const Vector e1 = v1 - v0; const Vector e2 = v2 - v1; this->normal = e1 * e2; // crossp BOOL normSuccess = this->normal.Normalize(); assert(normSuccess == TRUE); // TODO - code assumes triangle // choose a point as UV origin // need to choose a UV origin such that // the u,v of the verts are minimized // use the following heuristic: choose a vert vo // such that dist(vo, va) + dist(vo, vb) is minimized double minDist = 0.0; int iva = -1; bool firstIteration = true; for(int ivo = 0; ivo < 3; ++ivo) { TxsVertex* vo = this->verts[ivo]; TxsVertex* va = this->verts[(ivo + 1) % 3]; TxsVertex* vb = this->verts[(ivo + 2) % 3]; double curDist = Distance(vo->getPoint(), va->getPoint()) + Distance(vo->getPoint(), vb->getPoint()); if(firstIteration || curDist < minDist) { this->uvOrigin = vo->getPoint(); // note which vertex to use for uvUp // it's very important that we use +2 here, since we're going CCW, // the PREVIOUS vertex (ie. +2) should be up iva = (ivo + 2) % 3; minDist = curDist; firstIteration = false; } } assert(iva != -1); // TEMP maybe this will owrk? // we can't just do this, because we assign UV origin in the above loop, and thus need // to choose the iva assigned in the loop in order to get correct up/right vectors (ie. that don't require wrapping) //iva = 0; // now calc UV vectors // for now, just calc the directions, cuz they should // be aligned with our bounding box corners // thus, normalize them all // calc uvUp with the recorded va index const Point va = this->verts[iva]->getPoint(); this->uvUp = va - this->uvOrigin; this->uvUp.Normalize(); // use crossp to calc uvRight // make sure to use up * norm, not norm * up to get right this->uvRight = this->uvUp * this->normal; this->uvRight.Normalize(); // and distOfOrigin // use uv-origin, cuz it's a point on the plane this->distOfOrigin = this->normal.Dot(this->uvOrigin) * -1.0; } __LOG__("uv origin: " << this->uvOrigin); __LOG__("uv up: " << this->uvUp); __LOG__("uv right: " << this->uvRight); // use normalized uv coordinates to figure out the size // of our bounding square UVs mins, maxs; { vector::iterator vit; bool firstIteration = true; for(vit = this->verts.begin(); vit != this->verts.end(); ++vit) { TxsVertex* vert = *vit; UVs uv = this->calcUV(vert->getPoint()); __LOG__(" uv is " << uv << " for vert " << vert->getPoint()); if(firstIteration) { // just set the mins/maxs to this one // nothing previous to compare to mins = uv; maxs = uv; firstIteration = false; } else { // compare if(uv.u < mins.u) mins.u = uv.u; if(uv.v < mins.v) mins.v = uv.v; if(uv.u > maxs.u) maxs.u = uv.u; if(uv.v > maxs.v) maxs.v = uv.v; } } } // now, reset the UV Origin to the min-uv point // this is to avoid any wrapping whatsoever // of course, this means that uvOrigin is not necessarily in the triangle anymore! // so first calculate the point that mins WOULD be at Point minUvPt = this->getPointForUV(mins); // now, translate our UV axis this->uvOrigin = minUvPt; __LOG__("min uv: " << mins << " max uv: " << maxs); // Now calc pyramid size based on min/max UVs (ie. the sizes of our bounding box) { UVs dims(maxs.u - mins.u, maxs.v - mins.v); // use the longer dimension as texture size TXS_REAL idealSizeUnits = max(dims.u, dims.v); // now convert to pixels // make sure to ceil, not floor or round int sizePixels = (int)ceil(idealSizeUnits / unitsPerPixel); assert(sizePixels > 0); // now find nearest greater power of 2 // HACK HACK we force at least 128 for tetrus //int sizeTex = max(128, nearestGreaterPowerOf2(sizePixels)); // // or, don't force it int sizeTex = nearestGreaterPowerOf2(sizePixels); // now build the pyramid! this->pyramid = new TxsPyramid(sizeTex); __LOG__("calculated texture size: " << sizeTex << " for size units: " << idealSizeUnits << " and sizePixels " << sizePixels); // now, scale our UV unit vectors up by sizeUnits TXS_REAL actualSizeUnits = sizeTex * unitsPerPixel; this->uvUp = this->uvUp * actualSizeUnits; this->uvRight = this->uvRight * actualSizeUnits; //__LOG__("scaled UV up/right vects: " << this->uvUp << " " << this->uvRight); } // lastly, calculate all edge planes { for(int iv = 0; iv < getNumVerts(); ++iv) { this->edgePlanes.push_back( this->calcEdgePlane(iv)); } } // and faceplane this->facePlane.Set(this->normal, this->uvOrigin); if(false) { // TEMP DEBUG TODO // fill all pixels not in the plane as red, and others as green // this is to test isPointInFace TiffImage *img = this->getFinestPyLevel(); int lev = this->pyramid->finestLevel(); int sizeTex = (int)this->pyramid->getSizeOfLevel(lev); Point pixPos; for(int x = 0; x < sizeTex; x++) { for(int y = 0; y < sizeTex; y++) { if(this->getFacePixelPosition(lev, x, y, pixPos)) { // make a radial gradiant double dist = min((pixPos - this->uvOrigin).Magnitude() * 50, (float)1.0); NormRGB grad(0, dist, 0); img->setRGB(x, y, grad); } else { img->setRGB(x, y, NormRGB(1, 0, 0)); } } } } } Vector TxsFace::calcPhongNormalAt(const Point &p) { if(getNumVerts() == 3) return tri_calcPhongNormalAt(p); else if(getNumVerts() == 4) return quad_calcPhongNormalAt(p); else { __ERROR__("Face was not a tri or a quad, so it's not supported. it had " << getNumVerts() << " verts."); exit(1); } } Vector TxsFace::tri_calcPhongNormalAt(const Point &p) { Vector norm(0,0,0); for(int iv = 0; iv < 3; ++iv) { TxsVertex* vert = this->verts[iv]; Point v = vert->getPoint(); // calc distance from v to p Vector v2p = (p - v); double dist1 = v2p.Magnitude(); v2p.Normalize(); // now, calc distance from p to opposite edge intersection // for a triangle, the opposite edge is just the next one Plane edgePlane = this->getEdgePlane((iv+1) % getNumVerts()); FLOAT dist2 = 0.0; Point intxPt; if(Intersect(edgePlane, p, v2p, intxPt, &dist2) == false) { __ERROR__("opposite edge plane did not intersect with v2p ray.."); } // figure out proportional weight double weight = dist2 / (dist1 + dist2); norm += vert->getAvgNormal() * weight; } if(!norm.Normalize()) { __ERROR__("could not normalize phong-interped normal"); } return norm; } // NOTE - this does not properly handle when p is ON a vertex exactly Vector TxsFace::quad_calcPhongNormalAt(const Point &p) { Vector norm(0,0,0); //__LOG__("calcing normal at " << p); // for(int iv = 0; iv < getNumVerts(); ++iv) { TxsVertex* vert = this->verts[iv]; Point v = vert->getPoint(); // calc distance from v to p Vector v2p = (p - v); double dist1 = v2p.Magnitude(); v2p.Normalize(); //__LOG__("for vert at " << v << " dist to p was " << dist1); // now, calc distance from p to opposite edges // NOTE TODO this assumes quadrilaterals Plane edgePlaneA = this->getEdgePlane((iv+1) % getNumVerts()); Plane edgePlaneB = this->getEdgePlane((iv+2) % getNumVerts()); Point intxPt; FLOAT dist2a = 0.0; if(Intersect(edgePlaneA, p, v2p, intxPt, &dist2a) == false) { __ERROR__("opposite edge plane did not intersect with v2p ray.."); exit(1); } FLOAT dist2b = 0.0; if(Intersect(edgePlaneB, p, v2p, intxPt, &dist2b) == false) { __ERROR__("opposite edge plane did not intersect with v2p ray.."); exit(1); } // 2006-04-30 (22-11) - not sure why this works..but it deals with neg dists just fine dist2a = abs(dist2a); dist2b = abs(dist2b); // figure out proportional weight double dist2 = min(dist2a, dist2b); double weight = dist2 / (dist1 + dist2); //__LOG__("weighing at " << weight); norm += vert->getAvgNormal() * weight; } if(!norm.Normalize()) { __ERROR__("could not normalize phong-interped normal"); } return norm; } bool TxsFace::intersectRay(const Point &s, const Vector &d, Point &intxOut, TXS_REAL &distOut) { Point intx; FLOAT distTemp; if(Intersect(facePlane, s, d, intxOut, &distTemp)) { // it hits the plane.. // but does it hit inside the tri face? if(this->isPointInFace(intxOut)) { // we're good! distOut = distTemp; return true; } } return false; } bool TxsFace::getRgbForRay(const Point &s, const Vector &d, int level, NormRGB &rgbOut) { Point intx; if(Intersect(facePlane, s, d, intx)) { if(this->isPointInFace(intx)) { // we're good! UVs uv = this->calcUV(intx); rgbOut = this->getRGB(level, uv); return true; } else { // point wasn't in the triangle return false; } } else { // ray parallel to plane return false; } } void TxsFace::calcImmediateNbors() { this->accumFaceNbors(this->immediateNbors); this->immediateNbors.push_front(this); } TXS_REAL TxsFace::distToFacePlane(const Point &from) { return Distance(this->facePlane, from); } bool TxsFace::isPointInFace(const Point &p) { // go through each edge plane // num verts == num edges for(int ie = 0; ie < getNumVerts(); ie++) { TXS_REAL testDist = Distance(this->getEdgePlane(ie), p); if(testDist < 0) { // it's behind! this pt must be outside the tri return false; } } // it must've passed all three edges return true; } void TxsFace::fillTextureWithPhongShading() { Vector sunDir(-1,-1,-1); sunDir.Normalize(); TiffImage *img = this->getFinestPyLevel(); int lev = this->pyramid->finestLevel(); int sizeTex = (int)this->pyramid->getSizeOfLevel(lev); if(sizeTex != (int)128) { __WARN__("non-128 texture size: " << sizeTex); } Point pixPos; for(int x = 0; x < sizeTex; x++) { for(int y = 0; y < sizeTex; y++) { if(this->getFacePixelPosition(lev, x, y, pixPos)) { // phong shade it Vector pixNorm = calcPhongNormalAt(pixPos); double phongCoef = max((double)0, (double)(-1.0 * pixNorm.Dot(sunDir))); NormRGB grad(phongCoef, phongCoef, phongCoef); img->setRGB(x, y, grad); // TEMP test checks } else { img->setRGB(x, y, NormRGB(1, 1, 0)); } // TEMP test checks } } } /* * The d'tor does not destroy the verts - they are assumed to be shared. */ TxsFace::~TxsFace() { if(this->pyramid != NULL) delete this->pyramid; } bool TxsFace::isUvOnFace(const UVs &uv) { Point p = this->getPointForUV(uv); return this->isPointInFace(p); }