// This class provides methods to draw spheres. The shape is represented // as a "geodesic" mesh of triangles generated by subviding an icosahedron // until an edge length criteria is met. Supports wireframe and unshaded // triangle drawing styles. Provides front/back/both culling of faces. // // see drawSphere below // class DrawSphereHelper { public: Vec3 center; float radius; float maxEdgeLength; bool filled; Vec3 color; bool drawFrontFacing; bool drawBackFacing; Vec3 viewpoint; // default constructor (at origin, radius=1, white, wireframe, nocull) DrawSphereHelper () : center (Vec3::zero), radius (1.0f), maxEdgeLength (1.0f), filled (false), color (gWhite), drawFrontFacing (true), drawBackFacing (true), viewpoint (Vec3::zero) {} // "kitchen sink" constructor (allows specifying everything) DrawSphereHelper (const Vec3 _center, const float _radius, const float _maxEdgeLength, const bool _filled, const Vec3& _color, const bool _drawFrontFacing, const bool _drawBackFacing, const Vec3& _viewpoint) : center (_center), radius (_radius), maxEdgeLength (_maxEdgeLength), filled (_filled), color (_color), drawFrontFacing (_drawFrontFacing), drawBackFacing (_drawBackFacing), viewpoint (_viewpoint) {} // draw as an icosahedral geodesic sphere void draw (void) const { // Geometry based on Paul Bourke's excellent article: // Platonic Solids (Regular polytopes in 3D) // http://astronomy.swin.edu.au/~pbourke/polyhedra/platonic/ const float sqrt5 = sqrt (5.0f); const float phi = (1.0f + sqrt5) * 0.5f; // "golden ratio" // ratio of edge length to radius const float ratio = sqrt (10.0f + (2.0f * sqrt5)) / (4.0f * phi); const float a = (radius / ratio) * 0.5; const float b = (radius / ratio) / (2.0f * phi); // define the icosahedron's 12 vertices: const Vec3 v1 = center + Vec3 ( 0, b, -a); const Vec3 v2 = center + Vec3 ( b, a, 0); const Vec3 v3 = center + Vec3 (-b, a, 0); const Vec3 v4 = center + Vec3 ( 0, b, a); const Vec3 v5 = center + Vec3 ( 0, -b, a); const Vec3 v6 = center + Vec3 (-a, 0, b); const Vec3 v7 = center + Vec3 ( 0, -b, -a); const Vec3 v8 = center + Vec3 ( a, 0, -b); const Vec3 v9 = center + Vec3 ( a, 0, b); const Vec3 v10 = center + Vec3 (-a, 0, -b); const Vec3 v11 = center + Vec3 ( b, -a, 0); const Vec3 v12 = center + Vec3 (-b, -a, 0); // draw the icosahedron's 20 triangular faces: drawMeshedTriangleOnSphere (v1, v2, v3); drawMeshedTriangleOnSphere (v4, v3, v2); drawMeshedTriangleOnSphere (v4, v5, v6); drawMeshedTriangleOnSphere (v4, v9, v5); drawMeshedTriangleOnSphere (v1, v7, v8); drawMeshedTriangleOnSphere (v1, v10, v7); drawMeshedTriangleOnSphere (v5, v11, v12); drawMeshedTriangleOnSphere (v7, v12, v11); drawMeshedTriangleOnSphere (v3, v6, v10); drawMeshedTriangleOnSphere (v12, v10, v6); drawMeshedTriangleOnSphere (v2, v8, v9); drawMeshedTriangleOnSphere (v11, v9, v8); drawMeshedTriangleOnSphere (v4, v6, v3); drawMeshedTriangleOnSphere (v4, v2, v9); drawMeshedTriangleOnSphere (v1, v3, v10); drawMeshedTriangleOnSphere (v1, v8, v2); drawMeshedTriangleOnSphere (v7, v10, v12); drawMeshedTriangleOnSphere (v7, v11, v8); drawMeshedTriangleOnSphere (v5, v12, v6); drawMeshedTriangleOnSphere (v5, v9, v11); } // given two points, take midpoint and project onto this sphere inline Vec3 midpointOnSphere (const Vec3& a, const Vec3& b) const { const Vec3 midpoint = (a + b) * 0.5f; const Vec3 unitRadial = (midpoint - center).normalize (); return center + (unitRadial * radius); } // given three points on the surface of this sphere, if the triangle // is "small enough" draw it, otherwise subdivide it into four smaller // triangles and recursively draw each of them. void drawMeshedTriangleOnSphere (const Vec3& a, const Vec3& b, const Vec3& c) const { // if all edges are short enough if ((((a - b).length ()) < maxEdgeLength) && (((b - c).length ()) < maxEdgeLength) && (((c - a).length ()) < maxEdgeLength)) { // draw triangle drawTriangleOnSphere (a, b, c); } else // otherwise subdivide and recurse { // find edge midpoints const Vec3 ab = midpointOnSphere (a, b); const Vec3 bc = midpointOnSphere (b, c); const Vec3 ca = midpointOnSphere (c, a); // recurse on four sub-triangles drawMeshedTriangleOnSphere ( a, ab, ca); drawMeshedTriangleOnSphere (ab, b, bc); drawMeshedTriangleOnSphere (ca, bc, c); drawMeshedTriangleOnSphere (ab, bc, ca); } } // draw one mesh element for drawMeshedTriangleOnSphere void drawTriangleOnSphere (const Vec3& a, const Vec3& b, const Vec3& c) const { // draw triangle, subject to the camera orientation criteria // (according to drawBackFacing and drawFrontFacing) const Vec3 triCenter = (a + b + c) / 3.0f; const Vec3 triNormal = triCenter - center; // not unit length const Vec3 view = triCenter - viewpoint; const float dot = view.dot (triNormal); // project normal on view const bool seen = ((dot>0.0f) ? drawBackFacing : drawFrontFacing); if (seen) { if (filled) { // draw filled triangle if (drawFrontFacing) drawTriangle (c, b, a, color); else drawTriangle (a, b, c, color); } else { // draw triangle edges (use trick to avoid drawing each // edge twice (for each adjacent triangle) unless we are // culling and this tri is near the sphere's silhouette) const float unitDot = view.dot (triNormal.normalize ()); const float t = 0.05f; // near threshold const bool nearSilhouette = (unitDot-t); if (nearSilhouette && !(drawBackFacing&&drawFrontFacing)) { drawLine (a, b, color); drawLine (b, c, color); drawLine (c, a, color); } else { drawMeshedTriangleLine (a, b, color); drawMeshedTriangleLine (b, c, color); drawMeshedTriangleLine (c, a, color); } } } } // Draws line from A to B but not from B to A: assumes each edge // will be drawn in both directions, picks just one direction for // drawing according to an arbitary but reproducable criterion. void drawMeshedTriangleLine (const Vec3& a, const Vec3& b, const Vec3& color) const { if (a.x != b.x) { if (a.x > b.x) drawLine (a, b, color); } else { if (a.y != b.y) { if (a.y > b.y) drawLine (a, b, color); } else { if (a.z > b.z) drawLine (a, b, color); } } } }; // utility to draw a sphere void drawSphere (const Vec3 center, const float radius, const float maxEdgeLength, const bool filled, const Vec3& color, const bool drawFrontFacing, const bool drawBackFacing, const Vec3& viewpoint) { const DrawSphereHelper s (center, radius, maxEdgeLength, filled, color, drawFrontFacing, drawBackFacing, viewpoint); s.draw (); }