Last active
May 19, 2024 13:50
-
-
Save jtsiomb/43b5da0515d4786a80a861369d35097c to your computer and use it in GitHub Desktop.
Example of rendering cross-sections of closed geometry with OpenGL
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Example of rendering cross-sections of closed non-self-intersecting geometry | |
* with the stencil buffer. See draw_cross_section for details. | |
* | |
* Controls: | |
* - rotate object by dragging with the left mouse button | |
* - move cross-section plane back and forth by dragging up/down with the right | |
* mouse button | |
* | |
* Compile with: cc -o xsection xsection.c -lGL -lGLU -lglut | |
* | |
* - Nuclear / Mindlapse <nuclear@member.fsf.org> | |
*/ | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <assert.h> | |
#include <GL/glut.h> | |
void disp(void); | |
void draw_regular(void); | |
void draw_cross_section(void); | |
void draw_object(void); | |
void reshape(int x, int y); | |
void keyb(unsigned char key, int x, int y); | |
void mouse(int bn, int st, int x, int y); | |
void motion(int x, int y); | |
int win_width, win_height; | |
float theta, phi, clipz; | |
int main(int argc, char **argv) | |
{ | |
glutInit(&argc, argv); | |
glutInitWindowSize(1024, 600); | |
glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_STENCIL | GLUT_DOUBLE); | |
glutCreateWindow("GL cross-section hack"); | |
glutDisplayFunc(disp); | |
glutReshapeFunc(reshape); | |
glutKeyboardFunc(keyb); | |
glutMouseFunc(mouse); | |
glutMotionFunc(motion); | |
/* we're going to need backface culling for this */ | |
glEnable(GL_CULL_FACE); | |
glutMainLoop(); | |
return 0; | |
} | |
void disp(void) | |
{ | |
glViewport(0, 0, win_width, win_height); | |
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | |
/* ----- render the regular view on the left ----- */ | |
glViewport(0, 0, win_width / 2, win_height); | |
/* set up perspective projection matrix */ | |
glMatrixMode(GL_PROJECTION); | |
glLoadIdentity(); | |
gluPerspective(45.0, win_width * 0.5 / win_height, 0.5, 50.0); | |
draw_regular(); | |
/* ----- render the cross-section on the right ----- */ | |
glViewport(win_width / 2, 0, win_width / 2, win_height); | |
/* set up an orthographic projection matrix */ | |
glMatrixMode(GL_PROJECTION); | |
glLoadIdentity(); | |
glOrtho(-2, 2, -2, 2, -10, 10); | |
draw_cross_section(); | |
/* draw a separator, just to make it clear that we have two viewports */ | |
glViewport(0, 0, win_width, win_height); | |
glMatrixMode(GL_PROJECTION); | |
glLoadIdentity(); | |
glOrtho(0, win_width, 0, win_height, -1, 1); | |
glMatrixMode(GL_MODELVIEW); | |
glLoadIdentity(); | |
glColor3f(0.7, 0.7, 0.7); | |
glRectf(win_width / 2 - 2, 0, win_width / 2 + 2, win_height); | |
glutSwapBuffers(); | |
assert(glGetError() == GL_NO_ERROR); | |
} | |
void draw_regular(void) | |
{ | |
int i; | |
/* set up a view matrix */ | |
glMatrixMode(GL_MODELVIEW); | |
glLoadIdentity(); | |
glTranslatef(0, 0, -6); | |
glRotatef(20, 1, 0, 0); | |
glPushAttrib(GL_ENABLE_BIT | GL_DEPTH_BUFFER_BIT | GL_LINE_BIT); | |
glEnable(GL_DEPTH_TEST); | |
glEnable(GL_LIGHTING); | |
glEnable(GL_LIGHT0); | |
draw_object(); | |
/* also draw a quad to visualize the cross-section plane */ | |
glEnable(GL_BLEND); | |
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); | |
glDisable(GL_LIGHTING); | |
glDepthMask(0); | |
glLineWidth(2.0); | |
for(i=0; i<2; i++) { | |
glBegin(i ? GL_QUADS : GL_LINE_LOOP); | |
glColor4f(1, 0, 0, i ? 0.25 : 0.5); | |
glVertex3f(-1.8, -1.8, clipz); | |
glVertex3f(1.8, -1.8, clipz); | |
glVertex3f(1.8, 1.8, clipz); | |
glVertex3f(-1.8, 1.8, clipz); | |
glEnd(); | |
} | |
glPopAttrib(); | |
} | |
void draw_cross_section(void) | |
{ | |
double plane[] = {0, 0, -1, 0}; | |
/* just identity for the view matrix */ | |
glMatrixMode(GL_MODELVIEW); | |
glLoadIdentity(); | |
glPushAttrib(GL_ENABLE_BIT); | |
glEnable(GL_STENCIL_TEST); /* enable stencil testing */ | |
/* initially we don't care about the stencil buffer, we always want to draw */ | |
glStencilFunc(GL_ALWAYS, 0, 0); | |
/* set up a clip plane to cut everything from the plane Z to the user */ | |
plane[3] = clipz; | |
glClipPlane(GL_CLIP_PLANE0, plane); | |
glEnable(GL_CLIP_PLANE0); | |
glColorMask(0, 0, 0, 1); /* disable color writing, we only want to affect the stencil buffer */ | |
/* render back faces and increment stencil where we draw */ | |
glStencilOp(GL_INCR, GL_INCR, GL_INCR); | |
glCullFace(GL_FRONT); | |
draw_object(); | |
/* render front faces and decrement stencil where we draw */ | |
glStencilOp(GL_DECR, GL_DECR, GL_DECR); | |
glCullFace(GL_BACK); | |
draw_object(); | |
/* now everywhere outside the cross-section the stencil is 0, and inside the | |
* cross-section the stencil is 1 | |
*/ | |
glColorMask(1, 1, 1, 1); /* re-enable color writing */ | |
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); /* don't modify the stencil buffer further */ | |
glStencilFunc(GL_LEQUAL, 1, 0xffffffff); /* test passes only where stencil >= 1 */ | |
/* just draw a fullscreen quad, will be drawn only inside the cross-section | |
* (stencil == 1) area | |
*/ | |
glLoadIdentity(); | |
glMatrixMode(GL_PROJECTION); | |
glLoadIdentity(); | |
glDisable(GL_CLIP_PLANE0); /* disable the clip plane, we don't want the quad to be clipped */ | |
glBegin(GL_QUADS); | |
glColor3f(1, 0.3, 0.2); /* color of the cross-section */ | |
glVertex2f(-1, -1); | |
glVertex2f(1, -1); | |
glVertex2f(1, 1); | |
glVertex2f(-1, 1); | |
glEnd(); | |
glPopAttrib(); | |
} | |
void draw_object(void) | |
{ | |
glPushMatrix(); | |
glRotatef(phi, 1, 0, 0); | |
glRotatef(theta, 0, 1, 0); | |
glutSolidTorus(0.25, 1.0, 8, 28); | |
glutSolidSphere(0.5, 16, 8); | |
glPopMatrix(); | |
} | |
void reshape(int x, int y) | |
{ | |
win_width = x; | |
win_height = y; | |
} | |
void keyb(unsigned char key, int x, int y) | |
{ | |
switch(key) { | |
case 27: | |
exit(0); | |
} | |
} | |
int bnstate[16]; | |
int prev_x, prev_y; | |
void mouse(int bn, int st, int x, int y) | |
{ | |
int bidx = bn - GLUT_LEFT_BUTTON; | |
bnstate[bidx] = st == GLUT_DOWN ? 1 : 0; | |
prev_x = x; | |
prev_y = y; | |
} | |
void motion(int x, int y) | |
{ | |
int dx = x - prev_x; | |
int dy = y - prev_y; | |
prev_x = x; | |
prev_y = y; | |
if(!dx && !dy) return; | |
if(bnstate[0]) { | |
theta += dx * 0.5; | |
phi += dy * 0.5; | |
if(phi < -90) phi = -90; | |
if(phi > 90) phi = 90; | |
glutPostRedisplay(); | |
} | |
if(bnstate[2]) { | |
clipz += dy * 0.01; | |
if(clipz < -2) clipz = -2; | |
if(clipz > 2) clipz = 2; | |
glutPostRedisplay(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment