test/sphere.cpp

Go to the documentation of this file.
00001 /*
00002  * sphere.cpp
00003  *
00004  * Copyright (C) 2010  Thomas A. Vaughan
00005  * All rights reserved.
00006  *
00007  * Attempt at a funky sphere.
00008  */
00009 
00010 // includes --------------------------------------------------------------------
00011 #include <iostream>
00012 #include <fstream>
00013 
00014 #include "glut/frustum.h"
00015 #include "glut/texture.h"
00016 #include "glut-demo/glut-demo.h"
00017 #include "opengl-effects/opengl-effects.h"
00018 #include "perf/perf.h"
00019 
00020 
00021 
00022 static const float s_bounds     = 20.0;
00023 
00024 static const int s_nRings       = 7;
00025 
00026 
00027 ////////////////////////////////////////////////////////////////////////////////
00028 //
00029 //      static helper methods
00030 //
00031 ////////////////////////////////////////////////////////////////////////////////
00032 
00033 static float
00034 randomX
00035 (
00036 IN float x
00037 )
00038 {
00039         return (x * rand()) / RAND_MAX;
00040 }
00041 
00042 
00043 
00044 static void
00045 jitter
00046 (
00047 IO point3d_t& p,
00048 IN float dx
00049 )
00050 throw()
00051 {
00052         p.x += randomX(dx) - 0.5 * dx;
00053         p.y += randomX(dx) - 0.5 * dx;
00054         p.z += randomX(dx) - 0.5 * dx;
00055 }
00056 
00057 
00058 
00059 static void
00060 checkLimit
00061 (
00062 IN float& x,
00063 IN float& v
00064 )
00065 throw()
00066 {
00067         if (x < -s_bounds) {
00068                 x = -s_bounds;
00069                 if (v < 0) {
00070                         v = -v;
00071                 }
00072         } else if (x > s_bounds) {
00073                 x = s_bounds;
00074                 if (v > 0) {
00075                         v = -v;
00076                 }
00077         }
00078 }
00079 
00080 
00081 
00082 struct ring_t {
00083         ring_t(void) throw() {
00084                         gamma = 1.01 + randomX(0.06);
00085 
00086                         axis.x = -1.0 + randomX(2.0);
00087                         axis.y = -1.0 + randomX(2.0);
00088                         axis.z = -1.0 + randomX(2.0);
00089                         normalize(axis);
00090 
00091                         phi = randomX(2.0 * M_PI);
00092                         dPhi = 5 + randomX(10);
00093                 }
00094 
00095         void tick(void) throw() {
00096                         phi += dPhi;
00097                 }
00098 
00099         // data fields
00100         float           gamma;
00101         point3d_t       axis;
00102         float           phi;
00103         float           dPhi;
00104 };
00105 
00106 
00107 static void
00108 drawLightningRing
00109 (
00110 IN const point3d_t& center,
00111 IN float radius,
00112 IN const point3d_t& axis,
00113 IN float phi,
00114 IN const glut_color_t& color
00115 )
00116 throw()
00117 {
00118         ASSERT(radius > 0, "bad radius: %f", radius);
00119         int nPoints = 16;
00120         ASSERT(nPoints > 1, "need at least 2!");
00121 
00122         // translate to center, and rotate by specified axis
00123         glMatrixMode(GL_MODELVIEW);
00124         glPushMatrix();
00125         glTranslatef(center.x, center.y, center.z);
00126         glRotatef(phi, axis.x, axis.y, axis.z);
00127 
00128         float theta = 0;
00129         float dTheta = 2.0 * M_PI / (nPoints - 1);
00130 
00131         const float dJitter = 0.4;
00132 
00133         glDisable(GL_LIGHTING);
00134         glColor4fv(&color.red);
00135         glBegin(GL_LINE_LOOP);
00136         for (int i = 0; i < nPoints; ++i) {
00137                 float mu = theta + randomX(dJitter) - 0.5 * dJitter;
00138                 float nu = randomX(dJitter) - 0.5 * dJitter;
00139                 point3d_t p(cos(mu) * cos(nu), sin(nu), sin(mu) * cos(nu));
00140                 p *= radius;
00141                 glVertex3fv(&p.x);
00142                 theta += dTheta;
00143         }
00144         glEnd();
00145         glEnable(GL_LIGHTING);
00146 
00147         glPopMatrix();
00148 }
00149 
00150 
00151 
00152 struct cool_sphere_t {
00153         cool_sphere_t(void) throw() {
00154                         radius = 0.5 + randomX(0.5);
00155 
00156                         position.x = -10 + randomX(20);
00157                         position.y = -10 + randomX(20);
00158                         position.z = -10 + randomX(20);
00159 
00160                         const float vMax = 0.2;
00161                         velocity.x = -vMax + randomX(2.0 * vMax);
00162                         velocity.y = -vMax + randomX(2.0 * vMax);
00163                         velocity.z = -vMax + randomX(2.0 * vMax);
00164 
00165                         phi = randomX(2.0 * M_PI);
00166                         dPhi = -5 + randomX(10);
00167 
00168                         img_color_t c;
00169                         c.red =   (byte_t) (20 + randomX(235));
00170                         c.green = (byte_t) (20 + randomX(235));
00171                         c.blue =  (byte_t) (20 + randomX(235));
00172                         c.alpha = 128;
00173 
00174 //                      lineColor.red   = c.red / img_color_t::eMaxComponent;
00175 //                      lineColor.green = c.green / img_color_t::eMaxComponent;
00176 //                      lineColor.blue  = c.blue / img_color_t::eMaxComponent;
00177 //                      lineColor.alpha = 1.0;
00178 
00179                         // create density map
00180                         media::image_t img;
00181                         glut::createSphericalDensityMap(40, c, img);
00182 
00183                         // create texture
00184                         textureId = glut::createTextureFromImage(img);
00185                         DPRINTF("Texture ID: %d", textureId);
00186                 }
00187 
00188         ~cool_sphere_t(void) throw() {
00189                         if (textureId) {
00190                                 glDeleteTextures(1, &textureId);
00191                         }
00192                 }
00193 
00194         bool isValid(void) const throw() {
00195                         return (radius > 0 && textureId);
00196                 }
00197 
00198         void reset(void) throw() {
00199                         position.clear();
00200                         velocity.clear();
00201                 }
00202 
00203         void tick(void) throw() {
00204                         // update with velocity
00205                         position += velocity;
00206                         phi += dPhi;
00207 
00208                         // check limits
00209                         checkLimit(position.x, velocity.x);
00210                         checkLimit(position.y, velocity.y);
00211                         checkLimit(position.z, velocity.z);
00212 
00213                         // update rings
00214                         for (int i = 0; i < s_nRings; ++i) {
00215                                 rings[i].tick();
00216                         }
00217                 }
00218 
00219         // data fields
00220         float           radius;
00221         float           phi;
00222         float           dPhi;
00223         point3d_t       position;
00224         point3d_t       velocity;
00225         ring_t          rings[s_nRings];
00226         GLuint          textureId;
00227 };
00228 
00229 
00230 
00231 struct sort_entry_t {
00232         cool_sphere_t * sphere;
00233         float           d2;     // distance^2 from viewer
00234 };
00235 
00236 
00237 
00238 static int
00239 compareSpheres
00240 (
00241 IN const void * p1,
00242 IN const void * p2
00243 )
00244 throw()
00245 {
00246         const sort_entry_t * se1 = (const sort_entry_t *) p1;
00247         const sort_entry_t * se2 = (const sort_entry_t *) p2;
00248         ASSERT(se1 && se2, "null");
00249 
00250         if (se1->d2 < se2->d2)
00251                 return -1;      // first sphere is closer
00252         if (se1->d2 > se2->d2)
00253                 return +1;      // first sphere is farther
00254         return 0;       // equidistant!
00255 }
00256 
00257 
00258 
00259 static void
00260 drawCoolSphere
00261 (
00262 IN const cool_sphere_t& sphere,
00263 IN const glut::render_context_t& rc,
00264 IN glut::RenderQueue * rq
00265 )
00266 throw()
00267 {
00268         ASSERT(sphere.isValid(), "sphere is invalid?");
00269         ASSERT(rq, "null");
00270 
00271         // first, render a standard sphere at position
00272         glMatrixMode(GL_MODELVIEW);
00273         glPushMatrix();
00274         glTranslatef(sphere.position.x, sphere.position.y, sphere.position.z);
00275         glRotatef(sphere.phi, 0, 1, 0);
00276         glutSolidSphere(sphere.radius, 16, 16);
00277         glPopMatrix();
00278 
00279         // now draw lightning rings
00280         for (int i = 0; i < s_nRings; ++i) {
00281                 const ring_t& r = sphere.rings[i];
00282                 drawLightningRing(sphere.position, r.gamma * sphere.radius,
00283                     r.axis, r.phi, glut_color_t(1, 1, 1, 1));
00284         }
00285 
00286         // ray from viewer to center of sphere
00287         point3d_t to = sphere.position - rc.viewer.getPosition();
00288         float z2 = dotProduct(to, to);
00289         if (z2 < 1.0e-4)
00290                 return;         // too close!
00291         float z = sqrt(z2);
00292         to = (1.0 / z) * to;
00293 
00294         // we can choose an arbitrary "up" direction
00295         point3d_t up = rc.viewer.getUp();
00296         point3d_t right = crossProduct(to, up);
00297         normalize(right);
00298 
00299         // get a proper "up" direction now
00300         point3d_t realUp = -crossProduct(to, right);
00301         normalize(realUp);
00302 
00303         // queue request
00304         glut::poly_request_t * pr = rq->grabRequestSlot();
00305         if (!pr) {
00306                 DPRINTF("No available request slots?");
00307                 return;
00308         }
00309 
00310         matrix4_t T;
00311         glut::getModelViewMatrix(T);
00312 
00313         // regardless of how far away (or how close) the viewer is, we want the
00314         // "glow" effect to appear to be a constant radius
00315         // we perform some simple geometry calculations so that, even though the
00316         //      glow plane is closer to the viewer and therefore more sensitive
00317         //      to perspective effects, the overall glow always looks like it is
00318         //      an enclosing sphere of constant radius
00319         float beta = 1.25;      // desired radius scaling
00320         float gamma = 1.1;      // distance from sphere center to glow plane
00321         float alpha = beta * (z - gamma * sphere.radius) / z;
00322 //      DPRINTF("z=%f  beta=%f  gamma=%f  -->  alpha=%f", z, beta, gamma, alpha);
00323 
00324         // midpoint of new polygon
00325         float scale = alpha * sphere.radius;    // half width/height of glow plane
00326         point3d_t mid = sphere.position - gamma * sphere.radius * to;
00327 
00328         // set up
00329         pr->nVertices = 4;
00330         pr->textureId = sphere.textureId;
00331         pr->normal = -to;
00332 
00333         pr->vertex[0] = T * (mid + scale * (-up + right));
00334         pr->u[0] = 0;
00335         pr->v[0] = 0;
00336 
00337         pr->vertex[1] = T * (mid + scale * ( up + right));
00338         pr->u[1] = 0;
00339         pr->v[1] = 1;
00340 
00341         pr->vertex[2] = T * (mid + scale * ( up - right));
00342         pr->u[2] = 1;
00343         pr->v[2] = 1;
00344 
00345         pr->vertex[3] = T * (mid + scale * (-up - right));
00346         pr->u[3] = 1;
00347         pr->v[3] = 0;
00348 }
00349 
00350 
00351 
00352 class Drawer : public glut::DemoHost {
00353 public:
00354         Drawer(IN int nSpheres) throw() : m_nSpheres(nSpheres) { }
00355         ~Drawer(void) throw() { }
00356 
00357         // glut::Host class interface methods ----------------------------------
00358         void onInit(void) {
00359                         m_spheres.reserve(m_nSpheres);
00360                         m_sorting.reserve(m_nSpheres);
00361                         for (int i = 0; i < m_nSpheres; ++i) {
00362                                 smart_ptr<cool_sphere_t> sphere =
00363                                     new cool_sphere_t;
00364                                 ASSERT(sphere, "out of memory");
00365 
00366                                 // sphere->reset();
00367                                 m_spheres.push_back(sphere);
00368 
00369                                 // push an empty sort entry
00370                                 sort_entry_t se;
00371                                 m_sorting.push_back(se);
00372                         }
00373                 }
00374 
00375         void display3D(IN const glut::render_context_t& rc,
00376                                 IN glut::RenderQueue * rq) {
00377                         ASSERT(rq, "null");
00378 
00379                         ASSERT(m_nSpheres == (int) m_spheres.size(),
00380                             "size mismatch!");
00381                         ASSERT(m_nSpheres == (int) m_sorting.size(),
00382                             "size mismatch!");
00383 
00384                         // get view frustum
00385                         frustum_t f;
00386                         glut::getViewFrustum(rc.camera, rc.viewer, f);
00387 
00388                         // first pass: put all in array for depth sorting!
00389                         int nVisible = 0;
00390                         const point3d_t& viewPosition = rc.viewer.getPosition();
00391                         for (int i = 0; i < m_nSpheres; ++i) {
00392                                 cool_sphere_t * sphere = m_spheres[i];
00393                                 ASSERT(sphere, "null sphere in array");
00394                                 sphere->tick();
00395 
00396                                 rect3d_t r;
00397                                 r.getBoundingRectForSphere(sphere->position,
00398                                     sphere->radius);
00399                                 if (!(eContains_HitMask & f.containsRect(r))) {
00400                                         // this sphere isn't in the view
00401                                         // frustum!
00402                                         continue;
00403                                 }
00404 
00405                                 sort_entry_t& se = m_sorting[nVisible];
00406                                 point3d_t diff = sphere->position - viewPosition;
00407                                 se.sphere = sphere;
00408                                 se.d2 = dotProduct(diff, diff);
00409                                 nVisible++;
00410                         }
00411                         ASSERT(nVisible <= m_nSpheres, "mismatch");
00412 
00413                         // sort the array
00414                         qsort(m_sorting.data(), nVisible,
00415                             sizeof(sort_entry_t), compareSpheres);
00416 
00417                         // now walk from closest to farthest and draw
00418                         for (int i = 0; i < nVisible; ++i) {
00419                                 sort_entry_t& se = m_sorting[i];
00420                                 ASSERT(se.sphere, "null");
00421 
00422                                 drawCoolSphere(*se.sphere, rc, rq);
00423                         }
00424                 }
00425 
00426 private:
00427         // private typedefs ----------------------------------------------------
00428         typedef std::vector<smart_ptr<cool_sphere_t> > vec_sphere_t;
00429         typedef std::vector<sort_entry_t> vec_sort_t;
00430 
00431         // private member data -------------------------------------------------
00432         int                     m_nSpheres;
00433         vec_sphere_t            m_spheres;
00434         vec_sort_t              m_sorting;
00435 };
00436 
00437 
00438 
00439 ////////////////////////////////////////////////////////////////////////////////
00440 //
00441 //      entry point
00442 //
00443 ////////////////////////////////////////////////////////////////////////////////
00444 
00445 int
00446 main
00447 (
00448 IN int argc,
00449 IN const char * argv[]
00450 )
00451 {
00452         ASSERT(2 == argc, "Usage: opengl-effects-sphere <nSpheres>\n");
00453         int nSpheres = atoi(argv[1]);
00454         ASSERT(nSpheres > 0, "Bad sphere count: %d", nSpheres);
00455 
00456         try {
00457                 // title
00458                 std::string title = "OpenGL Effects Test -- Spheres";
00459 
00460                 // main loop
00461                 smart_ptr<glut::DemoHost> host = new Drawer(nSpheres);
00462                 ASSERT(host, "out of memory");
00463                 glut::startDemo(argc, argv, title.c_str(), host);
00464 
00465         } catch (std::exception& e) {
00466                 DPRINTF("Exception: %s", e.what());
00467         }
00468 
00469         // only reach here on error
00470         return -1;
00471 }
00472