/// LSU EE 4702-1 (Fall 2009), GPU Programming /// /// Homework 2 SOLUTION /// Solution Overview /// Seam Geometry // Seam shape is approximately rectangular, see the illustration below // in which the T's show the tiles, and the S's are the seams. // There are two sets of seams, axial seams, which are straight and // are parallel to the z axis, and the circumferential seams, which // are semi-circular. For axial seams the z axis is normal to the // screen (passes through it), for circumferential seams the z axis // goes from left to right (or right to left). // Each seam is divided into five surfaces, three that are planar, // S1, S2, and S4; and two that are lines, S3 and S6. // S2 // S3 ----\ | /--- S6 (the corner) // SSSSSSSSSSSSSS _ // S S | // S1 -> S S <- S4 height // S S | // TTTTTTTTT TTTTTTTTT <- tile _ // |<- width -->| // The seam shape is determined by two parameters, opt_seam_height // and opt_seam_width. // Seam coordinates are computed in routine platform_update. Two sets // of coordinates are computed, one for rendering and one for // collision detection. The coordinates for rendering are kept in // object platform_seam_coords and object platform_seam_norms, the // coordinates for collision detection are kept in array seam_info. /// Seam Rendering // Seams are rendered using quads (four-vertex primitives) by the code // in routine World::render. // Both circumferential and axial seams are rendered in sections // matching the tile size. Rendering in sections was necessary for the // circumferential seams because they are curved. Each axial seam // could have been rendered with just three long quads, but was // rendered using multiple quads for better lighting // appearance. (Lighting calculations are only performed on primitive // vertices, which would be far apart in axial seams rendered with // just three quads.) /// Collision Detection and Resolution // The following are the steps for collision detection and // resolution. See the routine time_step_cpu_ball for details. // o Determine closest axial and circumferential seam. // o Determine where ball is relative to each seam (left, right, or above). // o Based on relative position determine which surface can // be in contact (S1, S2, S3, S4, or S6 ). // o Find closest point on surface to ball. // o If point is further than ball radius, no collision. // o Adjust velocity to account for bounce. // o Move ball to surface. // The ball can hit each seam in one of five ways, corresponding // to the surfaces shown in the illustration above. There are two // kinds of collision, to a plane or to an edge (line). /// Problem 1 // /// Show seams graphically. // Show graphically the seam between tiles that was just in the // physical model of Homework 1. The graphical representation should // make it easy to see when the ball hits a seam. It would also be // nice if it looked like some kind of seam, say grout or some kind of // separator. // [ ] Pre-compute an array of coordinates for the seam in platform_update. // [ ] Choose colors and other styling appropriate for the seam. // [ ] Code should be clean and fast. /// Problem 2 // /// Improve Homework 1 Physics // Re-solve homework 1 so that the ball bounces as it hits the // seam and does so in a realistic manor. // [ ] Don't compute the distance from the ball to axis more than once. // [ ] Reflect velocity of ball based on point of impact. /// Problem 3 // /// Show some indication of ball impact on seam. // Change the appearance of the seam to indicate location ball impact. // The change in appearance should be at the correct location // and should be interesting to look at. // [ ] Change in appearance with impact. /// Optional // [ ] Base change in appearance on speed and angle of ball impact. // [ ] Have change fade with time. /// What Code Does // Simulates balls bouncing over a curved platform. The platform // consists of tiles and the shape is a half cylinder. /// Keyboard Commands // /// Object (Eye, Light, Ball) Location or Push // Arrows, Page Up, Page Down // Will move object or push ball, depending on mode: // 'e': Move eye. // 'l': Move light. // 'b': Move a ball. (Change position but not velocity.) // 'B': Push a ball. (Add velocity.) // /// Eye Direction // Home, End, Delete, Insert // Turn the eye direction. // Home should rotate eye direction up, End should rotate eye // down, Delete should rotate eye left, Insert should rotate eye // right. The eye direction vector is displayed in the upper left. /// Simulation Options // (Also see variables below.) // // 'p' Pause simulation. (Press again to resume.) // 'x' Release another ball. (Try holding it in.) // 'n' Toggle visibility of platform normals. // 's' Stop balls. // 'g' Turn gravity on and off. // 'F11' Change size of text. // 'F12' Write screenshot to file. /// Variables // Selected program variables can be modified using the keyboard. // Use "Tab" to cycle through the variable to be modified, the // name of the variable is displayed next to "VAR" on the bottom // line of green text. // 'Tab' Cycle to next variable. // '`' Cycle to previous variable. // '+' Increase variable value. // '-' Decrease variable value. // // VAR Light Intensity - The light intensity. // VAR Gravity - Gravitational acceleration. (Turn on/off using 'g'.) // VAR Ball Radius #define GL_GLEXT_PROTOTYPES #define GLX_GLXEXT_PROTOTYPES #include <GL/gl.h> #include <GL/glext.h> #include <GL/glx.h> #include <GL/glxext.h> #include <GL/glu.h> #include <GL/freeglut.h> #include <gp/util.h> #include <gp/glextfuncs.h> #include <gp/coord.h> #include <gp/shader.h> #include <gp/pstring.h> #include <gp/misc.h> #include <gp/gl-buffer.h> #include <gp/texture-util.h> #include "shapes.h" /// /// Main Data Structures /// // // class World: All data about scene. class World; /// SOLUTION -- Seam Geometry Data Structures // // Seam shape is approximately rectangular. // Structure describing surface of seam. Each seam has five useful // surfaces: three planes and two lines (where two planes meet). // struct Seam_Surface { // If true, surface is a plane, otherwise it's a line. bool plane; // A point on the surface. pCoor point; // For planes, the surface normal. // For lines, a vector in the direction of the line. pVect norm; }; // Structure describing a seam. // struct Seam { Seam_Surface surface[8]; }; // Structure describing place where ball collides with seam. // struct Collision_Marker { pCoor center; // Location that ball strikes. pVect bounce_dv; // Change in velocity with collision. float r; // Radius of collision marker. double t; // Time of collision. }; // Object Holding Ball State // class Ball { public: Ball(); pCoor position; pVect velocity; bool contact; // If true, in contact with platform. int row_num, col_num; // Refers to tile. pVect axis; // Direction of ball's axis. double angle; // Angle around axis. void push(pVect amt); void translate(pVect amt); void stop(); void freeze(); }; class World { public: World(pOpenGL_Helper &fb):ogl_helper(fb){init();} void init(); static void render_w(void *moi){ ((World*)moi)->render(); } void render(); void cb_keyboard(); void modelview_update(); pOpenGL_Helper& ogl_helper; pVariable_Control variable_control; pFrame_Timer frame_timer; double world_time; float opt_gravity_accel; // Value chosen by user. pVect gravity_accel; // Set to zero when opt_gravity is false; bool opt_gravity; float opt_ball_radius; // Tiled platform for ball. // float platform_xmin, platform_xmax, platform_zmin, platform_zmax; float platform_xmid, platform_xrad, platform_xrad_inv; float delta_theta_inv, tile_size_inv; float platform_pi_xwidth_inv; pBuffer_Object<pVect> platform_tile_norms; pBuffer_Object<pVect> platform_tile_coords; pBuffer_Object<float> platform_tex_coords; pBuffer_Object<pVect> platform_seam_coords; pBuffer_Object<pVect> platform_seam_norms; GLuint texid_syl; GLuint texid_emacs; bool opt_normals_visible; void platform_update(); bool platform_collision_possible(pCoor pos); pCoor light_location; float opt_light_intensity; enum { MI_Eye, MI_Light, MI_Ball, MI_Ball_V, MI_COUNT } opt_move_item; bool opt_pause; pCoor eye_location; pVect eye_direction; pMatrix modelview; pMatrix modelview_shadow; double ball_countdown; void ball_init(); void balls_render(); void balls_stop(); void time_step_cpu(double); void time_step_cpu_ball(double delta_t, Ball* ball); PStack<Ball*> balls; Sphere sphere; Cone cone; // Used to show platform normals. Seam *seam_info; PQueue<Collision_Marker> collision_markers; double marker_lifetime; float marker_lifetime_inv; int row_cnt; float opt_seam_height; float opt_seam_width; float opt_energy_loss; float bounce_factor; }; void World::init() { seam_info = NULL; frame_timer.work_unit_set("Steps / s"); world_time = 0; opt_gravity_accel = 9.8; opt_gravity = true; gravity_accel = pVect(0,-opt_gravity_accel,0); opt_normals_visible = false; eye_location = pCoor(17.9,-2,117.2); eye_direction = pVect(-0.15,-0.06,-0.96); platform_xmin = -40; platform_xmax = 40; platform_zmin = -40; platform_zmax = 40; texid_syl = pBuild_Texture_File("gpup.png",false,255); texid_emacs = pBuild_Texture_File("mult.png", false,-1); opt_light_intensity = 100.2; light_location = pCoor(28.2,-2.8,-14.3); opt_ball_radius = 2; // This was added in solution to make the ball less lively. // It wasn't necessary for the solution. opt_energy_loss = 0.5; bounce_factor = 2 - opt_energy_loss; /// SOLUTION - Initialize variables. // opt_seam_height = 1.5; opt_seam_width = 0.2; // Number of seconds that collision marker visible. marker_lifetime = 10; marker_lifetime_inv = 1/marker_lifetime; variable_control.insert(opt_seam_height,"Seam Height"); variable_control.insert(opt_seam_width,"Seam Width"); variable_control.insert(opt_gravity_accel,"Gravity"); variable_control.insert(opt_light_intensity,"Light Intensity"); variable_control.insert(opt_ball_radius,"Ball Radius"); variable_control.insert(opt_energy_loss,"Energy Loss"); opt_move_item = MI_Eye; opt_pause = false; ball_countdown = 1e10; balls += new Ball(); sphere.init(40); // Argument indicates amount of detail. sphere.radius = opt_ball_radius; platform_update(); modelview_update(); } void World::platform_update() { const float tile_count = 19; const float ep = 1.00001; const float platform_delta_x = platform_xmax - platform_xmin; const float zdelta = ( platform_zmax - platform_zmin ) / tile_count * ep; const float trmin = 0.05; const float trmax = 0.7; const float tsmin = 0; const float tsmax = 0.4; PStack<pVect> p_tile_coords; PStack<pVect> p1_tile_coords; PStack<pVect> p_tile_norms; PStack<pVect> p1_tile_norms; PStack<float> p_tex_coords; bool even = true; platform_pi_xwidth_inv = M_PI / platform_delta_x; const double delta_theta = M_PI / tile_count; delta_theta_inv = 1.0 / delta_theta; tile_size_inv = 1 / zdelta; platform_xmid = 0.5 * ( platform_xmax + platform_xmin ); platform_xrad = 0.5 * platform_delta_x; platform_xrad_inv = 1 / platform_xrad; row_cnt = int(tile_count); const float platform_srad = platform_xrad - opt_seam_height; const double seam_delta_theta = 0.5 * opt_seam_width / platform_srad; pVect z_depth(0,0,platform_zmax-platform_zmin); /// SOLUTION // // Seam coordinates and normals. // PStack<pVect> s_coords; PStack<pVect> s_norms; // // Information about seam surfaces. // seam_info = new Seam[ row_cnt * ( row_cnt + 1 ) ]; for ( int i = 0; i < tile_count; i++ ) { const double theta0 = i * delta_theta; const double theta1 = theta0 + delta_theta; const float x0 = platform_xmid - platform_xrad * cos(theta0); const float x1 = platform_xmid - platform_xrad * cos(theta1); const float y0 = -0.01 - platform_xrad * sin(theta0); const float y1 = -0.01 - platform_xrad * sin(theta1); pVect norm0( cos(theta0), sin(theta0), 0); pVect norm1( cos(theta1), sin(theta1), 0); /// SOLUTION - Find Seam Coordinates // const double s_th_a = theta0 - seam_delta_theta; const double s_th_b = theta0 + seam_delta_theta; const float x0s = platform_xmid - platform_srad * cos(theta0); const float x1s = platform_xmid - platform_srad * cos(theta1); const float y0s = -0.01 - platform_srad * sin(theta0); const float y1s = -0.01 - platform_srad * sin(theta1); pCoor p0(platform_xmid - platform_xrad * cos(s_th_a), -0.01 - platform_xrad * sin(s_th_a), platform_zmin); pCoor p1(platform_xmid - platform_srad * cos(s_th_a), -0.01 - platform_srad * sin(s_th_a), platform_zmin); pCoor p2(platform_xmid - platform_srad * cos(s_th_b), -0.01 - platform_srad * sin(s_th_b), platform_zmin); pCoor p3(platform_xmid - platform_xrad * cos(s_th_b), -0.01 - platform_xrad * sin(s_th_b), platform_zmin); pNorm snorm1 = cross(p0,p1,p0 + z_depth); pVect snorm2 = norm0; pNorm snorm4 = cross(p2,p3,p2 + z_depth); #define SQ(P,Q,N) \ s_coords += P + z_i; s_coords += P + z_ni; \ s_coords += Q + z_ni; s_coords += Q + z_i; \ s_norms += N; s_norms += N; s_norms += N; s_norms += N; for ( float z = platform_zmin; z < platform_zmax; z += zdelta ) { pVect z_i(0,0,z-platform_zmin); pVect z_ni(0,0,z+zdelta-platform_zmin); SQ(p0,p1,snorm1); SQ(p1,p2,snorm2); SQ(p2,p3,snorm4); } #undef SQ // Store information about seam surfaces. Seam_Surface* const su = seam_info[i].surface; su[1].point = p1; su[1].norm = snorm1; su[1].plane = true; su[2].point = p1; su[2].norm = snorm2; su[2].plane = true; su[3].point = p1; su[3].norm = pVect(0,0,1); su[3].plane = false; su[4].point = p2; su[4].norm = snorm4; su[4].plane = true; su[6].point = p2; su[6].norm = pVect(0,0,1); su[6].plane = false; for ( int row = 0; row < row_cnt; row++ ) { const float z = platform_zmin + row * zdelta; PStack<pVect>& t_coords = even ? p_tile_coords : p1_tile_coords; PStack<pVect>& t_norms = even ? p_tile_norms : p1_tile_norms; p_tex_coords += trmax; p_tex_coords += tsmax; t_coords += pVect(x0,y0,z); t_norms += norm0; t_norms += norm0; p_tex_coords += trmax; p_tex_coords += tsmin; t_coords += pVect(x0,y0,z+zdelta); p_tex_coords += trmin; p_tex_coords += tsmin; t_coords += pVect(x1,y1,z+zdelta); t_norms += norm1; t_norms += norm1; p_tex_coords += trmin; p_tex_coords += tsmax; t_coords += pVect(x1,y1,z); even = !even; /// SOLUTION // Find seam coordinates. const float z_a = z - opt_seam_width * 0.5; const float z_b = z + opt_seam_width * 0.5; pCoor p0(x0,y0,z_a); pCoor p1(x0s,y0s,z_a); pCoor p2(x0s,y0s,z_b); pCoor p3(x0,y0,z_b); pCoor p4(x1,y1,z_a); pCoor p5(x1s,y1s,z_a); pCoor p6(x1s,y1s,z_b); pCoor p7(x1,y1,z_b); pVect snorm1 = pVect(0,0,-1); pVect snorm2 = pVect(0,0,1); #define SQ(a,b,c,d,N1,N2) \ s_coords+=a; s_coords+=b; s_coords+=c; s_coords+=d; \ s_norms += N1; s_norms += N1; s_norms += N2; s_norms += N2; SQ(p0,p1,p5,p4,snorm1,snorm1); SQ(p1,p2,p6,p5,norm0,norm1); SQ(p2,p3,p7,p6,snorm2,snorm2); #undef SQ const int s_idx = ( row + 1 ) * row_cnt + i; Seam_Surface* const su = seam_info[s_idx].surface; su[1].point = p1; su[1].norm = snorm1; su[1].plane = true; su[2].point = p1; su[2].norm = 0.5 * (norm0 + norm1); su[2].plane = true; su[3].point = p1; su[3].norm = pVect(-sin(theta0),cos(theta0),0); su[3].plane = false; su[4].point = p2; su[4].norm = snorm2; su[4].plane = true; su[6].point = p2; su[6].norm = pVect(-sin(theta0),cos(theta0),0); su[6].plane = false; } } while ( pVect* const v = p1_tile_coords.iterate() ) p_tile_coords += *v; while ( pVect* const v = p1_tile_norms.iterate() ) p_tile_norms += *v; platform_tile_norms.re_take(p_tile_norms); platform_tile_norms.to_gpu(); platform_tile_coords.re_take(p_tile_coords); platform_tile_coords.to_gpu(); platform_tex_coords.re_take(p_tex_coords); platform_tex_coords.to_gpu(); /// SOLUTION // // Copy seam coordinates and normals to buffer object and send // to gpu. // platform_seam_coords.re_take(s_coords); platform_seam_coords.to_gpu(); platform_seam_norms.re_take(s_norms); platform_seam_norms.to_gpu(); } void World::modelview_update() { pMatrix_Translate center_eye(-eye_location); pMatrix_Rotation rotate_eye(eye_direction,pVect(0,0,-1)); modelview = rotate_eye * center_eye; } /// /// Physical Simulation Code /// /// Initialize Simulation // Ball::Ball() { position = pCoor(30,22,-15.4); velocity = pVect(random()/(0.0+RAND_MAX),0,random()/(0.0+RAND_MAX)); axis = pNorm(0,1,0); angle = 0; } bool World::platform_collision_possible(pCoor pos) { // Assuming no motion in x or z axes. // return pos.y < opt_ball_radius && pos.x >= platform_xmin && pos.x <= platform_xmax && pos.z >= platform_zmin && pos.z <= platform_zmax; } /// External Modifications to State // // These allow the user to play with state while simulation // running. // Move the ball. // void Ball::translate(pVect amt) {position += amt;} // Add velocity to the ball. // void Ball::push(pVect amt) {velocity += amt;} // Set the velocity to zero. // void Ball::stop() {velocity = pVect(0,0,0); } void World::balls_stop() { for ( Ball *ball; balls.iterate(ball); ) ball->stop(); } void World::time_step_cpu(double delta_t) { const float deep = -100; // Iterate over balls. // for ( Ball *ball; balls.iterate(ball); ) { // Simulate ball. // time_step_cpu_ball(delta_t,ball); // Remove ball if it has fallen too far. // if ( ball->position.y < deep ) { balls.iterate_yank(); delete ball; } } // Possibly add more balls to simulation. // ball_countdown -= delta_t; if ( ball_countdown <= 0 || balls.occ() == 0 ) { balls += new Ball(); ball_countdown = 1e10; } /// SOLUTION // // Remove old markers. // const double visible_time = world_time - marker_lifetime; while ( collision_markers.occ() && collision_markers.head().t < visible_time ) collision_markers.dequeue(); } void World::time_step_cpu_ball(double delta_t, Ball* ball) { // Compute new ball position, accounting for current speed and // acceleration, and assuming no collision. // ball->position += ball->velocity * delta_t + 0.5 * gravity_accel * delta_t * delta_t; // Compute new velocity, assuming no collision. // ball->velocity += gravity_accel * delta_t; // Return quickly if collision impossible. // if ( !platform_collision_possible(ball->position) ) return; /// SOLUTION pNorm axis_normal(ball->position.x - platform_xmid, ball->position.y, 0); const float axis_dist = axis_normal.magnitude; const float platform_dist = platform_xrad - opt_ball_radius - axis_dist; // Find row and column of tiles. // const int row_num_raw = int( ( ball->position.z - platform_zmin ) * tile_size_inv + 0.5); const int row_num = max(0,min(row_cnt-1,row_num_raw)); const double theta = acos( - ( ball->position.x - platform_xmid ) / axis_dist ); const int col_num_raw = int( 0.5 + theta * delta_theta_inv ); const int col_num = max(0,min(row_cnt-1,col_num_raw)); // Find array index for closest circumferential seam. // const int circ_idx = ( row_num + 1 ) * row_cnt + col_num; for ( int dir=0; dir<2; dir++ ) { const bool circ_seam = dir == 1; const int seam_num = circ_seam ? circ_idx : col_num; int idx = 0; // Examine the three flat surfaces, S1, S2, S4, and for each // determine which side ball is on. Use this to determine // which surface can have collision. // for ( int p=0; p<3; p++ ) { const int face = 1 << p; Seam_Surface* const si = &seam_info[seam_num].surface[face]; const float dist = -dot( pVect(ball->position,si->point), si->norm ); if ( dist >= 0 ) idx |= face; } Seam_Surface* const si = &seam_info[seam_num].surface[idx]; pVect b_to_pt(ball->position,si->point); const float some_dist = -dot(b_to_pt,si->norm); pCoor tact; float dist; pVect tact_to_b; if ( si->plane ) { tact = ball->position - some_dist * si->norm; tact_to_b = si->norm; dist = some_dist; } else { tact = si->point + some_dist * si->norm; tact_to_b = pVect(tact,ball->position); dist = tact_to_b.normalize(); } if ( dist > opt_ball_radius ) continue; const float v_to_tact = -dot(ball->velocity,tact_to_b); if ( v_to_tact <= 0 ) continue; const float pen_dist = opt_ball_radius - dist; pCoor ball_contact_pos = ball->position + pen_dist * tact_to_b; pVect bounce_delta_v = bounce_factor * v_to_tact * tact_to_b; ball->velocity += bounce_delta_v; ball->position = ball_contact_pos; // Insert a marker describing collision. // Collision_Marker* const marker = collision_markers.enqueuei(); marker->center = tact; marker->bounce_dv = 0.2 * bounce_delta_v; marker->r = 0.5; marker->t = world_time; } ball->contact = platform_dist < 0.5; // Return if ball is not in contact with platform. // if ( !ball->contact ) return; // Snap ball to surface. // const float z = ball->position.z; ball->position = ( platform_xrad - opt_ball_radius ) * axis_normal; ball->position.x += platform_xmid; ball->position.z = z; pVect platform_norm = -axis_normal; const float speed_to_surface = dot(platform_norm,ball->velocity); if ( speed_to_surface >= 0 ) return; ball->velocity -= bounce_factor * speed_to_surface * platform_norm; return; #if 0 /// SOLUTION ABOVE, ORIGINAL CODE BELOW // Compute y coordinate of platform under ball. // // Note that this is not really the closest point on the platform to the ball. // const float cos_th = ( platform_xmid - ball->position.x ) * platform_xrad_inv; const float sin_th = sqrt(1.0 - cos_th * cos_th ); const float platform_y = -platform_xrad * sin_th; const bool contact_prev = ball->contact; const bool true_contact = ball->position.y <= platform_y + 0.01; ball->contact = ball->position.y <= platform_y + 0.5; if ( !ball->contact ) return; ball->position.y = max(ball->position.y,platform_y); pVect platform_norm( cos_th, sin_th, 0); const int row_num_prev = ball->row_num; const int col_num_prev = ball->col_num; const int row_num = int(ball->position.z * tile_size_inv); const int col_num = int(acos(cos_th) * delta_theta_inv); ball->col_num = col_num; ball->row_num = row_num; if ( contact_prev && ( row_num_prev != row_num || col_num_prev != col_num ) ) { // Ball has rolled from one tile to another. } if ( !true_contact ) return; const float speed_to_surface = dot(platform_norm,ball->velocity); ball->velocity -= 1.9 * speed_to_surface * platform_norm; #endif } void World::balls_render() { for ( Ball *ball; balls.iterate(ball); ) sphere.render(ball->position,ball->axis,ball->angle); } void World::render() { // This routine called whenever window needs to be updated. // Get any waiting keyboard commands. // cb_keyboard(); // Start a timer object used for tuning this code. // frame_timer.frame_start(); const double time_now = time_wall_fp(); if ( opt_pause || world_time == 0 ) { /// Don't change simulation state. // world_time = time_now; } else { /// Advance simulation state by wall clock time. // const double delta_t = min(1./20,time_now - world_time); time_step_cpu(delta_t); world_time += delta_t; } /// Emit a Graphical Representation of Simulation State // // Understanding of the code below not required for introductory // lectures. const pColor white(0xffffff); const pColor gray(0x303030); const pColor lsu_business_purple(0x7f5ca2); const pColor lsu_spirit_purple(0x580da6); const pColor lsu_spirit_gold(0xf9b237); const pColor lsu_official_purple(0x2f0462); const pColor dark(0); const int win_width = ogl_helper.get_width(); const int win_height = ogl_helper.get_height(); const float aspect = float(win_width) / win_height; glMatrixMode(GL_MODELVIEW); glLoadTransposeMatrixf(modelview); glMatrixMode(GL_PROJECTION); glLoadIdentity(); // Frustum: left, right, bottom, top, near, far glFrustum(-.8,.8,-.8/aspect,.8/aspect,1,5000); glViewport(0, 0, win_width, win_height); pError_Check(); glClearColor(0,0,0,0.5); glClearDepth(1.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT ); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); glDisable(GL_BLEND); glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,1); glLightfv(GL_LIGHT0, GL_POSITION, light_location); glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 0.3); glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 1.0); glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0); pColor ambient_color(0x999999); glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient_color); glLightfv(GL_LIGHT0, GL_DIFFUSE, white * opt_light_intensity); glLightfv(GL_LIGHT0, GL_AMBIENT, dark); glLightfv(GL_LIGHT0, GL_SPECULAR, white * opt_light_intensity); glEnable(GL_LIGHT0); glEnable(GL_LIGHTING); glEnable(GL_COLOR_MATERIAL); glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE); glShadeModel(GL_SMOOTH); pColor color_ball(0x666666); pColor scolor_ball(0x111111); const float shininess_ball = 5; // Common to all textures. // glActiveTexture(GL_TEXTURE0); glEnable(GL_TEXTURE_2D); glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE); glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT); glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT); glEnable(GL_RESCALE_NORMAL); glEnable(GL_NORMALIZE); ogl_helper.fbprintf("%s\n",frame_timer.frame_rate_text_get()); ogl_helper.fbprintf ("Eye location: [%5.1f, %5.1f, %5.1f] " "Eye direction: [%+.2f, %+.2f, %+.2f]\n", eye_location.x, eye_location.y, eye_location.z, eye_direction.x, eye_direction.y, eye_direction.z); ogl_helper.fbprintf ("Light location: [%5.1f, %5.1f, %5.1f]\n", light_location.x, light_location.y, light_location.z); Ball& ball = *balls[0]; ogl_helper.fbprintf ("Ball Pos [%5.1f,%5.1f,%5.1f] Vel [%+5.1f,%+5.1f,%+5.1f]\n", ball.position.x,ball.position.y,ball.position.z, ball.velocity.x,ball.velocity.y,ball.velocity.z ); pVariable_Control_Elt* const cvar = variable_control.current; ogl_helper.fbprintf("VAR %s = %.5f (TAB or '`' to change, +/- to adjust)\n", cvar->name,cvar->var[0]); const int half_elements = platform_tile_coords.elements >> 3 << 2; // Setup texture for platform. // glBindTexture(GL_TEXTURE_2D,texid_syl); // Blend dark tiles with existing ball reflection. // glEnable(GL_STENCIL_TEST); glBlendEquation(GL_FUNC_ADD); glBlendFunc(GL_CONSTANT_ALPHA,GL_ONE_MINUS_CONSTANT_ALPHA); // src, dst glBlendColor(0,0,0,0.5); glDepthFunc(GL_ALWAYS); glEnable(GL_TEXTURE_2D); platform_tex_coords.bind(); glTexCoordPointer(2, GL_FLOAT,2*sizeof(float), 0); glEnableClientState(GL_TEXTURE_COORD_ARRAY); platform_tile_coords.bind(); glVertexPointer (3, GL_FLOAT,sizeof(platform_tile_coords.data[0]), 0); glEnableClientState(GL_VERTEX_ARRAY); platform_tile_norms.bind(); glNormalPointer (GL_FLOAT,sizeof(platform_tile_norms.data[0]), 0); glEnableClientState(GL_NORMAL_ARRAY); for ( int pass = 0; pass < 2; pass++ ) { if ( pass == 0 ) { // Prepare to write unshadowed parts of frame buffer. // glStencilFunc(GL_NOTEQUAL,1,1); } else { // Prepare to write shadowed parts of frame buffer. // glStencilFunc(GL_EQUAL,1,1); glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 6.0); } glEnable(GL_TEXTURE_2D); // Write lighter-colored, textured tiles. // glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,gray); glMaterialf(GL_FRONT_AND_BACK,GL_SHININESS,2.0); glColor3f(0.35,0.35,0.35); glDrawArrays(GL_QUADS,0,half_elements+4); // Write darker-colored, untextured, mirror tiles. // glEnable(GL_BLEND); glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,white); glMaterialf(GL_FRONT_AND_BACK,GL_SHININESS,20); glDisable(GL_TEXTURE_2D); glColor3fv(lsu_spirit_purple); glDrawArrays(GL_QUADS,half_elements+4,half_elements-4); glDisable(GL_BLEND); } glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); glBindBuffer(GL_ARRAY_BUFFER,0); glDisable(GL_STENCIL_TEST); glDepthFunc(GL_LESS); /// SOLUTION - Render Seams // platform_seam_coords.bind(); glVertexPointer (3, GL_FLOAT,sizeof(platform_seam_coords.data[0]), 0); glEnableClientState(GL_VERTEX_ARRAY); platform_seam_norms.bind(); glNormalPointer (GL_FLOAT,sizeof(platform_seam_norms.data[0]), 0); glBindBuffer(GL_ARRAY_BUFFER,0); glEnableClientState(GL_NORMAL_ARRAY); glColor3f(0.9,0.9,0.9); glDrawArrays(GL_QUADS,0,platform_seam_coords.elements); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); // Render Ball // glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 1.0); glMaterialf(GL_BACK,GL_SHININESS,shininess_ball); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D,texid_emacs); balls_render(); /// SOLUTION - Render Collision Locations // glDisable(GL_TEXTURE_2D); glColor4fv(lsu_spirit_purple); for ( Collision_Marker c; collision_markers.iterate(c); ) cone.render(c.center,c.r,c.bounce_dv); if ( opt_normals_visible ) { glColor3fv(lsu_spirit_gold); for ( int i=0; i<platform_tile_coords.elements; i++ ) cone.render(platform_tile_coords[i],0.2,5 * platform_tile_norms[i]); } // Render Marker for Light Source // insert_tetrahedron(light_location,0.5); pError_Check(); glColor3f(0.5,1,0.5); glDisable(GL_LIGHTING); glDisable(GL_BLEND); glDisable(GL_DEPTH_TEST); glDisable(GL_STENCIL_TEST); frame_timer.frame_end(); glutSwapBuffers(); } void World::cb_keyboard() { if ( !ogl_helper.keyboard_key ) return; pVect adjustment(0,0,0); pVect user_rot_axis(0,0,0); const float move_amt = 0.4; const float prev_seam_width = opt_seam_width; const float prev_seam_height = opt_seam_height; switch ( ogl_helper.keyboard_key ) { case FB_KEY_LEFT: adjustment.x = -move_amt; break; case FB_KEY_RIGHT: adjustment.x = move_amt; break; case FB_KEY_PAGE_UP: adjustment.y = move_amt; break; case FB_KEY_PAGE_DOWN: adjustment.y = -move_amt; break; case FB_KEY_DOWN: adjustment.z = move_amt; break; case FB_KEY_UP: adjustment.z = -move_amt; break; case FB_KEY_DELETE: user_rot_axis.y = 1; break; case FB_KEY_INSERT: user_rot_axis.y = -1; break; case FB_KEY_HOME: user_rot_axis.x = 1; break; case FB_KEY_END: user_rot_axis.x = -1; break; case 'b': opt_move_item = MI_Ball; break; case 'B': opt_move_item = MI_Ball_V; break; case 'e': case 'E': opt_move_item = MI_Eye; break; case 'g': case 'G': opt_gravity = !opt_gravity; break; case 'l': case 'L': opt_move_item = MI_Light; break; case 'n': case 'N': opt_normals_visible = !opt_normals_visible; break; case 'p': case 'P': opt_pause = !opt_pause; break; case 's': balls_stop(); break; case 'x': case 'X': balls += new Ball(); break; case 9: variable_control.switch_var_right(); break; case 96: variable_control.switch_var_left(); break; // `, until S-TAB works. case '-':case '_': variable_control.adjust_lower(); break; case '+':case '=': variable_control.adjust_higher(); break; default: printf("Unknown key, %d\n",ogl_helper.keyboard_key); break; } gravity_accel.y = opt_gravity ? -opt_gravity_accel : 0; sphere.radius = opt_ball_radius; opt_energy_loss = max(0.0f,min(1.0f,opt_energy_loss)); bounce_factor = 2 - opt_energy_loss; /// SOLUTION - Recompute platform if seam parameters change. // if ( opt_seam_height != prev_seam_height || opt_seam_width != prev_seam_width ) platform_update(); // Update eye_direction based on keyboard command. // if ( user_rot_axis.x || user_rot_axis.y ) { pMatrix_Rotation rotall(eye_direction,pVect(0,0,-1)); user_rot_axis *= invert(rotall); eye_direction *= pMatrix_Rotation(user_rot_axis, M_PI * 0.03); modelview_update(); } // Update eye_location based on keyboard command. // if ( adjustment.x || adjustment.y || adjustment.z ) { const double angle = fabs(eye_direction.y) > 0.99 ? 0 : atan2(eye_direction.x,-eye_direction.z); pMatrix_Rotation rotall(pVect(0,1,0),-angle); adjustment *= rotall; switch ( opt_move_item ){ case MI_Ball: balls.peek()->translate(adjustment); break; case MI_Ball_V: balls.peek()->push(adjustment); break; case MI_Light: light_location += adjustment; break; case MI_Eye: eye_location += adjustment; break; default: break; } modelview_update(); } } int main(int argv, char **argc) { pOpenGL_Helper popengl_helper(argv,argc); World world(popengl_helper); popengl_helper.rate_set(30); popengl_helper.display_cb_set(world.render_w,&world); }