#!/usr/local/bin/perl # # nodched.pl : from cube to anticube (stella octangula) # (c) 2013 jl_morel@bribes.org - http://http://bribes.org/perl use strict; use warnings; use OpenGL qw/ :all /; # ============ Some math routines #------ Returns the cross product of 2 vectors sub CrossProduct { return ( $_[1] * $_[5] - $_[2] * $_[4], $_[3] * $_[2] - $_[0] * $_[5], $_[0] * $_[4] - $_[1] * $_[3] ); } #------ Returns the length of a vector sub GetVectorLength { return sqrt( $_[0] * $_[0] + $_[1] * $_[1] + $_[2] * $_[2] ); } #------ Returns the vector scaled by the last parameter sub ScaleVector { return ( $_[0] * $_[3], $_[1] * $_[3], $_[2] * $_[3] ); } #------ Returns a normalized vector (length = 1) sub NormalizeVector { return ( 0, 0, 0 ) if ( my $norm = GetVectorLength(@_) ) == 0; return ScaleVector( @_, 1 / $norm ); } #------ Returns the unit normal vector of a triangle specified by the three # points P1, P2, and P3. sub FindUnitNormal { return NormalizeVector( CrossProduct( $_[0] - $_[3], $_[1] - $_[4], $_[2] - $_[5], # P1-P2 $_[3] - $_[6], $_[4] - $_[7], $_[5] - $_[8] # P2-P3 ) ); } #------ Base vectors my @i = ( 1, 0, 0 ); my @j = ( 0, 1, 0 ); my @k = ( 0, 0, 1 ); #============ End math routines my $spin = 0; # rotation angle my $k = 0; # notch depth 0<$k<1 my $delta = 0.005; # increment for $k my @light0_position = ( 2, 8, 2, 0 ); my @mat_amb_diff_color = ( 1, 0.7, 0.5, 0 ); my @light_diffuse = ( 1, 1, 1, 1 ); my @light_ambient = ( 0.15, 0.15, 0.15, 0.15 ); #------ Draw only a diedre (1/12th of the cube) sub DrawDiedre { my $h = 1 - $k; my @A = ( -1, 1, 1 ); my @B = ( 1, 1, 1 ); my @C = ( 0, 1, 0 ); my @D = ( 0, 0, 1 ); my @E = ( -$k, 1, 1 ); my @F = ( $k, 1, 1 ); my @G = ( 0, 1, $h ); my @H = ( 0, $h, 1 ); glBegin(GL_QUADS); # The upper polygon is concave, glNormal3d(@j); # we draw it with two convex quads. glVertex3d(@A); # We don't want to use a tessallator! glVertex3d(@E); glVertex3d(@G); glVertex3d(@C); glVertex3d(@F); glVertex3d(@B); glVertex3d(@C); glVertex3d(@G); glNormal3d(@k); # idem for the front polygon glVertex3d(@A); glVertex3d(@D); glVertex3d(@H); glVertex3d(@E); glVertex3d(@B); glVertex3d(@F); glVertex3d(@H); glVertex3d(@D); glEnd(); # the two triangles of the notch glBegin(GL_TRIANGLES); glNormal3d( FindUnitNormal( @E, @H, @G ) ); glVertex3d(@E); glVertex3d(@H); glVertex3d(@G); glNormal3d( FindUnitNormal( @H, @F, @G ) ); glVertex3d(@H); glVertex3d(@F); glVertex3d(@G); glEnd(); } #------ Draw the notched cube sub display { glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); glLightfv_p( GL_LIGHT0, GL_POSITION, @light0_position ); glLoadIdentity(); gluLookAt( 2, 4, 10, 0, 0, 0, 0, 1, 0 ); glPushMatrix(); glScalef( 2, 2, 2 ); glRotatef( $spin, @j ); DrawDiedre(); # The cube is built with 11 rotations glRotatef( 90, @j ); DrawDiedre(); glRotatef( 90, @j ); DrawDiedre(); glRotatef( 90, @j ); DrawDiedre(); glRotatef( 90, @i ); DrawDiedre(); glRotatef( 90, @j ); DrawDiedre(); glRotatef( 90, @k ); DrawDiedre(); glRotatef( 90, @k ); DrawDiedre(); glRotatef( 90, @j ); DrawDiedre(); glRotatef( 90, @j ); DrawDiedre(); glRotatef( 90, @i ); DrawDiedre(); glRotatef( -90, @j ); DrawDiedre(); glPopMatrix(); glutSwapBuffers(); # debug code # if ( ( my $e = glGetError() ) != GL_NO_ERROR ) { # print "error : ", gluErrorString($e), "\n"; # } } #------ Initialization routine sub init { glClearColor( 0, 0, 0, 0 ); # Black background glShadeModel(GL_SMOOTH); # Smooth shading glEnable(GL_MULTISAMPLE); # Enable multisample antialiasing glEnable(GL_DEPTH_TEST); # Enable hidden surface removal glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); # Light and material glLightfv_p( GL_LIGHT0, GL_DIFFUSE, @light_diffuse ); glLightfv_p( GL_LIGHT0, GL_AMBIENT, @light_ambient ); glMaterialfv_p( GL_FRONT, GL_AMBIENT_AND_DIFFUSE, @mat_amb_diff_color ); } #------ GLUT Callback called when the window is resized sub reshape { my ( $w, $h ) = @_; glViewport( 0, 0, $w, $h ); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective( 45, $h ? $w / $h : 0, 1, 20 ); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } #------ Routine for rotating the cube my $WaitUntil = 0; sub spinDisplay { my $TimeNow = glutGet(GLUT_ELAPSED_TIME); if ( $TimeNow >= $WaitUntil ) { $spin += 1; $delta = -$delta if $k > 1 or $k < 0; $k += $delta; $spin = $spin - 360 if ( $spin > 360 ); glutPostRedisplay(); $WaitUntil = $TimeNow + 1000 / 25; # 25 frames/s } } #------ GLUT callback for the mouse sub mouse { my ( $button, $state, $x, $y ) = @_; if ( $button == GLUT_LEFT_BUTTON ) { glutIdleFunc( \&spinDisplay ) if ( $state == GLUT_DOWN ); } elsif ( $button == GLUT_RIGHT_BUTTON ) { glutIdleFunc(undef) if ( $state == GLUT_DOWN ); } } #------ Main program glutInit(); glutInitDisplayMode( GLUT_DOUBLE # Double buffering | GLUT_RGB # RGB color mode | GLUT_DEPTH # Hidden surface removal | GLUT_MULTISAMPLE # Multisample antialiasing ); glutInitWindowSize( 300, 300 ); glutCreateWindow("Notched cube"); init(); glutDisplayFunc( \&display ); glutReshapeFunc( \&reshape ); glutMouseFunc( \&mouse ); glutIdleFunc( \&spinDisplay ); glutMainLoop();