From e4d08b323265fb544a159d3d3e7ccd2f15f31fe4 Mon Sep 17 00:00:00 2001 From: David Mayerich Date: Sun, 12 Feb 2017 23:12:58 -0600 Subject: [PATCH] implemented a new spherical harmonics renderer --- FindGLEW.cmake | 126 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ main.cpp | 307 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------------------------------------------------------------------------------------- 2 files changed, 323 insertions(+), 110 deletions(-) create mode 100644 FindGLEW.cmake diff --git a/FindGLEW.cmake b/FindGLEW.cmake new file mode 100644 index 0000000..0b72457 --- /dev/null +++ b/FindGLEW.cmake @@ -0,0 +1,126 @@ +# Copyright (c) 2012-2016 DreamWorks Animation LLC +# +# All rights reserved. This software is distributed under the +# Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +# +# Redistributions of source code must retain the above copyright +# and license notice and the following restrictions and disclaimer. +# +# * Neither the name of DreamWorks Animation nor the names of +# its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +# LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +# + +#-*-cmake-*- +# - Find GLEW +# +# Author : Nicholas Yue yue.nicholas@gmail.com +# +# This auxiliary CMake file helps in find the GLEW headers and libraries +# +# GLEW_FOUND set if Glew is found. +# GLEW_INCLUDE_DIR GLEW's include directory +# GLEW_glew_LIBRARY GLEW libraries +# GLEW_glewmx_LIBRARY GLEWmx libraries (Mulitple Rendering Context) + +FIND_PACKAGE ( PackageHandleStandardArgs ) + +FIND_PATH( GLEW_LOCATION include/GL/glew.h + "$ENV{GLEW_ROOT}" + NO_DEFAULT_PATH + NO_SYSTEM_ENVIRONMENT_PATH + ) + +FIND_PACKAGE_HANDLE_STANDARD_ARGS ( GLEW + REQUIRED_VARS GLEW_LOCATION + ) + +IF ( GLEW_LOCATION ) + + SET( GLEW_INCLUDE_DIR "${GLEW_LOCATION}/include" CACHE STRING "GLEW include path") + + SET ( ORIGINAL_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES}) + IF (GLEW_USE_STATIC_LIBS) + IF (APPLE) + SET(CMAKE_FIND_LIBRARY_SUFFIXES ".a") + FIND_LIBRARY ( GLEW_LIBRARY_PATH GLEW PATHS ${GLEW_LOCATION}/lib + NO_DEFAULT_PATH + NO_SYSTEM_ENVIRONMENT_PATH + ) + FIND_LIBRARY ( GLEWmx_LIBRARY_PATH GLEWmx PATHS ${GLEW_LOCATION}/lib + NO_DEFAULT_PATH + NO_SYSTEM_ENVIRONMENT_PATH + ) + # MESSAGE ( "APPLE STATIC" ) + # MESSAGE ( "GLEW_LIBRARY_PATH = " ${GLEW_LIBRARY_PATH} ) + ELSEIF (WIN32) + # Link library + SET(CMAKE_FIND_LIBRARY_SUFFIXES ".lib") + FIND_LIBRARY ( GLEW_LIBRARY_PATH GLEW32S PATHS ${GLEW_LOCATION}/lib ) + FIND_LIBRARY ( GLEWmx_LIBRARY_PATH GLEW32MXS PATHS ${GLEW_LOCATION}/lib ) + ELSE (APPLE) + SET(CMAKE_FIND_LIBRARY_SUFFIXES ".a") + FIND_LIBRARY ( GLEW_LIBRARY_PATH GLEW PATHS ${GLEW_LOCATION}/lib + NO_DEFAULT_PATH + NO_SYSTEM_ENVIRONMENT_PATH + ) + FIND_LIBRARY ( GLEWmx_LIBRARY_PATH GLEWmx PATHS ${GLEW_LOCATION}/lib + NO_DEFAULT_PATH + NO_SYSTEM_ENVIRONMENT_PATH + ) + # MESSAGE ( "LINUX STATIC" ) + # MESSAGE ( "GLEW_LIBRARY_PATH = " ${GLEW_LIBRARY_PATH} ) + ENDIF (APPLE) + ELSE () + IF (APPLE) + SET(CMAKE_FIND_LIBRARY_SUFFIXES ".dylib") + FIND_LIBRARY ( GLEW_LIBRARY_PATH GLEW PATHS ${GLEW_LOCATION}/lib ) + FIND_LIBRARY ( GLEWmx_LIBRARY_PATH GLEWmx PATHS ${GLEW_LOCATION}/lib ) + ELSEIF (WIN32) + # Link library + SET(CMAKE_FIND_LIBRARY_SUFFIXES ".lib") + FIND_LIBRARY ( GLEW_LIBRARY_PATH GLEW32 PATHS ${GLEW_LOCATION}/lib ) + FIND_LIBRARY ( GLEWmx_LIBRARY_PATH GLEW32mx PATHS ${GLEW_LOCATION}/lib ) + # Load library + SET(CMAKE_FIND_LIBRARY_SUFFIXES ".dll") + FIND_LIBRARY ( GLEW_DLL_PATH GLEW32 PATHS ${GLEW_LOCATION}/bin + NO_DEFAULT_PATH + NO_SYSTEM_ENVIRONMENT_PATH + ) + FIND_LIBRARY ( GLEWmx_DLL_PATH GLEW32mx PATHS ${GLEW_LOCATION}/bin + NO_DEFAULT_PATH + NO_SYSTEM_ENVIRONMENT_PATH + ) + ELSE (APPLE) + # Unices + FIND_LIBRARY ( GLEW_LIBRARY_PATH GLEW PATHS ${GLEW_LOCATION}/lib + NO_DEFAULT_PATH + NO_SYSTEM_ENVIRONMENT_PATH + ) + FIND_LIBRARY ( GLEWmx_LIBRARY_PATH GLEWmx PATHS ${GLEW_LOCATION}/lib + NO_DEFAULT_PATH + NO_SYSTEM_ENVIRONMENT_PATH + ) + ENDIF (APPLE) + ENDIF () + # MUST reset + SET(CMAKE_FIND_LIBRARY_SUFFIXES ${ORIGINAL_CMAKE_FIND_LIBRARY_SUFFIXES}) + + SET( GLEW_GLEW_LIBRARY ${GLEW_LIBRARY_PATH} CACHE STRING "GLEW library") + SET( GLEW_GLEWmx_LIBRARY ${GLEWmx_LIBRARY_PATH} CACHE STRING "GLEWmx library") + +ENDIF () diff --git a/main.cpp b/main.cpp index af970d4..c4d314f 100644 --- a/main.cpp +++ b/main.cpp @@ -9,16 +9,15 @@ #include #include -#define theta_scale 0.01 -#define phi_scale 0.01 -#define zoom_scale 0.1 +#define theta_scale 0.01f +#define phi_scale 0.01f +#define zoom_scale 0.1f //create a global camera that will specify the viewport stim::camera cam; int mx, my; //mouse coordinates in the window space -stim::spharmonics S; -stim::gl_spharmonics R; //spherical harmonics to render +stim::gl_spharmonics SH(300); //spherical harmonics to render float d = 1.5; //initial distance between the camera and the sphere @@ -26,13 +25,19 @@ bool rotate_zoom = true; //sets the current camera mode (rotation = true, zoom = stim::arglist args; //class for processing command line arguments -bool zaxis = false; //render the z-axis (set via a command line flag) +bool axis = false; //render the z-axis (set via a command line flag) /// INTERPOLATION -stim::gl_spharmonics Si; +//stim::gl_spharmonics Si; bool interp = false; //if we are interpolating -float alpha = 0.0; //alpha value for interpolation -float dalpha = 0.1; //change in alpha value with a key press +float alpha = 0.0f; //alpha value for interpolation +float dalpha = 0.1f; //change in alpha value with a key press + +/// Visualization flags +bool mag = true; +bool cmap = true; +bool displace = true; +bool light = true; bool init(){ @@ -45,11 +50,47 @@ bool init(){ cam.setFOV(40); //initialize the texture map stuff - R.glInit(256); + //R.glInit(256); return true; } +void render_axes() { + glDisable(GL_TEXTURE_2D); //turn off texture mapping + glBegin(GL_LINES); + glColor3f(1.0f, 0.0f, 0.0f); //set the color to RED and render X + glVertex3f(0.0, 0.0, 0.0); + glVertex3f(100.0, 0.0, 0.0); + + glColor3f(0.0f, 1.0f, 0.0f); //set the color to RED and render X + glVertex3f(0.0, 0.0, 0.0); + glVertex3f(0.0, 100.0, 0.0); + + glColor3f(0.0f, 0.0f, 1.0f); //set the color to RED and render X + glVertex3f(0.0, 0.0, 0.0); + glVertex3f(0.0, 0.0, 100.0); + glEnd(); +} + +void render_lights() { + + stim::vec3 view = cam.getDirection(); //get the camera view direction + stim::vec3 up = cam.getUp(); //get the camera up direction + stim::vec3 left = view.cross(up).norm(); //create a vector pointing to the left + + GLfloat light0_diffuse[] = { 0.5f, 0.5f, 0.5f, 1 }; //create a directional light shining from the viewer's left + GLfloat light0_position[] = { left[0], left[1], left[2], 0.0 }; + glLightfv(GL_LIGHT0, GL_DIFFUSE, light0_diffuse); + glLightfv(GL_LIGHT0, GL_POSITION, light0_position); + + GLfloat light1_diffuse[] = { 0.8f, 0.8f, 0.8f, 1 }; //create a directional light shining from the viewer's position + GLfloat light1_position[] = { -view[0], -view[1], -view[2], 0.0 }; + glLightfv(GL_LIGHT1, GL_DIFFUSE, light0_diffuse); + glLightfv(GL_LIGHT1, GL_POSITION, light1_position); + + +} + //code that is run every time the user changes something void display(){ //clear the screen @@ -73,28 +114,28 @@ void display(){ //specify the camera parameters to OpenGL gluLookAt(p[0], p[1], p[2], d[0], d[1], d[2], u[0], u[1], u[2]); - //draw the sphere - if (interp) - R = (S * (1.0f - alpha) + Si * alpha); - else - R = S; - R.glRender(); - - - //glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); - - //draw the z-axis if requested - if(zaxis){ - glDisable(GL_TEXTURE_2D); - glColor3f(0.0f, 1.0f, 0.0f); - glBegin(GL_LINES); - glVertex3f(0.0, 0.0, 0.0); - glVertex3f(0.0, 0.0, 100.0); - glEnd(); + if (light) { + glEnable(GL_LIGHTING); + glEnable(GL_LIGHT0); + glEnable(GL_LIGHT1); + } + + glEnable(GL_COLOR_MATERIAL); + glEnable(GL_CULL_FACE); + glEnable(GL_DEPTH_TEST); + + render_lights(); //render the lights + glColor3f(1.0, 1.0, 1.0); + SH.render(); + + if (light) { + glDisable(GL_LIGHTING); //disable lighting to render the axes + glDisable(GL_LIGHT0); + glDisable(GL_LIGHT1); } - //flush commands on the GPU - glutSwapBuffers(); + if(axis) render_axes(); //render the axes if the user requests them + glutSwapBuffers(); //swap in the back buffer (double-buffering is used to prevent tearing) } void mouse_press(int button, int state, int x, int y){ @@ -104,11 +145,8 @@ void mouse_press(int button, int state, int x, int y){ rotate_zoom = true; else if(button == GLUT_RIGHT_BUTTON) rotate_zoom = false; - - //if the mouse is pressed - if(state == GLUT_DOWN){ - //set the current mouse position - mx = x; my = y; + if(state == GLUT_DOWN){ //if the mouse is pressed + mx = x; my = y; //set the current mouse position } } @@ -139,28 +177,26 @@ float uniformRandom() } std::vector > -sample_sphere(int num_samples, float radius = 1.0) -{ +sample_sphere(int num_samples, float radius = 1.0) { - float solidAngle = stim::TAU; ///Solid angle to sample over - float PHI[2], Z[2], range; ///Range of angles in cylinderical coordinates - PHI[0] = solidAngle/2; ///project the solid angle into spherical coords - PHI[1] = asin(0); /// - Z[0] = cos(PHI[0]); ///project the z into spherical coordinates - Z[1] = cos(PHI[1]); /// - range = Z[0] - Z[1]; ///the range of all possible z values. + float solidAngle = (float)stim::TAU; ///Solid angle to sample over + float PHI[2], Z[2], range; ///Range of angles in cylinderical coordinates + PHI[0] = solidAngle/2; ///project the solid angle into spherical coords + PHI[1] = (float)asin(0); + Z[0] = cos(PHI[0]); ///project the z into spherical coordinates + Z[1] = cos(PHI[1]); + range = Z[0] - Z[1]; ///the range of all possible z values. - float z, theta, phi; /// temporary individual + float z, theta, phi; /// temporary individual std::vector > samples; - //srand(time(NULL)); ///set random seed - srand(100); ///set random seed + srand(100); ///set random seed for(int i = 0; i < num_samples; i++) { z = uniformRandom()*range + Z[1]; - theta = uniformRandom() * stim::TAU; + theta = uniformRandom() * (float)stim::TAU; phi = acos(z); stim::vec3 sph(1, theta, phi); stim::vec3 cart = sph.sph2cart(); @@ -187,37 +223,81 @@ sample_sphere(int num_samples, float radius = 1.0) return samples; } +std::vector> obj2vec3(stim::obj& o) { + stim::vec3 c = o.centroid(); //calculate the centroid + size_t nv = o.numV(); //get the number of vertices in the obj file + std::vector> vlist(nv); //create an array to store the vertices + stim::vec3 p, v, s; + for (size_t vi = 0; vi < nv; vi++) { //for each vertex in the obj file + v = o.getV(vi); + p = (v - c); //center and normalize + s = p.cart2sph(); //convert to spherical coordinates + vlist[vi] = s; //store the spherical coordinates in the point list + } + return vlist; +} + +void advertise() { + std::cout << "usage: shview c0 c1 c2 c3 ... --option [A B C]" << std::endl; + std::cout << "examples:" << std::endl; + std::cout << " generate a spherical function with 4 coefficients (l=0 to 2)" << std::endl; + std::cout << " shview 1.3 0.2 2.3 1.34" << std::endl; + std::cout << " display a spherical function representing the spherical harmonic l = 3, m = -2" << std::endl; + std::cout << " shview --basis 3 -2" << std::endl; + std::cout << args.str(); + exit(0); +} + void process_arguments(int argc, char* argv[]){ args.add("help", "prints this help"); + args.section("Coefficients"); + args.add("coef", "specify spherical harmonic coefficients", "", "c0 c1 c2 c3 ..."); args.add("rand", "generates a random set of SH coefficients", "", "[N min max]"); args.add("sparse", "generates a function based on a set of sparse basis functions", "", "[l0 m0 c0 l1 m1 c1 l2 m2 c2 ...]"); - args.add("basis", "displays the specified SH basis function", "", "n, or [l m]"); + args.section("Projection"); + args.add("image", "approximates a spherical function represented by an image", "", "filename1.ppm filename2.ppm"); + args.add("n", "number of spherical harmonics coefficients to use for a projection", "10", "positive integer"); + args.add("basis", "displays the specified SH basis function", "", "n, or [l m]"); args.add("obj", "approximates a geometric object given as a Wavefront OBJ file", "", "filename"); args.add("out", "filename for outputting spherical harmonics coefficients", "", "filename"); - args.add("zaxis", "render the z-axis as a green line"); args.add("pdf", "outputs the PDF if an OBJ files is given"); + args.section("Visualization"); + args.add("axis", "render the z-axis as a green line"); + args.add("nodisp", "render the spherical function without displacement"); + args.add("nocmap", "render the spherical function without color mapping"); + args.add("nomag", "render the spherical function without calculating the absolute value (negative values are displaced negatively"); + args.add("nolight", "render the spherical function without lighting"); args.add("interp", "interpolates between two specified sets of coefficients", "", "[c0 c1 c2 c3 ...]"); //process the command line arguments args.parse(argc, argv); - //set the z-axis flag - if(args["zaxis"].is_set()) - zaxis = true; - - if (args["interp"]) { //if an interpolation harmonic is provided - for (unsigned int a = 0; a < args["interp"].nargs(); a++) - Si.push(args["interp"].as_float(a)); + //if the user asks for help, give it and exit + if (args["help"].is_set()) { + advertise(); } + //set the z-axis flag + if(args["axis"].is_set()) + axis = true; + if (args["nodisp"]) displace = false; + if (args["nocmap"]) cmap = false; + if (args["nomag"]) mag = false; + if (args["nolight"]) light = false; + SH.rendermode(displace, cmap, mag); + + //if (args["interp"]) { //if an interpolation harmonic is provided + // for (unsigned int a = 0; a < args["interp"].nargs(); a++) + // Si.push(args["interp"].as_float(a)); + //} + //if arguments are specified, push them as coefficients - if(args.nargs() > 0){ + if(args["coef"]){ //push all of the arguments to the spherical harmonics class as coefficients - for(unsigned int a = 0; a < args.nargs(); a++) - S.push(atof(args.arg(a).c_str())); + for(unsigned int a = 0; a < args["coef"].nargs(); a++) + SH.push((float)args["coef"].as_float(a)); } - //if the user wants to use a random set of SH coefficients else if(args["rand"].is_set()){ @@ -228,21 +308,21 @@ void process_arguments(int argc, char* argv[]){ } //seed the random number generator - srand(time(NULL)); + srand((unsigned int)time(NULL)); - unsigned int N = args["rand"].as_int(0); //get the number of random coefficients - double Cmin = args["rand"].as_float(1); //get the minimum and maximum coefficient values - double Cmax = args["rand"].as_float(2); + unsigned int N = args["rand"].as_int(0); //get the number of random coefficients + float Cmin = (float)args["rand"].as_float(1); //get the minimum and maximum coefficient values + float Cmax = (float)args["rand"].as_float(2); //generate the coefficients for(unsigned int c = 0; c < N; c++){ - double norm = (double) rand() / RAND_MAX; //calculate a random number in the range [0, 1] - double scaled = norm * (Cmax - Cmin) + Cmin; //scale the random number to [Cmin, Cmax] - S.push(scaled); //push the value as a coefficient + float norm = (float) rand() / RAND_MAX; //calculate a random number in the range [0, 1] + float scaled = norm * (Cmax - Cmin) + Cmin; //scale the random number to [Cmin, Cmax] + SH.push(scaled); //push the value as a coefficient } } - else if(args["sparse"].is_set()){ + else if (args["sparse"].is_set()) { //calculate the number of sparse coefficients unsigned int nC = args["sparse"].nargs() / 3; @@ -256,36 +336,59 @@ void process_arguments(int argc, char* argv[]){ int l, m; double v; //for each provided coefficient - for(unsigned int i = 0; i < nC; i++){ + for (unsigned int i = 0; i < nC; i++) { //load data for a single coefficient from the command line - l = args["sparse"].as_int( i * 3 + 0 ); - m = args["sparse"].as_int( i * 3 + 1 ); - v = args["sparse"].as_float( i * 3 + 2 ); + l = args["sparse"].as_int(i * 3 + 0); + m = args["sparse"].as_int(i * 3 + 1); + v = args["sparse"].as_float(i * 3 + 2); //calculate the 1D coefficient c = pow(l + 1, 2) - (l - m) - 1; //update the maximum coefficient index - if(c > Cmax) Cmax = c; + if (c > Cmax) Cmax = c; //insert the coefficient and value into vectors C.push_back(c); - V.push_back(v); + V.push_back(v); } //set the size of the SH coefficient array - S.resize(Cmax + 1); - + SH.resize(Cmax + 1); + //insert each coefficient - for(unsigned int i = 0; i < nC; i++){ - S.setc(C[i], V[i]); + for (unsigned int i = 0; i < nC; i++) { + SH.setc(C[i], V[i]); + } + } + else if (args["image"]) { + if (args["image"].nargs() == 0) { + std::cout << "ERROR: an image file must be specified as an argument to --image" << std::endl; + exit(1); + } + stim::image I(args["image"].as_string(0)); //load the image + if (I.channels() > 1) I = I.channel(0); //if a color image is provided, convert to monochrome + I = I.stretch(0.0f, 1.0f); //stretch the function so that it lies in [0 1] + SH.project(I.data(), I.width(), I.height(), args["n"].as_int(0)); //project the image function onto a spherical harmonics basis + if (args["image"].nargs() > 1) { //if the user provides two images + I.load(args["image"].as_string(1)); + if (I.channels() > 1) I = I.channel(0); //if a color image is provided, convert to monochrome + I = I.stretch(0.0f, 1.0f); //stretch the function so that it lies in [0 1] + SH.Sc.project(I.data(), I.width(), I.height(), args["n"].as_int(0)); } - } + else if(args["obj"].is_set()){ + if (args["obj"].nargs() == 0) { + std::cout << "ERROR: an OBJ file must be specified as an argument to --obj" << std::endl; + exit(1); + } + stim::obj OBJ(args["obj"].as_string(0)); //open the OBJ file + std::vector> vlist = obj2vec3(OBJ); //get the centered points for the OBJ + SH.pdf(vlist, args["n"].as_int(0)); - std::string filename = args["obj"].as_string(0); + /*std::string filename = args["obj"].as_string(0); unsigned int l = args["obj"].as_int(1); int p = args["obj"].as_int(2); std::cout << p << std::endl; @@ -354,9 +457,9 @@ void process_arguments(int argc, char* argv[]){ S.mcSample(sph[1], sph[2], weights[i]); } S.mcEnd(); - + */ } - else{ + /*else{ //begin Monte-Carlo sampling, using the model vertices as samples S.mcBegin(l, l); double theta, phi, fx, px; @@ -368,11 +471,11 @@ void process_arguments(int argc, char* argv[]){ S.mcSample(theta, phi, fx / px); } S.mcEnd(); - } - } + }*/ + //} //if the user specifies an SH basis function - else if(args["basis"].is_set()){ + /*if(args["basis"].is_set()){ unsigned int n; @@ -388,14 +491,14 @@ void process_arguments(int argc, char* argv[]){ //add zeros for the first (n-1) coefficients for(unsigned int c = 0; c < n; c++) - S.push(0); + SH.push(0); //add the n'th coefficient - S.push(1); - } + SH.push(1); + }*/ //output the spherical harmonics coefficients if requested - if(args["out"].is_set()){ + /*if(args["out"].is_set()){ if(args["out"].nargs() == 0) std::cout<