/***** * glrender.cc * John Bowman, Orest Shardt, and Supakorn "Jamie" Rassameemasmuang * Render 3D Bezier paths and surfaces. *****/ #ifdef __CYGWIN__ #define _POSIX_C_SOURCE 200809L #endif #include #include #include #include #include "common.h" #include "locate.h" #include "seconds.h" #include "statistics.h" #include "bezierpatch.h" #include "beziercurve.h" #include "picture.h" #include "bbox3.h" #include "drawimage.h" #include "interact.h" #include "fpu.h" namespace gl { #ifdef HAVE_PTHREAD pthread_t mainthread; #endif } #ifdef HAVE_GL #include "tr.h" #ifdef HAVE_LIBGLUT #ifdef __MSDOS__ #ifndef FGAPI #define FGAPI GLUTAPI #endif #ifndef FGAPIENTRY #define FGAPIENTRY APIENTRY #endif #endif #define GLUT_BUILDING_LIB #endif // HAVE_LIBGLUT #ifdef HAVE_LIBGLUT #ifdef FREEGLUT #include #endif #endif #include "shaders.h" #ifdef HAVE_LIBOPENIMAGEIO #include #endif using settings::locateFile; using utils::seconds; namespace camp { Billboard BB; GLint pixelShader; GLint materialShader; GLint colorShader; GLint transparentShader; vertexBuffer material0Data(GL_POINTS); vertexBuffer material1Data(GL_LINES); vertexBuffer materialData; vertexBuffer colorData; vertexBuffer transparentData; vertexBuffer triangleData; const size_t Nbuffer=10000; const size_t nbuffer=1000; } #endif /* HAVE_GL */ #ifdef HAVE_LIBGLM using camp::Material; using camp::Maxmaterials; using camp::Nmaterials; using camp::nmaterials; using camp::MaterialMap; namespace camp { std::vector material; MaterialMap materialMap; size_t materialIndex; size_t Maxmaterials; size_t Nmaterials=1; size_t nmaterials=48; } extern void exitHandler(int); namespace gl { bool outlinemode=false; bool glthread=false; bool glupdate=false; bool glexit=false; bool initialize=true; using camp::picture; using camp::drawRawImage; using camp::transform; using camp::pair; using camp::triple; using vm::array; using vm::read; using camp::bbox3; using settings::getSetting; using settings::Setting; bool Iconify=false; bool ignorezoom; int Fitscreen=1; bool firstFit; bool queueExport=false; bool readyAfterExport=false; bool remesh; int Mode; double Aspect; bool View; int Oldpid; string Prefix; const picture* Picture; string Format; int fullWidth,fullHeight; int Width,Height; double oWidth,oHeight; int screenWidth,screenHeight; int maxTileWidth; int maxTileHeight; double Angle; bool orthographic; double H; double xmin,xmax; double ymin,ymax; double zmin,zmax; double Xmin,Xmax; double Ymin,Ymax; pair Shift; pair Margin; double X,Y; int x0,y0; double cx,cy; double Xfactor,Yfactor; double ArcballFactor; static const double pi=acos(-1.0); static const double degrees=180.0/pi; static const double radians=1.0/degrees; double *Background; size_t Nlights=1; // Maximum number of lights compiled in shader size_t nlights; // Actual number of lights size_t nlights0; triple *Lights; double *Diffuse; double *Specular; bool antialias; double Zoom; double Zoom0; double lastzoom; GLint lastshader=-1; using glm::dvec3; using glm::dmat3; using glm::mat3; using glm::mat4; using glm::dmat4; using glm::value_ptr; using glm::translate; mat3 normMat; dmat3 dnormMat; mat4 projViewMat; mat4 viewMat; dmat4 dprojMat; dmat4 dprojViewMat; dmat4 dviewMat; dmat4 drotateMat; const double *dprojView; const double *dView; double BBT[9]; unsigned int framecount; template inline T min(T a, T b) { return (a < b) ? a : b; } template inline T max(T a, T b) { return (a > b) ? a : b; } glm::vec4 vec4(triple v) { return glm::vec4(v.getx(),v.gety(),v.getz(),0); } glm::vec4 vec4(double *v) { return glm::vec4(v[0],v[1],v[2],v[3]); } void setDimensions(int Width, int Height, double X, double Y) { double Aspect=((double) Width)/Height; double xshift=(X/Width+Shift.getx()*Xfactor)*Zoom; double yshift=(Y/Height+Shift.gety()*Yfactor)*Zoom; double Zoominv=1.0/lastzoom; if(orthographic) { double xsize=Xmax-Xmin; double ysize=Ymax-Ymin; if(xsize < ysize*Aspect) { double r=0.5*ysize*Aspect*Zoominv; double X0=2.0*r*xshift; double Y0=(Ymax-Ymin)*Zoominv*yshift; xmin=-r-X0; xmax=r-X0; ymin=Ymin*Zoominv-Y0; ymax=Ymax*Zoominv-Y0; } else { double r=0.5*xsize/(Aspect*Zoom); double X0=(Xmax-Xmin)*Zoominv*xshift; double Y0=2.0*r*yshift; xmin=Xmin*Zoominv-X0; xmax=Xmax*Zoominv-X0; ymin=-r-Y0; ymax=r-Y0; } } else { double r=H*Zoominv; double rAspect=r*Aspect; double X0=2.0*rAspect*xshift; double Y0=2.0*r*yshift; xmin=-rAspect-X0; xmax=rAspect-X0; ymin=-r-Y0; ymax=r-Y0; } } void updateProjection() { dprojViewMat=dprojMat*dviewMat; projViewMat=mat4(dprojViewMat); dprojView=value_ptr(dprojViewMat); } void frustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble nearVal, GLdouble farVal) { dprojMat=glm::frustum(left,right,bottom,top,nearVal,farVal); updateProjection(); } void ortho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble nearVal, GLdouble farVal) { dprojMat=glm::ortho(left,right,bottom,top,nearVal,farVal); updateProjection(); } void setProjection() { setDimensions(Width,Height,X,Y); if(orthographic) ortho(xmin,xmax,ymin,ymax,-zmax,-zmin); else frustum(xmin,xmax,ymin,ymax,-zmax,-zmin); } void updateModelViewData() { // Like Fortran, OpenGL uses transposed (column-major) format! dnormMat=dmat3(glm::inverse(dviewMat)); double *T=value_ptr(dnormMat); for(size_t i=0; i < 9; ++i) BBT[i]=T[i]; normMat=mat3(dnormMat); } bool Xspin,Yspin,Zspin; bool Animate; bool Step; #ifdef HAVE_GL void idle() { glutIdleFunc(NULL); Xspin=Yspin=Zspin=Animate=Step=false; } #endif void home(bool webgl=false) { X=Y=cx=cy=0.0; #ifdef HAVE_GL #ifdef HAVE_LIBGLUT #ifndef HAVE_LIBOSMESA if(!webgl) idle(); #endif #endif #endif dviewMat=dmat4(1.0); dView=value_ptr(dviewMat); viewMat=mat4(dviewMat); drotateMat=dmat4(1.0); updateModelViewData(); remesh=true; lastzoom=Zoom=Zoom0; setDimensions(Width,Height,0,0); framecount=0; } #ifdef HAVE_GL double T[16]; #ifdef HAVE_LIBGLUT timeval lasttime; timeval lastframetime; int oldWidth,oldHeight; bool queueScreen=false; string Action; double lastangle; int window; #endif using utils::statistics; statistics S; #ifdef HAVE_LIBOPENIMAGEIO GLuint envMapBuf; GLuint initHDR() { GLuint tex; glGenTextures(1, &tex); auto imagein = OIIO::ImageInput::open(locateFile("res/studio006.hdr").c_str()); OIIO::ImageSpec const& imspec = imagein->spec(); // uses GL_TEXTURE1 for now. glActiveTexture(GL_TEXTURE1); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glBindTexture(GL_TEXTURE_2D, tex); std::vector pixels(imspec.width*imspec.height*3); imagein->read_image(pixels.data()); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, imspec.width, imspec.height, 0, GL_RGB, GL_FLOAT, pixels.data()); glGenerateMipmap(GL_TEXTURE_2D); imagein->close(); glActiveTexture(GL_TEXTURE0); return tex; } #endif GLint shaderProg,shaderProgColor; void *glrenderWrapper(void *a); #ifdef HAVE_LIBOSMESA OSMesaContext ctx; unsigned char *osmesa_buffer; #endif #ifdef HAVE_PTHREAD pthread_cond_t initSignal=PTHREAD_COND_INITIALIZER; pthread_mutex_t initLock=PTHREAD_MUTEX_INITIALIZER; pthread_cond_t readySignal=PTHREAD_COND_INITIALIZER; pthread_mutex_t readyLock=PTHREAD_MUTEX_INITIALIZER; void endwait(pthread_cond_t& signal, pthread_mutex_t& lock) { pthread_mutex_lock(&lock); pthread_cond_signal(&signal); pthread_mutex_unlock(&lock); } void wait(pthread_cond_t& signal, pthread_mutex_t& lock) { pthread_mutex_lock(&lock); pthread_cond_signal(&signal); pthread_cond_wait(&signal,&lock); pthread_mutex_unlock(&lock); } #endif void initShaders() { Nlights=nlights == 0 ? 0 : max(Nlights,nlights); Nmaterials=max(Nmaterials,nmaterials); shaderProg=glCreateProgram(); string vs=locateFile("shaders/vertex.glsl"); string fs=locateFile("shaders/fragment.glsl"); if(vs.empty() || fs.empty()) { cerr << "GLSL shaders not found." << endl; exit(-1); } std::vector shaderParams; #if HAVE_LIBOPENIMAGEIO if (getSetting("envmap")) { shaderParams.push_back("ENABLE_TEXTURE"); envMapBuf=initHDR(); } #endif std::vector shaders; shaders.push_back(ShaderfileModePair(vs.c_str(),GL_VERTEX_SHADER)); shaders.push_back(ShaderfileModePair(fs.c_str(),GL_FRAGMENT_SHADER)); if(orthographic) shaderParams.push_back("ORTHOGRAPHIC"); shaderParams.push_back("WIDTH"); camp::pixelShader=compileAndLinkShader(shaders,Nlights,Nmaterials, shaderParams); shaderParams.pop_back(); shaderParams.push_back("NORMAL"); camp::materialShader=compileAndLinkShader(shaders,Nlights,Nmaterials, shaderParams); shaderParams.push_back("COLOR"); camp::colorShader=compileAndLinkShader(shaders,Nlights,Nmaterials, shaderParams); shaderParams.push_back("TRANSPARENT"); camp::transparentShader=compileAndLinkShader(shaders,Nlights,Nmaterials, shaderParams); } void deleteShaders() { glDeleteProgram(camp::transparentShader); glDeleteProgram(camp::colorShader); glDeleteProgram(camp::materialShader); glDeleteProgram(camp::pixelShader); } void setBuffers() { GLuint vao; glGenVertexArrays(1,&vao); glBindVertexArray(vao); camp::material0Data.reserve0(); camp::materialData.reserve(); camp::colorData.Reserve(); camp::triangleData.Reserve(); camp::transparentData.Reserve(); } void drawscene(int Width, int Height) { #ifdef HAVE_PTHREAD static bool first=true; if(glthread && first) { wait(initSignal,initLock); endwait(initSignal,initLock); first=false; } #endif if((nlights == 0 && Nlights > 0) || nlights > Nlights || nmaterials > Nmaterials) { deleteShaders(); initShaders(); lastshader=-1; } glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); triple m(xmin,ymin,zmin); triple M(xmax,ymax,zmax); double perspective=orthographic ? 0.0 : 1.0/zmax; double size2=hypot(Width,Height); if(remesh) camp::drawElement::center.clear(); Picture->render(size2,m,M,perspective,remesh); if(!outlinemode) remesh=false; } // Return x divided by y rounded up to the nearest integer. int Quotient(int x, int y) { return (x+y-1)/y; } void Export() { glReadBuffer(GL_BACK_LEFT); glPixelStorei(GL_PACK_ALIGNMENT,1); glFinish(); try { size_t ndata=3*fullWidth*fullHeight; unsigned char *data=new unsigned char[ndata]; if(data) { TRcontext *tr=trNew(); int width=Quotient(fullWidth,Quotient(fullWidth,min(maxTileWidth,Width))); int height=Quotient(fullHeight,Quotient(fullHeight, min(maxTileHeight,Height))); if(settings::verbose > 1) cout << "Exporting " << Prefix << " as " << fullWidth << "x" << fullHeight << " image" << " using tiles of size " << width << "x" << height << endl; unsigned border=min(min(1,width/2),height/2); trTileSize(tr,width,height,border); trImageSize(tr,fullWidth,fullHeight); trImageBuffer(tr,GL_RGB,GL_UNSIGNED_BYTE,data); setDimensions(fullWidth,fullHeight,X/Width*fullWidth,Y/Width*fullWidth); (orthographic ? trOrtho : trFrustum)(tr,xmin,xmax,ymin,ymax,-zmax,-zmin); size_t count=0; do { trBeginTile(tr); remesh=true; drawscene(fullWidth,fullHeight); ++count; } while (trEndTile(tr)); if(settings::verbose > 1) cout << count << " tile" << (count != 1 ? "s" : "") << " drawn" << endl; trDelete(tr); picture pic; double w=oWidth; double h=oHeight; double Aspect=((double) fullWidth)/fullHeight; if(w > h*Aspect) w=(int) (h*Aspect+0.5); else h=(int) (w/Aspect+0.5); // Render an antialiased image. drawRawImage *Image=new drawRawImage(data,fullWidth,fullHeight, transform(0.0,0.0,w,0.0,0.0,h), antialias); pic.append(Image); pic.shipout(NULL,Prefix,Format,false,View); delete Image; delete[] data; } } catch(handled_error) { } catch(std::bad_alloc&) { outOfMemory(); } remesh=true; setProjection(); #ifndef HAVE_LIBOSMESA #ifdef HAVE_LIBGLUT glutPostRedisplay(); #endif #ifdef HAVE_PTHREAD if(glthread && readyAfterExport) { readyAfterExport=false; endwait(readySignal,readyLock); } #endif #endif } void nodisplay() { } void destroywindow() { glutDestroyWindow(glutGetWindow()); } // Return the greatest power of 2 less than or equal to n. inline unsigned int floorpow2(unsigned int n) { n |= n >> 1; n |= n >> 2; n |= n >> 4; n |= n >> 8; n |= n >> 16; return n-(n >> 1); } void quit() { #ifdef HAVE_LIBOSMESA if(osmesa_buffer) delete[] osmesa_buffer; if(ctx) OSMesaDestroyContext(ctx); exit(0); #endif #ifdef HAVE_LIBGLUT if(glthread) { bool animating=getSetting("animating"); if(animating) Setting("interrupt")=true; home(); Animate=getSetting("autoplay"); #ifdef HAVE_PTHREAD if(!interact::interactive || animating) { idle(); glutDisplayFunc(nodisplay); endwait(readySignal,readyLock); } #endif if(interact::interactive) glutHideWindow(); } else { glutDestroyWindow(window); exit(0); } #endif } void mode() { remesh=true; switch(Mode) { case 0: // regular outlinemode=false; nlights=nlights0; lastshader=-1; glPolygonMode(GL_FRONT_AND_BACK,GL_FILL); ++Mode; break; case 1: // outline outlinemode=true; nlights=0; glPolygonMode(GL_FRONT_AND_BACK,GL_LINE); ++Mode; break; case 2: // wireframe outlinemode=false; Mode=0; break; } #ifdef HAVE_LIBGLUT #ifndef HAVE_LIBOSMESA glutPostRedisplay(); #endif #endif } // GUI-related functions #ifdef HAVE_LIBGLUT bool capsize(int& width, int& height) { bool resize=false; if(width > screenWidth) { width=screenWidth; resize=true; } if(height > screenHeight) { height=screenHeight; resize=true; } return resize; } void reshape0(int width, int height) { X=(X/Width)*width; Y=(Y/Height)*height; Width=width; Height=height; setProjection(); glViewport(0,0,Width,Height); } void windowposition(int& x, int& y, int width=Width, int height=Height) { pair z=getSetting("position"); x=(int) z.getx(); y=(int) z.gety(); if(x < 0) { x += screenWidth-width; if(x < 0) x=0; } if(y < 0) { y += screenHeight-height; if(y < 0) y=0; } } void setsize(int w, int h, bool reposition=true) { int x,y; capsize(w,h); if(reposition) { windowposition(x,y,w,h); glutPositionWindow(x,y); } else glutPositionWindow(max(glutGet(GLUT_WINDOW_X)-2,0), max(glutGet(GLUT_WINDOW_Y)-2,0)); glutReshapeWindow(w,h); reshape0(w,h); glutPostRedisplay(); } void capzoom() { static double maxzoom=sqrt(DBL_MAX); static double minzoom=1.0/maxzoom; if(Zoom <= minzoom) Zoom=minzoom; if(Zoom >= maxzoom) Zoom=maxzoom; if(Zoom != lastzoom) remesh=true; lastzoom=Zoom; } void fullscreen(bool reposition=true) { Width=screenWidth; Height=screenHeight; if(firstFit) { if(Width < Height*Aspect) Zoom *= Width/(Height*Aspect); capzoom(); setProjection(); firstFit=false; } Xfactor=((double) screenHeight)/Height; Yfactor=((double) screenWidth)/Width; reshape0(Width,Height); if(reposition) glutPositionWindow(0,0); glutReshapeWindow(Width,Height); glutPostRedisplay(); } void fitscreen(bool reposition=true) { if(Animate && Fitscreen == 2) Fitscreen=0; switch(Fitscreen) { case 0: // Original size { Xfactor=Yfactor=1.0; double pixelRatio=getSetting("devicepixelratio"); setsize(oldWidth*pixelRatio,oldHeight*pixelRatio,reposition); break; } case 1: // Fit to screen in one dimension { oldWidth=Width; oldHeight=Height; int w=screenWidth; int h=screenHeight; if(w > h*Aspect) w=min((int) ceil(h*Aspect),w); else h=min((int) ceil(w/Aspect),h); setsize(w,h,reposition); break; } case 2: // Full screen { fullscreen(reposition); break; } } } void togglefitscreen() { ++Fitscreen; if(Fitscreen > 2) Fitscreen=0; fitscreen(); } void initTimer() { gettimeofday(&lasttime,NULL); gettimeofday(&lastframetime,NULL); } void idleFunc(void (*f)()) { initTimer(); glutIdleFunc(f); } void screen() { if(glthread && !interact::interactive) fitscreen(false); } void nextframe(int) { #ifdef HAVE_PTHREAD endwait(readySignal,readyLock); #endif double framedelay=getSetting("framedelay"); if(framedelay > 0) usleep((unsigned int) (1000.0*framedelay+0.5)); if(Step) Animate=false; } void display() { if(queueScreen) { if(!Animate) screen(); queueScreen=false; } bool fps=settings::verbose > 2; drawscene(Width,Height); if(fps) { if(framecount < 10) // Measure steady-state framerate seconds(); else { double s=seconds(); if(s > 0.0) { double rate=1.0/s; S.add(rate); cout << "FPS=" << rate << "\t" << S.mean() << " +/- " << S.stdev() << endl; } } ++framecount; } glutSwapBuffers(); #ifdef HAVE_PTHREAD if(glthread && Animate) { queueExport=false; double delay=1.0/getSetting("framerate"); timeval tv; gettimeofday(&tv,NULL); double seconds=tv.tv_sec-lastframetime.tv_sec+ ((double) tv.tv_usec-lastframetime.tv_usec)/1000000.0; lastframetime=tv; double milliseconds=1000.0*(delay-seconds); double framedelay=getSetting("framedelay"); if(framedelay > 0) milliseconds -= framedelay; if(milliseconds > 0) glutTimerFunc((int) (milliseconds+0.5),nextframe,0); else nextframe(0); } #endif if(queueExport) { Export(); queueExport=false; } if(!glthread) { if(Oldpid != 0 && waitpid(Oldpid,NULL,WNOHANG) != Oldpid) { kill(Oldpid,SIGHUP); Oldpid=0; } } } void update() { glutDisplayFunc(display); Animate=getSetting("autoplay"); glutShowWindow(); if(Zoom != lastzoom) remesh=true; lastzoom=Zoom; double cz=0.5*(zmin+zmax); dviewMat=translate(translate(dmat4(1.0),dvec3(cx,cy,cz))*drotateMat, dvec3(0,0,-cz)); dView=value_ptr(dviewMat); viewMat=mat4(dviewMat); setProjection(); updateModelViewData(); glutPostRedisplay(); } void updateHandler(int) { queueScreen=true; remesh=true; update(); if(interact::interactive || !Animate) { glutShowWindow(); } } void poll(int) { if(glupdate) { updateHandler(0); glupdate=false; } if(glexit) { exitHandler(0); glexit=false; } glutTimerFunc(100.0,poll,0); } void animate() { Animate=!Animate; if(Animate) { if(Fitscreen == 2) { togglefitscreen(); togglefitscreen(); } update(); } else idle(); } void reshape(int width, int height) { if(glthread) { static bool initialize=true; if(initialize) { initialize=false; Signal(SIGUSR1,updateHandler); } } if(capsize(width,height)) glutReshapeWindow(width,height); reshape0(width,height); remesh=true; } void shift(int x, int y) { double Zoominv=1.0/Zoom; X += (x-x0)*Zoominv; Y += (y0-y)*Zoominv; x0=x; y0=y; update(); } void pan(int x, int y) { if(orthographic) { double Zoominv=1.0/Zoom; X += (x-x0)*Zoominv; Y += (y0-y)*Zoominv; } else { cx += (x-x0)*(xmax-xmin)/Width; cy += (y0-y)*(ymax-ymin)/Height; } x0=x; y0=y; update(); } void zoom(int x, int y) { if(ignorezoom) {ignorezoom=false; y0=y; return;} double zoomFactor=getSetting("zoomfactor"); if(zoomFactor > 0.0) { double zoomStep=getSetting("zoomstep"); const double limit=log(0.1*DBL_MAX)/log(zoomFactor); double stepPower=zoomStep*(y0-y); if(fabs(stepPower) < limit) { Zoom *= pow(zoomFactor,stepPower); capzoom(); y0=y; setProjection(); glutPostRedisplay(); } } } void mousewheel(int wheel, int direction, int x, int y) { double zoomFactor=getSetting("zoomfactor"); if(zoomFactor > 0.0) { if(direction > 0) Zoom *= zoomFactor; else Zoom /= zoomFactor; capzoom(); setProjection(); glutPostRedisplay(); } } struct arcball { double angle; triple axis; arcball(double x0, double y0, double x, double y) { triple v0=norm(x0,y0); triple v1=norm(x,y); double Dot=dot(v0,v1); angle=Dot > 1.0 ? 0.0 : Dot < -1.0 ? pi : acos(Dot); axis=unit(cross(v0,v1)); } triple norm(double x, double y) { double norm=hypot(x,y); if(norm > 1.0) { double denom=1.0/norm; x *= denom; y *= denom; } return triple(x,y,sqrt(max(1.0-x*x-y*y,0.0))); } }; inline double glx(int x) { return 2.0*x/Width-1.0; } inline double gly(int y) { return 1.0-2.0*y/Height; } void rotate(int x, int y) { if(x != x0 || y != y0) { arcball A(glx(x0),gly(y0),glx(x),gly(y)); triple v=A.axis; drotateMat=glm::rotate(2*A.angle/lastzoom*ArcballFactor, glm::dvec3(v.getx(),v.gety(),v.getz()))* drotateMat; x0=x; y0=y; update(); } } double Degrees(int x, int y) { return atan2(0.5*Height-y-Y,x-0.5*Width-X)*degrees; } void rotateX(double step) { dmat4 tmpRot(1.0); tmpRot=glm::rotate(tmpRot,glm::radians(step),dvec3(1,0,0)); drotateMat=tmpRot*drotateMat; update(); } void rotateY(double step) { dmat4 tmpRot(1.0); tmpRot=glm::rotate(tmpRot,glm::radians(step),dvec3(0,1,0)); drotateMat=tmpRot*drotateMat; update(); } void rotateZ(double step) { dmat4 tmpRot(1.0); tmpRot=glm::rotate(tmpRot,glm::radians(step),dvec3(0,0,1)); drotateMat=tmpRot*drotateMat; update(); } void rotateX(int x, int y) { double angle=Degrees(x,y); rotateX(angle-lastangle); lastangle=angle; } void rotateY(int x, int y) { double angle=Degrees(x,y); rotateY(angle-lastangle); lastangle=angle; } void rotateZ(int x, int y) { double angle=Degrees(x,y); rotateZ(angle-lastangle); lastangle=angle; } #ifndef GLUT_WHEEL_UP #define GLUT_WHEEL_UP 3 #endif #ifndef GLUT_WHEEL_DOWN #define GLUT_WHEEL_DOWN 4 #endif string action(int button, int mod) { size_t Button; size_t nButtons=5; switch(button) { case GLUT_LEFT_BUTTON: Button=0; break; case GLUT_MIDDLE_BUTTON: Button=1; break; case GLUT_RIGHT_BUTTON: Button=2; break; case GLUT_WHEEL_UP: Button=3; break; case GLUT_WHEEL_DOWN: Button=4; break; default: Button=nButtons; } size_t Mod; size_t nMods=4; switch(mod) { case 0: Mod=0; break; case GLUT_ACTIVE_SHIFT: Mod=1; break; case GLUT_ACTIVE_CTRL: Mod=2; break; case GLUT_ACTIVE_ALT: Mod=3; break; default: Mod=nMods; } if(Button < nButtons) { array *left=getSetting("leftbutton"); array *middle=getSetting("middlebutton"); array *right=getSetting("rightbutton"); array *wheelup=getSetting("wheelup"); array *wheeldown=getSetting("wheeldown"); array *Buttons[]={left,middle,right,wheelup,wheeldown}; array *a=Buttons[button]; size_t size=checkArray(a); if(Mod < size) return read(a,Mod); } return ""; } void timeout(int) { } void mouse(int button, int state, int x, int y) { int mod=glutGetModifiers(); string Action=action(button,mod); if(Action == "zoomin") { glutMotionFunc(NULL); mousewheel(0,1,x,y); return; } if(Action == "zoomout") { glutMotionFunc(NULL); mousewheel(0,-1,x,y); return; } if(state == GLUT_DOWN) { if(Action == "rotate") { x0=x; y0=y; glutMotionFunc(rotate); } else if(Action == "shift") { x0=x; y0=y; glutMotionFunc(shift); } else if(Action == "pan") { x0=x; y0=y; glutMotionFunc(pan); } else if(Action == "zoom" || Action == "zoom/menu") { y0=y; glutMotionFunc(zoom); } else if(Action == "rotateX") { lastangle=Degrees(x,y); glutMotionFunc(rotateX); } else if(Action == "rotateY") { lastangle=Degrees(x,y); glutMotionFunc(rotateY); } else if(Action == "rotateZ") { lastangle=Degrees(x,y); glutMotionFunc(rotateZ); } } else { glutMotionFunc(NULL); } } double spinstep() { timeval tv; gettimeofday(&tv,NULL); double step=getSetting("spinstep")* (tv.tv_sec-lasttime.tv_sec+ ((double) tv.tv_usec-lasttime.tv_usec)/1000000.0); lasttime=tv; return step; } void xspin() { rotateX(spinstep()); } void yspin() { rotateY(spinstep()); } void zspin() { rotateZ(spinstep()); } void expand() { double resizeStep=getSetting("resizestep"); if(resizeStep > 0.0) setsize((int) (Width*resizeStep+0.5),(int) (Height*resizeStep+0.5)); } void shrink() { double resizeStep=getSetting("resizestep"); if(resizeStep > 0.0) setsize(max((int) (Width/resizeStep+0.5),1), max((int) (Height/resizeStep+0.5),1)); } void spinx() { if(Xspin) idle(); else { idleFunc(xspin); Xspin=true; Yspin=Zspin=false; } } void spiny() { if(Yspin) idle(); else { idleFunc(yspin); Yspin=true; Xspin=Zspin=false; } } void spinz() { if(Zspin) idle(); else { idleFunc(zspin); Zspin=true; Xspin=Yspin=false; } } void showCamera() { projection P=camera(); string projection=P.orthographic ? "orthographic(" : "perspective("; string indent(2+projection.length(),' '); cout << endl << "currentprojection=" << endl << " " << projection << "camera=" << P.camera << "," << endl << indent << "up=" << P.up << "," << endl << indent << "target=" << P.target << "," << endl << indent << "zoom=" << P.zoom; if(!orthographic) cout << "," << endl << indent << "angle=" << P.angle; if(P.viewportshift != pair(0.0,0.0)) cout << "," << endl << indent << "viewportshift=" << P.viewportshift*Zoom; if(!orthographic) cout << "," << endl << indent << "autoadjust=false"; cout << ");" << endl; } void keyboard(unsigned char key, int x, int y) { switch(key) { case 'h': home(); update(); break; case 'f': togglefitscreen(); break; case 'x': spinx(); break; case 'y': spiny(); break; case 'z': spinz(); break; case 's': idle(); break; case 'm': mode(); break; case 'e': Export(); break; case 'c': showCamera(); break; case '+': case '=': case '>': expand(); break; case '-': case '_': case '<': shrink(); break; case 'p': if(getSetting("reverse")) Animate=false; Setting("reverse")=Step=false; animate(); break; case 'r': if(!getSetting("reverse")) Animate=false; Setting("reverse")=true; Step=false; animate(); break; case ' ': Step=true; animate(); break; case 17: // Ctrl-q case 'q': if(!Format.empty()) Export(); quit(); break; } } void setosize() { oldWidth=(int) ceil(oWidth); oldHeight=(int) ceil(oHeight); } #endif // end of GUI-related functions void exportHandler(int=0) { #ifdef HAVE_LIBGLUT #ifndef HAVE_LIBOSMESA if(!Iconify) glutShowWindow(); #endif #endif readyAfterExport=true; Export(); #ifdef HAVE_LIBGLUT #ifndef HAVE_LIBOSMESA if(!Iconify) glutHideWindow(); glutDisplayFunc(nodisplay); #endif #endif } static bool glinitialize=true; projection camera(bool user) { if(glinitialize) return projection(); camp::Triple vCamera,vUp,vTarget; double cz=0.5*(zmin+zmax); double *Rotate=value_ptr(drotateMat); if(user) { for(int i=0; i < 3; ++i) { double sumCamera=0.0, sumTarget=0.0, sumUp=0.0; int i4=4*i; for(int j=0; j < 4; ++j) { int j4=4*j; double R0=Rotate[j4]; double R1=Rotate[j4+1]; double R2=Rotate[j4+2]; double R3=Rotate[j4+3]; double T4ij=T[i4+j]; sumCamera += T4ij*(R3-cx*R0-cy*R1-cz*R2); sumUp += T4ij*R1; sumTarget += T4ij*(R3-cx*R0-cy*R1); } vCamera[i]=sumCamera; vUp[i]=sumUp; vTarget[i]=sumTarget; } } else { for(int i=0; i < 3; ++i) { int i4=4*i; double R0=Rotate[i4]; double R1=Rotate[i4+1]; double R2=Rotate[i4+2]; double R3=Rotate[i4+3]; vCamera[i]=R3-cx*R0-cy*R1-cz*R2; vUp[i]=R1; vTarget[i]=R3-cx*R0-cy*R1; } } return projection(orthographic,vCamera,vUp,vTarget,Zoom, 2.0*atan(tan(0.5*Angle)/Zoom)/radians, pair(X/Width+Shift.getx(), Y/Height+Shift.gety())); } void init() { #ifdef HAVE_LIBGLUT mem::vector cmd; cmd.push_back(settings::argv0); if(!interact::interactive && Iconify) cmd.push_back("-iconic"); push_split(cmd,getSetting("glOptions")); char **argv=args(cmd,true); int argc=cmd.size(); #ifndef __APPLE__ glutInitContextProfile(GLUT_CORE_PROFILE); #endif fpu_trap(false); // Work around FE_INVALID glutInit(&argc,argv); fpu_trap(settings::trap()); screenWidth=glutGet(GLUT_SCREEN_WIDTH); screenHeight=glutGet(GLUT_SCREEN_HEIGHT); #endif } void init_osmesa() { #ifdef HAVE_LIBOSMESA // create context and buffer if(settings::verbose > 1) cout << "Allocating osmesa_buffer of size " << screenWidth << "x" << screenHeight << "x4x" << sizeof(GLubyte) << endl; osmesa_buffer=new unsigned char[screenWidth*screenHeight*4*sizeof(GLubyte)]; if(!osmesa_buffer) { cerr << "Cannot allocate image buffer." << endl; exit(-1); } ctx = OSMesaCreateContextExt(OSMESA_RGBA,16,0,0,NULL); if(!ctx) { cerr << "OSMesaCreateContext failed." << endl; exit(-1); } if(!OSMesaMakeCurrent(ctx,osmesa_buffer,GL_UNSIGNED_BYTE, screenWidth,screenHeight )) { cerr << "OSMesaMakeCurrent failed." << endl; exit(-1); } int z=0, s=0, a=0; glGetIntegerv(GL_DEPTH_BITS,&z); glGetIntegerv(GL_STENCIL_BITS,&s); glGetIntegerv(GL_ACCUM_RED_BITS,&a); if(settings::verbose > 1) cout << "Offscreen context settings: Depth=" << z << " Stencil=" << s << " Accum=" << a << endl; if(z <= 0) { cerr << "Error initializing offscreen context: Depth=" << z << endl; exit(-1); } #endif // HAVE_LIBOSMESA } #endif /* HAVE_GL */ // angle=0 means orthographic. void glrender(const string& prefix, const picture *pic, const string& format, double width, double height, double angle, double zoom, const triple& m, const triple& M, const pair& shift, const pair& margin, double *t, double *background, size_t nlightsin, triple *lights, double *diffuse, double *specular, bool view, int oldpid) { Iconify=getSetting("iconify"); if(zoom == 0.0) zoom=1.0; Prefix=prefix; Picture=pic; Format=format; nlights0=nlights=nlightsin; Lights=lights; Diffuse=diffuse; Specular=specular; View=view; Angle=angle*radians; Zoom0=zoom; Oldpid=oldpid; Shift=shift/zoom; Margin=margin; Background=background; Xmin=m.getx(); Xmax=M.getx(); Ymin=m.gety(); Ymax=M.gety(); zmin=m.getz(); zmax=M.getz(); orthographic=Angle == 0.0; H=orthographic ? 0.0 : -tan(0.5*Angle)*zmax; ignorezoom=false; Mode=0; Xfactor=Yfactor=1.0; pair maxtile=getSetting("maxtile"); maxTileWidth=(int) maxtile.getx(); maxTileHeight=(int) maxtile.gety(); if(maxTileWidth <= 0) maxTileWidth=1024; if(maxTileHeight <= 0) maxTileHeight=768; bool webgl=Format == "html"; #ifdef HAVE_GL #ifdef HAVE_PTHREAD #ifndef HAVE_LIBOSMESA static bool initializedView=false; #endif #endif #ifdef HAVE_LIBOSMESA if(!webgl) { screenWidth=maxTileWidth; screenHeight=maxTileHeight; static bool osmesa_initialized=false; if(!osmesa_initialized) { osmesa_initialized=true; fpu_trap(false); // Work around FE_INVALID. init_osmesa(); fpu_trap(settings::trap()); } } #else if(glinitialize) { if(!webgl) init(); Fitscreen=1; } #endif #endif static bool initialized=false; if(!(initialized && (interact::interactive || getSetting("animating")))) { antialias=getSetting("antialias") > 1; double expand; if(webgl) expand=1.0; else { expand=getSetting("render"); if(expand < 0) expand *= (Format.empty() || Format == "eps" || Format == "pdf") ? -2.0 : -1.0; if(antialias) expand *= 2.0; } oWidth=width; oHeight=height; Aspect=width/height; // Force a hard viewport limit to work around direct rendering bugs. // Alternatively, one can use -glOptions=-indirect (with a performance // penalty). pair maxViewport=getSetting("maxviewport"); int maxWidth=maxViewport.getx() > 0 ? (int) ceil(maxViewport.getx()) : screenWidth; int maxHeight=maxViewport.gety() > 0 ? (int) ceil(maxViewport.gety()) : screenHeight; if(maxWidth <= 0) maxWidth=max(maxHeight,2); if(maxHeight <= 0) maxHeight=max(maxWidth,2); if(screenWidth <= 0) screenWidth=maxWidth; else screenWidth=min(screenWidth,maxWidth); if(screenHeight <= 0) screenHeight=maxHeight; else screenHeight=min(screenHeight,maxHeight); fullWidth=(int) ceil(expand*width); fullHeight=(int) ceil(expand*height); if(webgl) { Width=fullWidth; Height=fullHeight; } else { Width=min(fullWidth,screenWidth); Height=min(fullHeight,screenHeight); if(Width > Height*Aspect) Width=min((int) (ceil(Height*Aspect)),screenWidth); else Height=min((int) (ceil(Width/Aspect)),screenHeight); } home(webgl); setProjection(); if(webgl) return; ArcballFactor=1+8.0*hypot(Margin.getx(),Margin.gety())/hypot(Width,Height); #ifdef HAVE_GL for(int i=0; i < 16; ++i) T[i]=t[i]; Aspect=((double) Width)/Height; if(maxTileWidth <= 0) maxTileWidth=screenWidth; if(maxTileHeight <= 0) maxTileHeight=screenHeight; #ifdef HAVE_LIBGLUT setosize(); #endif if(View && settings::verbose > 1) cout << "Rendering " << stripDir(prefix) << " as " << Width << "x" << Height << " image" << endl; #endif } #ifdef HAVE_GL bool havewindow=initialized && glthread; #ifndef HAVE_LIBOSMESA #ifdef HAVE_LIBGLUT unsigned int displaymode=GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH; #endif #ifdef __APPLE__ displaymode |= GLUT_3_2_CORE_PROFILE; #endif #endif camp::clearMaterialBuffer(); #ifndef HAVE_LIBOSMESA #ifdef HAVE_PTHREAD if(glthread && initializedView) { if(View) { #ifdef __MSDOS__ // Signals are unreliable in MSWindows glupdate=true; #else pthread_kill(mainthread,SIGUSR1); #endif } else readyAfterExport=queueExport=true; return; } #endif #ifdef HAVE_LIBGLUT if(View) { int x,y; if(havewindow) glutDestroyWindow(window); windowposition(x,y); glutInitWindowPosition(x,y); glutInitWindowSize(1,1); Int multisample=getSetting("multisample"); if(multisample <= 1) multisample=0; if(multisample) displaymode |= GLUT_MULTISAMPLE; glutInitDisplayMode(displaymode); int samples; #ifdef FREEGLUT #ifdef GLUT_INIT_MAJOR_VERSION while(true) { if(multisample > 0) glutSetOption(GLUT_MULTISAMPLE,multisample); #endif #endif string title=string(settings::PROGRAM)+": "+prefix; fpu_trap(false); // Work around FE_INVALID window=glutCreateWindow(title.c_str()); fpu_trap(settings::trap()); GLint samplebuf[1]; glGetIntegerv(GL_SAMPLES,samplebuf); samples=samplebuf[0]; #ifdef FREEGLUT #ifdef GLUT_INIT_MAJOR_VERSION if(samples < multisample) { multisample=floorpow2(multisample-1); if(multisample > 1) { glutReshapeWindow(1,1); glutDisplayFunc(destroywindow); glutShowWindow(); glutMainLoopEvent(); continue; } } break; } #endif #endif if(settings::verbose > 1 && samples > 1) cout << "Multisampling enabled with sample width " << samples << endl; glutDisplayFunc(display); glutShowWindow(); } else if(!havewindow) { glutInitWindowSize(maxTileWidth,maxTileHeight); glutInitDisplayMode(displaymode); fpu_trap(false); // Work around FE_INVALID window=glutCreateWindow(""); fpu_trap(settings::trap()); glutHideWindow(); } #endif // HAVE_LIBGLUT #endif // HAVE_LIBOSMESA initialized=true; GLint val; glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE,&val); Maxmaterials=val/sizeof(Material); if(nmaterials > Maxmaterials) nmaterials=Maxmaterials; if(glinitialize) { glinitialize=false; int result = glewInit(); if (result != GLEW_OK) { cerr << "GLEW initialization error." << endl; exit(-1); } initShaders(); setBuffers(); } glClearColor(Background[0],Background[1],Background[2],Background[3]); #ifdef HAVE_LIBGLUT #ifndef HAVE_LIBOSMESA Animate=getSetting("autoplay") && glthread; if(View) { if(!getSetting("fitscreen")) Fitscreen=0; firstFit=true; fitscreen(); setosize(); } #endif #endif glEnable(GL_BLEND); glEnable(GL_DEPTH_TEST); glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); mode(); #ifdef HAVE_LIBOSMESA View=false; #endif if(View) { #ifdef HAVE_LIBGLUT #ifdef HAVE_PTHREAD #ifndef HAVE_LIBOSMESA initializedView=true; #endif #endif glutReshapeFunc(reshape); glutKeyboardFunc(keyboard); glutMouseFunc(mouse); glutDisplayFunc(display); #ifdef __MSDOS__ if(glthread && interact::interactive) poll(0); #endif glutMainLoop(); #endif // HAVE_LIBGLUT } else { if(glthread) { if(havewindow) { readyAfterExport=true; #ifdef HAVE_PTHREAD pthread_kill(mainthread,SIGUSR1); #endif } else { initialized=true; readyAfterExport=true; Signal(SIGUSR1,exportHandler); exportHandler(); } } else { exportHandler(); quit(); } } #endif /* HAVE_GL */ } } // namespace gl #endif #ifdef HAVE_GL namespace camp { string getLightIndex(size_t const& index, string const& fieldName) { ostringstream buf; buf << "lights[" << index << "]." << fieldName; return Strdup(buf.str()); } string getCenterIndex(size_t const& index) { ostringstream buf; buf << "Centers[" << index << "]"; return Strdup(buf.str()); } template void registerBuffer(const std::vector& buffervector, GLuint& bufferIndex, bool copy, GLenum type=GL_ARRAY_BUFFER) { if(!buffervector.empty()) { if(bufferIndex == 0) { glGenBuffers(1,&bufferIndex); copy=true; } glBindBuffer(type,bufferIndex); if(copy) glBufferData(type,buffervector.size()*sizeof(T), buffervector.data(),GL_STATIC_DRAW); } } void setUniforms(vertexBuffer& data, GLint shader) { bool normal=shader != pixelShader; if(shader != gl::lastshader) { glUseProgram(shader); gl::lastshader=shader; glUniform1i(glGetUniformLocation(shader,"nlights"),gl::nlights); for(size_t i=0; i < gl::nlights; ++i) { triple Lighti=gl::Lights[i]; size_t i4=4*i; glUniform3f(glGetUniformLocation(shader, getLightIndex(i,"direction").c_str()), (GLfloat) Lighti.getx(),(GLfloat) Lighti.gety(), (GLfloat) Lighti.getz()); glUniform3f(glGetUniformLocation(shader, getLightIndex(i,"color").c_str()), (GLfloat) gl::Diffuse[i4],(GLfloat) gl::Diffuse[i4+1], (GLfloat) gl::Diffuse[i4+2]); } #if HAVE_LIBOPENIMAGEIO // textures if (settings::getSetting("envmap")) { glActiveTexture(GL_TEXTURE1); glBindBuffer(GL_TEXTURE_2D, gl::envMapBuf); glUniform1i(glGetUniformLocation(shader, "environmentMap"), 1); glActiveTexture(GL_TEXTURE0); } #endif } GLuint binding=0; GLint blockindex=glGetUniformBlockIndex(shader,"MaterialBuffer"); glUniformBlockBinding(shader,blockindex,binding); bool copy=gl::remesh || data.partial || !data.rendered; registerBuffer(data.materials,data.materialsBuffer,copy,GL_UNIFORM_BUFFER); glBindBufferBase(GL_UNIFORM_BUFFER,binding,data.materialsBuffer); glUniformMatrix4fv(glGetUniformLocation(shader,"projViewMat"),1,GL_FALSE, value_ptr(gl::projViewMat)); glUniformMatrix4fv(glGetUniformLocation(shader,"viewMat"),1,GL_FALSE, value_ptr(gl::viewMat)); if(normal) glUniformMatrix3fv(glGetUniformLocation(shader,"normMat"),1,GL_FALSE, value_ptr(gl::normMat)); } void drawBuffer(vertexBuffer& data, GLint shader) { if(data.indices.empty()) return; bool normal=shader != pixelShader; bool color=shader == colorShader || shader == transparentShader; const size_t size=sizeof(GLfloat); const size_t intsize=sizeof(GLint); const size_t bytestride=color ? sizeof(VertexData) : (normal ? sizeof(vertexData) : sizeof(vertexData0)); bool copy=gl::remesh || data.partial || !data.rendered; if(color) registerBuffer(data.Vertices,data.VerticesBuffer,copy); else if(normal) registerBuffer(data.vertices,data.verticesBuffer,copy); else registerBuffer(data.vertices0,data.vertices0Buffer,copy); registerBuffer(data.indices,data.indicesBuffer,copy,GL_ELEMENT_ARRAY_BUFFER); camp::setUniforms(data,shader); data.rendered=true; glVertexAttribPointer(positionAttrib,3,GL_FLOAT,GL_FALSE,bytestride, (void *) 0); glEnableVertexAttribArray(positionAttrib); if(normal && gl::Nlights > 0) { glVertexAttribPointer(normalAttrib,3,GL_FLOAT,GL_FALSE,bytestride, (void *) (3*size)); glEnableVertexAttribArray(normalAttrib); } else if(!normal) { glVertexAttribPointer(widthAttrib,1,GL_FLOAT,GL_FALSE,bytestride, (void *) (3*size)); glEnableVertexAttribArray(widthAttrib); } glVertexAttribIPointer(materialAttrib,1,GL_INT,bytestride, (void *) ((normal ? 6 : 4)*size)); glEnableVertexAttribArray(materialAttrib); if(color) { glVertexAttribPointer(colorAttrib,4,GL_UNSIGNED_BYTE,GL_TRUE,bytestride, (void *) (6*size+intsize)); glEnableVertexAttribArray(colorAttrib); } fpu_trap(false); // Work around FE_INVALID glDrawElements(data.type,data.indices.size(),GL_UNSIGNED_INT,(void *) 0); fpu_trap(settings::trap()); glDisableVertexAttribArray(positionAttrib); if(normal && gl::Nlights > 0) glDisableVertexAttribArray(normalAttrib); if(!normal) glDisableVertexAttribArray(widthAttrib); glDisableVertexAttribArray(materialAttrib); if(color) glDisableVertexAttribArray(colorAttrib); glBindBuffer(GL_UNIFORM_BUFFER,0); glBindBuffer(GL_ARRAY_BUFFER,0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0); } void drawMaterial0() { drawBuffer(material0Data,pixelShader); material0Data.clear(); } void drawMaterial1() { drawBuffer(material1Data,materialShader); material1Data.clear(); } void drawMaterial() { drawBuffer(materialData,materialShader); materialData.clear(); } void drawColor() { drawBuffer(colorData,colorShader); colorData.clear(); } void drawTriangle() { drawBuffer(triangleData,transparentShader); triangleData.rendered=false; // Force copying of sorted triangles to GPU. triangleData.clear(); } void drawTransparent() { sortTriangles(); glDepthMask(GL_FALSE); // Enable transparency drawBuffer(transparentData,transparentShader); transparentData.rendered=false; // Force copying of sorted triangles to GPU. glDepthMask(GL_TRUE); // Disable transparency transparentData.clear(); } void drawBuffers() { drawMaterial0(); drawMaterial1(); drawMaterial(); drawColor(); drawTriangle(); drawTransparent(); } void clearMaterialBuffer() { material.clear(); material.reserve(nmaterials); materialMap.clear(); materialIndex=0; material0Data.partial=false; material1Data.partial=false; materialData.partial=false; colorData.partial=false; triangleData.partial=false; transparentData.partial=false; } void setMaterial(vertexBuffer& data, draw_t *draw) { if(materialIndex >= data.materialTable.size() || data.materialTable[materialIndex] == -1) { if(data.materials.size() >= Maxmaterials) { data.partial=true; (*draw)(); } size_t size0=data.materialTable.size(); data.materialTable.resize(materialIndex+1); for(size_t i=size0; i < materialIndex; ++i) data.materialTable[i]=-1; data.materialTable[materialIndex]=data.materials.size(); data.materials.push_back(material[materialIndex]); } materialIndex=data.materialTable[materialIndex]; } } #endif /* HAVE_GL */