// Copyright (C) 1996 Keith Whitwell. // This file may only be copied under the terms of the GNU Library General // Public License - see the file COPYING in the lib3d distribution. #include #include #include #include #include #include SmoothPipeline::ClipFunc SmoothPipeline::Intersect[] = { intersectZ1, intersectZ2, intersectY1, intersectY2, intersectX1, intersectX2 }; extern float D; struct Smooth_VertexNormalData { float sumDotProd; int intensity; }; struct Smooth_PolygonData { bool backface; }; class Smooth_VertexData : public SmoothPipelineData { public: Vector3 cvv; uint outcodes; }; SmoothPipeline::SmoothPipeline() : sizeNpool(256), npool(new Smooth_VertexNormalData[sizeNpool]), nrVpool(0), sizeVpool(256), vpool(new Smooth_VertexData[sizeVpool]), nrPpool(0), sizePpool(256), ppool(new Smooth_PolygonData[sizePpool]), pv(new (SmoothPipelineData*)[MAX_CLIPPED_VERTICES]) { } SmoothPipeline::~SmoothPipeline() { delete npool; delete vpool; delete ppool; delete pv; } void SmoothPipeline::registerModel( Model& model ) { stitchModel( model ); // Ensure we have room for temporary vertices. uint maxVertices = nrVertices + MAX_CLIPPED_VERTICES; if (sizeVpool < maxVertices) { while ((sizeVpool *= 2) < maxVertices); delete [] vpool; vpool = new Smooth_VertexData[sizeVpool]; } if (sizePpool < nrPolygons) { while ((sizePpool *= 2) < nrPolygons); delete [] ppool; ppool = new Smooth_PolygonData[sizePpool]; } if (sizeNpool < nrVertexNormals) { while ((sizeNpool *= 2) < nrVertexNormals); delete [] npool; npool = new Smooth_VertexNormalData[sizeNpool]; } } void SmoothPipeline::render(Model &model, Viewport &viewport, const Light *lights, uint nrLights, uint clipPlanes, uint flags) { stitchModel(model); // The pipeline proper. // Currently recalculates all lighting information every frame. renderFlags = flags; thisFrame++; have_backface_info = false; clip = false; calculateLightData( viewport, lights, nrLights ); if ( !clipPlanes ) { transform( viewport ); } else if ( !transformForClipping( viewport ) ) { return; } if (!clip) { cullAndRenderPolygons( viewport, nrLights ); } else { cullBackFaces(); clipAndRenderPolygons( viewport, nrLights ); } } void SmoothPipeline::calculateLightData(Viewport &viewport, const Light *light, uint nrLights) { if ((renderFlags & lightColourChange) || materials->ramp == 0) { if_debug { debug() << "Recalulating colour ramps" << endlog; } Material *m = materials; for (uint i = 0 ; i < nrMaterials ; i++ ) { if (m->ramp == 0) m->ramp = new ColourRamp; if (nrLights) { Vector3 amb; Vector3 dif; amb.assign(0,0,0); const Light *l = light; do { Vector3 tmp(l->getAmbient()); tmp.scale(m->Ka); amb.add( tmp ); l = l->getNextLight(); } while(l); amb.scale(255.0); amb.clamp(255.0); if (renderFlags & uniformDiffuse) { dif.assign( light->getDiffuse() ); dif.scale(255.0); } else { dif.assign( 255.0, 255.0, 255.0 ); // fallback. } dif.scale( m->diffuse ); m->ramp->build(viewport, amb, dif); } else { Vector3 tmp; tmp.scale(m->ambient, 255); m->ramp->fallback(viewport, tmp); } m++; } } uint i = nrVertexNormals; Smooth_VertexNormalData *n = npool; do { n->sumDotProd = 0.0; n++; } while (--i); if (nrLights) { const Light *l = light; Vector3 pov; while (l) { pov.mul_T( *objectToCvv_T, l->getCvvPov() ); pov.normalize(); Smooth_VertexNormalData *n = npool; const Normal *nn = vertexNormals; uint i = nrVertexNormals; do { float dp = dot(nn->model,pov); if (dp > 0) { n->sumDotProd += dp; } n++; nn++; } while (--i); l = l->getNextLight(); } Smooth_VertexNormalData *n = npool; uint i = nrVertexNormals; do { if (n->sumDotProd < .99999) { n->intensity = int(n->sumDotProd * float(1<<16)); } else { n->intensity = (1<<16)-1; } n++; } while (--i); } } void SmoothPipeline::cullBackFaces() { Vector3 tmp; have_backface_info = true; const Polygon *poly = polygons; Smooth_PolygonData *pd = ppool; int i = nrPolygons; do { tmp.sub(vertices[poly->vertex0].model, *objectViewPos); pd->backface = (dot(tmp, polygonNormals[poly->normal].model) < 0); pd++; poly++; } while (--i); } // Transform for rendering without clipping void SmoothPipeline::transform( Viewport &viewport ) { Smooth_VertexData *vp = vpool; const Vertex *v = vertices; int j = nrVertices; nrVpool = j; // Transform the first vertex here. vp->device.project( *objectToDevice, v->model ); xmax = vp->device.v[X]; xmin = vp->device.v[X]; ymax = vp->device.v[Y]; ymin = vp->device.v[Y]; vp++; v++; j--; // Transform the remaining vertices. do { vp->device.project( *objectToDevice, v->model ); vp->intensity = npool[v->normal].intensity; if (xmax < vp->device.v[X]) xmax = vp->device.v[X]; if (xmin > vp->device.v[X]) xmin = vp->device.v[X]; if (ymax < vp->device.v[Y]) ymax = vp->device.v[Y]; if (ymin > vp->device.v[Y]) ymin = vp->device.v[Y]; vp++; v++; } while (--j); viewport.setDirty( xmin, ymin, xmax, ymax ); } // Render void SmoothPipeline::renderPolygons( Viewport &viewport, uint ) { for (int j = nrPolygons ; j-- ; ) { const Polygon &poly = polygons[j]; if (!ppool[j].backface) { pv[0] = &vpool[poly.vertex0]; pv[1] = &vpool[poly.vertex1]; pv[2] = &vpool[poly.vertex2]; viewport.smoothTriangleZb(pv, *materials[poly.material].ramp); } } } // Backface cull and render void SmoothPipeline::cullAndRenderPolygons( Viewport &viewport, uint ) { for (int j = nrPolygons ; j-- ; ) { const Polygon &poly = polygons[j]; pv[0] = &vpool[poly.vertex0]; pv[1] = &vpool[poly.vertex1]; pv[2] = &vpool[poly.vertex2]; bool cw =( ((pv[1]->device.v[1] - pv[0]->device.v[1]) * (pv[2]->device.v[0] - pv[0]->device.v[0])) >= ((pv[1]->device.v[0] - pv[0]->device.v[0]) * (pv[2]->device.v[1] - pv[0]->device.v[1]))); if (cw) { viewport.smoothTriangleZb(pv, *materials[poly.material].ramp); } } } // Transform for clipping bool SmoothPipeline::transformForClipping( Viewport &viewport ) { Smooth_VertexData *vp = vpool; const Vertex *v = vertices; int j = nrVertices; nrVpool = j; uint in = 0; xmax = 0; xmin = 1000000; // arbitary bignum. ymax = 0; ymin = 1000000; do { vp->intensity = npool[v->normal].intensity; vp->cvv.mul( *objectToCvv, v->model ); vp->outcodes = vp->cvv.computeOutcodes(D); if ( !vp->outcodes ) { in++; vp->device.project( *cvvToDevice, vp->cvv ); if (xmax < vp->device.v[X]) xmax = vp->device.v[X]; if (xmin > vp->device.v[X]) xmin = vp->device.v[X]; if (ymax < vp->device.v[Y]) ymax = vp->device.v[Y]; if (ymin > vp->device.v[Y]) ymin = vp->device.v[Y]; } vp++; v++; } while (--j); clip = (in != nrVertices); if (!clip) viewport.setDirty( xmin, ymin, xmax, ymax ); return in != 0; } // Clip and Render void SmoothPipeline::clipAndRenderPolygons( Viewport &viewport, uint ) { const Polygon *poly = polygons; Smooth_PolygonData *pp = ppool; int j = nrPolygons; do { if (!pp->backface) { // optimistic assignment of pv[] - good for the teapot // program, but what about real usage? pv[0] = &vpool[poly->vertex0]; uint oc0 = vpool[poly->vertex0].outcodes; pv[1] = &vpool[poly->vertex1]; uint oc1 = vpool[poly->vertex1].outcodes; pv[2] = &vpool[poly->vertex2]; uint oc2 = vpool[poly->vertex2].outcodes; if ((oc0&oc1&oc2) == 0) { uint intersections = oc0|oc1|oc2; if (intersections == 0) { viewport.smoothPolygonZb(3, pv, *(materials[poly->material].ramp)); } else { uint nr = nrVpool; if (clipPolygon(*poly, intersections)) { viewport.smoothPolygonZb(nrClippedVertices, pv, *materials[poly->material].ramp); } nrVpool = nr; } } } poly++; pp++; } while (--j); viewport.setDirty( xmin, ymin, xmax, ymax ); } // Clipping in the truncated pyramid CVV. // // bool SmoothPipeline::clipPolygon(const Polygon &poly, uint intersections ) { // Clip against a truncated pyramid Smooth_VertexData *tmp_pool[MAX_CLIPPED_VERTICES]; Smooth_VertexData **from = tmp_pool; Smooth_VertexData **to = (Smooth_VertexData **)pv; int fromCount = 3; from[0] = &vpool[poly.vertex0]; from[1] = &vpool[poly.vertex1]; from[2] = &vpool[poly.vertex2]; int cb = 1; int plane = 0; for (; plane < 6; plane++, cb *= 2) { if ((cb & intersections) == 0) continue; int toCount = 0; int j = fromCount-1; int i = 0; int flagJ = from[j]->outcodes & cb; do { int flagI = from[i]->outcodes & cb; if (flagI ^ flagJ) { // Edge crosses plane. to[toCount] = &vpool[nrVpool++]; (*Intersect[plane])(*from[i], *from[j], *to[toCount]); to[toCount]->outcodes = to[toCount]->cvv.computeOutcodes(D); toCount++; } if (!flagI) { // Vertex is inside plane. to[toCount++] = from[i]; } flagJ = flagI; j = i++; } while ( i < fromCount ); if (toCount == 0) return false; fromCount = toCount; Smooth_VertexData **tmp = from; from = to; to = tmp; } // Transform new vertices to device space. The others are already // done. // for (int i = 0 ; i < fromCount ; i++ ) { Smooth_VertexData &v = *from[i]; pv[i] = from[i]; v.device.project( *cvvToDevice, v.cvv ); if (xmax < v.device.v[X]) xmax = v.device.v[X]; if (xmin > v.device.v[X]) xmin = v.device.v[X]; if (ymax < v.device.v[Y]) ymax = v.device.v[Y]; if (ymin > v.device.v[Y]) ymin = v.device.v[Y]; } nrClippedVertices = fromCount; return true; } void SmoothPipeline::intersectZ1(const Smooth_VertexData& a, const Smooth_VertexData& b, Smooth_VertexData &out ) { // Intersect all four components with Z=1 Vector3 d; d.sub(b.cvv,a.cvv); float t = (1-a.cvv.v[Z]) / d.v[Z]; out.cvv.v[X] = a.cvv.v[X] + t*d.v[X]; out.cvv.v[Y] = a.cvv.v[Y] + t*d.v[Y]; out.cvv.v[Z] = 1; out.intensity = a.intensity + int(t*(b.intensity-a.intensity)); } void SmoothPipeline::intersectZ2(const Smooth_VertexData& a, const Smooth_VertexData& b, Smooth_VertexData &out ) { // Intersect all four components with Z=::D Vector3 d; d.sub(b.cvv,a.cvv); float t = (::D-a.cvv.v[Z]) / d.v[Z]; out.cvv.v[X] = a.cvv.v[X] + t*d.v[X]; out.cvv.v[Y] = a.cvv.v[Y] + t*d.v[Y]; out.cvv.v[Z] = ::D; out.intensity = a.intensity + int(t*(b.intensity-a.intensity)); } void SmoothPipeline::intersectY1(const Smooth_VertexData& a, const Smooth_VertexData& b, Smooth_VertexData &out ) { // Intersect with Y=Z Vector3 d; d.sub(b.cvv,a.cvv); float t = (a.cvv.v[Y]-a.cvv.v[Z]) / (d.v[Z]-d.v[Y]); out.cvv.v[X] = a.cvv.v[X] + t*d.v[X]; out.cvv.v[Y] = a.cvv.v[Y] + t*d.v[Y]; out.cvv.v[Z] = out.cvv.v[Y]; out.intensity = a.intensity + int(t*(b.intensity-a.intensity)); } void SmoothPipeline::intersectY2(const Smooth_VertexData& a, const Smooth_VertexData& b, Smooth_VertexData &out ) { // Intersect with Y=-Z Vector3 d; d.sub(b.cvv,a.cvv); float t = -(a.cvv.v[Y]+a.cvv.v[Z]) / (d.v[Z]+d.v[Y]); out.cvv.v[X] = a.cvv.v[X] + t*d.v[X]; out.cvv.v[Y] = a.cvv.v[Y] + t*d.v[Y]; out.cvv.v[Z] = - out.cvv.v[Y]; out.intensity = a.intensity + int(t*(b.intensity-a.intensity)); } void SmoothPipeline::intersectX1(const Smooth_VertexData& a, const Smooth_VertexData& b, Smooth_VertexData &out ) { // Intersect with X=Z Vector3 d; d.sub(b.cvv,a.cvv); float t = (a.cvv.v[X]-a.cvv.v[Z]) / (d.v[Z]-d.v[X]); out.cvv.v[X] = a.cvv.v[X] + t*d.v[X]; out.cvv.v[Y] = a.cvv.v[Y] + t*d.v[Y]; out.cvv.v[Z] = out.cvv.v[X]; out.intensity = a.intensity + int(t*(b.intensity-a.intensity)); } void SmoothPipeline::intersectX2(const Smooth_VertexData& a, const Smooth_VertexData& b, Smooth_VertexData &out ) { // Intersect with X=-Z Vector3 d; d.sub(b.cvv,a.cvv); float t = -(a.cvv.v[X]+a.cvv.v[Z]) / (d.v[Z]+d.v[X]); out.cvv.v[X] = a.cvv.v[X] + t*d.v[X]; out.cvv.v[Y] = a.cvv.v[Y] + t*d.v[Y]; out.cvv.v[Z] = - out.cvv.v[X]; out.intensity = a.intensity + int(t*(b.intensity-a.intensity)); }