Permalink
| // Include standard headers | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <vector> | |
| #include <sstream> | |
| // Include GLEW | |
| #include <GL/glew.h> | |
| // Include GLFW | |
| #include <glfw3.h> | |
| GLFWwindow* window; | |
| // Include GLM | |
| #include <glm/glm.hpp> | |
| #include <glm/gtc/matrix_transform.hpp> | |
| #include <glm/gtc/quaternion.hpp> | |
| #include <glm/gtx/quaternion.hpp> | |
| using namespace glm; | |
| // Include AntTweakBar | |
| #include <AntTweakBar.h> | |
| #include <common/shader.hpp> | |
| #include <common/texture.hpp> | |
| #include <common/controls.hpp> | |
| #include <common/objloader.hpp> | |
| #include <common/vboindexer.hpp> | |
| void ScreenPosToWorldRay( | |
| int mouseX, int mouseY, // Mouse position, in pixels, from bottom-left corner of the window | |
| int screenWidth, int screenHeight, // Window size, in pixels | |
| glm::mat4 ViewMatrix, // Camera position and orientation | |
| glm::mat4 ProjectionMatrix, // Camera parameters (ratio, field of view, near and far planes) | |
| glm::vec3& out_origin, // Ouput : Origin of the ray. /!\ Starts at the near plane, so if you want the ray to start at the camera's position instead, ignore this. | |
| glm::vec3& out_direction // Ouput : Direction, in world space, of the ray that goes "through" the mouse. | |
| ){ | |
| // The ray Start and End positions, in Normalized Device Coordinates (Have you read Tutorial 4 ?) | |
| glm::vec4 lRayStart_NDC( | |
| ((float)mouseX/(float)screenWidth - 0.5f) * 2.0f, // [0,1024] -> [-1,1] | |
| ((float)mouseY/(float)screenHeight - 0.5f) * 2.0f, // [0, 768] -> [-1,1] | |
| -1.0, // The near plane maps to Z=-1 in Normalized Device Coordinates | |
| 1.0f | |
| ); | |
| glm::vec4 lRayEnd_NDC( | |
| ((float)mouseX/(float)screenWidth - 0.5f) * 2.0f, | |
| ((float)mouseY/(float)screenHeight - 0.5f) * 2.0f, | |
| 0.0, | |
| 1.0f | |
| ); | |
| // The Projection matrix goes from Camera Space to NDC. | |
| // So inverse(ProjectionMatrix) goes from NDC to Camera Space. | |
| glm::mat4 InverseProjectionMatrix = glm::inverse(ProjectionMatrix); | |
| // The View Matrix goes from World Space to Camera Space. | |
| // So inverse(ViewMatrix) goes from Camera Space to World Space. | |
| glm::mat4 InverseViewMatrix = glm::inverse(ViewMatrix); | |
| glm::vec4 lRayStart_camera = InverseProjectionMatrix * lRayStart_NDC; lRayStart_camera/=lRayStart_camera.w; | |
| glm::vec4 lRayStart_world = InverseViewMatrix * lRayStart_camera; lRayStart_world /=lRayStart_world .w; | |
| glm::vec4 lRayEnd_camera = InverseProjectionMatrix * lRayEnd_NDC; lRayEnd_camera /=lRayEnd_camera .w; | |
| glm::vec4 lRayEnd_world = InverseViewMatrix * lRayEnd_camera; lRayEnd_world /=lRayEnd_world .w; | |
| // Faster way (just one inverse) | |
| //glm::mat4 M = glm::inverse(ProjectionMatrix * ViewMatrix); | |
| //glm::vec4 lRayStart_world = M * lRayStart_NDC; lRayStart_world/=lRayStart_world.w; | |
| //glm::vec4 lRayEnd_world = M * lRayEnd_NDC ; lRayEnd_world /=lRayEnd_world.w; | |
| glm::vec3 lRayDir_world(lRayEnd_world - lRayStart_world); | |
| lRayDir_world = glm::normalize(lRayDir_world); | |
| out_origin = glm::vec3(lRayStart_world); | |
| out_direction = glm::normalize(lRayDir_world); | |
| } | |
| bool TestRayOBBIntersection( | |
| glm::vec3 ray_origin, // Ray origin, in world space | |
| glm::vec3 ray_direction, // Ray direction (NOT target position!), in world space. Must be normalize()'d. | |
| glm::vec3 aabb_min, // Minimum X,Y,Z coords of the mesh when not transformed at all. | |
| glm::vec3 aabb_max, // Maximum X,Y,Z coords. Often aabb_min*-1 if your mesh is centered, but it's not always the case. | |
| glm::mat4 ModelMatrix, // Transformation applied to the mesh (which will thus be also applied to its bounding box) | |
| float& intersection_distance // Output : distance between ray_origin and the intersection with the OBB | |
| ){ | |
| // Intersection method from Real-Time Rendering and Essential Mathematics for Games | |
| float tMin = 0.0f; | |
| float tMax = 100000.0f; | |
| glm::vec3 OBBposition_worldspace(ModelMatrix[3].x, ModelMatrix[3].y, ModelMatrix[3].z); | |
| glm::vec3 delta = OBBposition_worldspace - ray_origin; | |
| // Test intersection with the 2 planes perpendicular to the OBB's X axis | |
| { | |
| glm::vec3 xaxis(ModelMatrix[0].x, ModelMatrix[0].y, ModelMatrix[0].z); | |
| float e = glm::dot(xaxis, delta); | |
| float f = glm::dot(ray_direction, xaxis); | |
| if ( fabs(f) > 0.001f ){ // Standard case | |
| float t1 = (e+aabb_min.x)/f; // Intersection with the "left" plane | |
| float t2 = (e+aabb_max.x)/f; // Intersection with the "right" plane | |
| // t1 and t2 now contain distances betwen ray origin and ray-plane intersections | |
| // We want t1 to represent the nearest intersection, | |
| // so if it's not the case, invert t1 and t2 | |
| if (t1>t2){ | |
| float w=t1;t1=t2;t2=w; // swap t1 and t2 | |
| } | |
| // tMax is the nearest "far" intersection (amongst the X,Y and Z planes pairs) | |
| if ( t2 < tMax ) | |
| tMax = t2; | |
| // tMin is the farthest "near" intersection (amongst the X,Y and Z planes pairs) | |
| if ( t1 > tMin ) | |
| tMin = t1; | |
| // And here's the trick : | |
| // If "far" is closer than "near", then there is NO intersection. | |
| // See the images in the tutorials for the visual explanation. | |
| if (tMax < tMin ) | |
| return false; | |
| }else{ // Rare case : the ray is almost parallel to the planes, so they don't have any "intersection" | |
| if(-e+aabb_min.x > 0.0f || -e+aabb_max.x < 0.0f) | |
| return false; | |
| } | |
| } | |
| // Test intersection with the 2 planes perpendicular to the OBB's Y axis | |
| // Exactly the same thing than above. | |
| { | |
| glm::vec3 yaxis(ModelMatrix[1].x, ModelMatrix[1].y, ModelMatrix[1].z); | |
| float e = glm::dot(yaxis, delta); | |
| float f = glm::dot(ray_direction, yaxis); | |
| if ( fabs(f) > 0.001f ){ | |
| float t1 = (e+aabb_min.y)/f; | |
| float t2 = (e+aabb_max.y)/f; | |
| if (t1>t2){float w=t1;t1=t2;t2=w;} | |
| if ( t2 < tMax ) | |
| tMax = t2; | |
| if ( t1 > tMin ) | |
| tMin = t1; | |
| if (tMin > tMax) | |
| return false; | |
| }else{ | |
| if(-e+aabb_min.y > 0.0f || -e+aabb_max.y < 0.0f) | |
| return false; | |
| } | |
| } | |
| // Test intersection with the 2 planes perpendicular to the OBB's Z axis | |
| // Exactly the same thing than above. | |
| { | |
| glm::vec3 zaxis(ModelMatrix[2].x, ModelMatrix[2].y, ModelMatrix[2].z); | |
| float e = glm::dot(zaxis, delta); | |
| float f = glm::dot(ray_direction, zaxis); | |
| if ( fabs(f) > 0.001f ){ | |
| float t1 = (e+aabb_min.z)/f; | |
| float t2 = (e+aabb_max.z)/f; | |
| if (t1>t2){float w=t1;t1=t2;t2=w;} | |
| if ( t2 < tMax ) | |
| tMax = t2; | |
| if ( t1 > tMin ) | |
| tMin = t1; | |
| if (tMin > tMax) | |
| return false; | |
| }else{ | |
| if(-e+aabb_min.z > 0.0f || -e+aabb_max.z < 0.0f) | |
| return false; | |
| } | |
| } | |
| intersection_distance = tMin; | |
| return true; | |
| } | |
| int main( void ) | |
| { | |
| // Initialise GLFW | |
| if( !glfwInit() ) | |
| { | |
| fprintf( stderr, "Failed to initialize GLFW\n" ); | |
| getchar(); | |
| return -1; | |
| } | |
| glfwWindowHint(GLFW_SAMPLES, 4); | |
| glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); | |
| glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); | |
| glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // To make MacOS happy; should not be needed | |
| glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); | |
| // Open a window and create its OpenGL context | |
| window = glfwCreateWindow( 1024, 768, "Misc 05 - version with custom Ray-OBB code", NULL, NULL); | |
| if( window == NULL ){ | |
| fprintf( stderr, "Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.\n" ); | |
| getchar(); | |
| glfwTerminate(); | |
| return -1; | |
| } | |
| glfwMakeContextCurrent(window); | |
| // Initialize GLEW | |
| glewExperimental = true; // Needed for core profile | |
| if (glewInit() != GLEW_OK) { | |
| fprintf(stderr, "Failed to initialize GLEW\n"); | |
| getchar(); | |
| glfwTerminate(); | |
| return -1; | |
| } | |
| // Initialize the GUI | |
| TwInit(TW_OPENGL_CORE, NULL); | |
| TwWindowSize(1024, 768); | |
| TwBar * GUI = TwNewBar("Picking"); | |
| TwSetParam(GUI, NULL, "refresh", TW_PARAM_CSTRING, 1, "0.1"); | |
| std::string message; | |
| TwAddVarRW(GUI, "Last picked object", TW_TYPE_STDSTRING, &message, NULL); | |
| // Ensure we can capture the escape key being pressed below | |
| glfwSetInputMode(window, GLFW_STICKY_KEYS, GL_TRUE); | |
| glfwSetCursorPos(window, 1024/2, 768/2); | |
| // Dark blue background | |
| glClearColor(0.0f, 0.0f, 0.4f, 0.0f); | |
| // Enable depth test | |
| glEnable(GL_DEPTH_TEST); | |
| // Accept fragment if it closer to the camera than the former one | |
| glDepthFunc(GL_LESS); | |
| // Cull triangles which normal is not towards the camera | |
| glEnable(GL_CULL_FACE); | |
| GLuint VertexArrayID; | |
| glGenVertexArrays(1, &VertexArrayID); | |
| glBindVertexArray(VertexArrayID); | |
| // Create and compile our GLSL program from the shaders | |
| GLuint programID = LoadShaders( "StandardShading.vertexshader", "StandardShading.fragmentshader" ); | |
| // Get a handle for our "MVP" uniform | |
| GLuint MatrixID = glGetUniformLocation(programID, "MVP"); | |
| GLuint ViewMatrixID = glGetUniformLocation(programID, "V"); | |
| GLuint ModelMatrixID = glGetUniformLocation(programID, "M"); | |
| // Load the texture | |
| GLuint Texture = loadDDS("uvmap.DDS"); | |
| // Get a handle for our "myTextureSampler" uniform | |
| GLuint TextureID = glGetUniformLocation(programID, "myTextureSampler"); | |
| // Read our .obj file | |
| std::vector<glm::vec3> vertices; | |
| std::vector<glm::vec2> uvs; | |
| std::vector<glm::vec3> normals; | |
| bool res = loadOBJ("suzanne.obj", vertices, uvs, normals); | |
| std::vector<unsigned short> indices; | |
| std::vector<glm::vec3> indexed_vertices; | |
| std::vector<glm::vec2> indexed_uvs; | |
| std::vector<glm::vec3> indexed_normals; | |
| indexVBO(vertices, uvs, normals, indices, indexed_vertices, indexed_uvs, indexed_normals); | |
| // Load it into a VBO | |
| GLuint vertexbuffer; | |
| glGenBuffers(1, &vertexbuffer); | |
| glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer); | |
| glBufferData(GL_ARRAY_BUFFER, indexed_vertices.size() * sizeof(glm::vec3), &indexed_vertices[0], GL_STATIC_DRAW); | |
| GLuint uvbuffer; | |
| glGenBuffers(1, &uvbuffer); | |
| glBindBuffer(GL_ARRAY_BUFFER, uvbuffer); | |
| glBufferData(GL_ARRAY_BUFFER, indexed_uvs.size() * sizeof(glm::vec2), &indexed_uvs[0], GL_STATIC_DRAW); | |
| GLuint normalbuffer; | |
| glGenBuffers(1, &normalbuffer); | |
| glBindBuffer(GL_ARRAY_BUFFER, normalbuffer); | |
| glBufferData(GL_ARRAY_BUFFER, indexed_normals.size() * sizeof(glm::vec3), &indexed_normals[0], GL_STATIC_DRAW); | |
| // Generate a buffer for the indices as well | |
| GLuint elementbuffer; | |
| glGenBuffers(1, &elementbuffer); | |
| glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer); | |
| glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned short), &indices[0] , GL_STATIC_DRAW); | |
| // Generate positions & rotations for 100 monkeys | |
| std::vector<glm::vec3> positions(100); | |
| std::vector<glm::quat> orientations(100); | |
| for(int i=0; i<100; i++){ | |
| positions[i] = glm::vec3(rand()%20-10, rand()%20-10, rand()%20-10); | |
| orientations[i] = glm::quat(glm::vec3(rand()%360, rand()%360, rand()%360)); | |
| } | |
| // Get a handle for our "LightPosition" uniform | |
| glUseProgram(programID); | |
| GLuint LightID = glGetUniformLocation(programID, "LightPosition_worldspace"); | |
| // For speed computation | |
| double lastTime = glfwGetTime(); | |
| int nbFrames = 0; | |
| do{ | |
| // Measure speed | |
| double currentTime = glfwGetTime(); | |
| nbFrames++; | |
| if ( currentTime - lastTime >= 1.0 ){ // If last prinf() was more than 1sec ago | |
| // printf and reset | |
| printf("%f ms/frame\n", 1000.0/double(nbFrames)); | |
| nbFrames = 0; | |
| lastTime += 1.0; | |
| } | |
| // Compute the MVP matrix from keyboard and mouse input | |
| computeMatricesFromInputs(); | |
| glm::mat4 ProjectionMatrix = getProjectionMatrix(); | |
| glm::mat4 ViewMatrix = getViewMatrix(); | |
| // PICKING IS DONE HERE | |
| // (Instead of picking each frame if the mouse button is down, | |
| // you should probably only check if the mouse button was just released) | |
| if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT)){ | |
| glm::vec3 ray_origin; | |
| glm::vec3 ray_direction; | |
| ScreenPosToWorldRay( | |
| 1024/2, 768/2, | |
| 1024, 768, | |
| ViewMatrix, | |
| ProjectionMatrix, | |
| ray_origin, | |
| ray_direction | |
| ); | |
| //ray_direction = ray_direction*20.0f; | |
| message = "background"; | |
| // Test each each Oriented Bounding Box (OBB). | |
| // A physics engine can be much smarter than this, | |
| // because it already has some spatial partitionning structure, | |
| // like Binary Space Partitionning Tree (BSP-Tree), | |
| // Bounding Volume Hierarchy (BVH) or other. | |
| for(int i=0; i<100; i++){ | |
| float intersection_distance; // Output of TestRayOBBIntersection() | |
| glm::vec3 aabb_min(-1.0f, -1.0f, -1.0f); | |
| glm::vec3 aabb_max( 1.0f, 1.0f, 1.0f); | |
| // The ModelMatrix transforms : | |
| // - the mesh to its desired position and orientation | |
| // - but also the AABB (defined with aabb_min and aabb_max) into an OBB | |
| glm::mat4 RotationMatrix = glm::toMat4(orientations[i]); | |
| glm::mat4 TranslationMatrix = translate(mat4(), positions[i]); | |
| glm::mat4 ModelMatrix = TranslationMatrix * RotationMatrix; | |
| if ( TestRayOBBIntersection( | |
| ray_origin, | |
| ray_direction, | |
| aabb_min, | |
| aabb_max, | |
| ModelMatrix, | |
| intersection_distance) | |
| ){ | |
| std::ostringstream oss; | |
| oss << "mesh " << i; | |
| message = oss.str(); | |
| break; | |
| } | |
| } | |
| } | |
| // Dark blue background | |
| glClearColor(0.0f, 0.0f, 0.4f, 0.0f); | |
| // Re-clear the screen for real rendering | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | |
| // Use our shader | |
| glUseProgram(programID); | |
| glEnableVertexAttribArray(0); | |
| glEnableVertexAttribArray(1); | |
| glEnableVertexAttribArray(2); | |
| for(int i=0; i<100; i++){ | |
| glm::mat4 RotationMatrix = glm::toMat4(orientations[i]); | |
| glm::mat4 TranslationMatrix = translate(mat4(), positions[i]); | |
| glm::mat4 ModelMatrix = TranslationMatrix * RotationMatrix; | |
| glm::mat4 MVP = ProjectionMatrix * ViewMatrix * ModelMatrix; | |
| // Send our transformation to the currently bound shader, | |
| // in the "MVP" uniform | |
| glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]); | |
| glUniformMatrix4fv(ModelMatrixID, 1, GL_FALSE, &ModelMatrix[0][0]); | |
| glUniformMatrix4fv(ViewMatrixID, 1, GL_FALSE, &ViewMatrix[0][0]); | |
| glm::vec3 lightPos = glm::vec3(4,4,4); | |
| glUniform3f(LightID, lightPos.x, lightPos.y, lightPos.z); | |
| // Bind our texture in Texture Unit 0 | |
| glActiveTexture(GL_TEXTURE0); | |
| glBindTexture(GL_TEXTURE_2D, Texture); | |
| // Set our "myTextureSampler" sampler to user Texture Unit 0 | |
| glUniform1i(TextureID, 0); | |
| // 1rst attribute buffer : vertices | |
| glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer); | |
| glVertexAttribPointer( | |
| 0, // attribute | |
| 3, // size | |
| GL_FLOAT, // type | |
| GL_FALSE, // normalized? | |
| 0, // stride | |
| (void*)0 // array buffer offset | |
| ); | |
| // 2nd attribute buffer : UVs | |
| glBindBuffer(GL_ARRAY_BUFFER, uvbuffer); | |
| glVertexAttribPointer( | |
| 1, // attribute | |
| 2, // size | |
| GL_FLOAT, // type | |
| GL_FALSE, // normalized? | |
| 0, // stride | |
| (void*)0 // array buffer offset | |
| ); | |
| // 3rd attribute buffer : normals | |
| glBindBuffer(GL_ARRAY_BUFFER, normalbuffer); | |
| glVertexAttribPointer( | |
| 2, // attribute | |
| 3, // size | |
| GL_FLOAT, // type | |
| GL_FALSE, // normalized? | |
| 0, // stride | |
| (void*)0 // array buffer offset | |
| ); | |
| // Index buffer | |
| glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer); | |
| // Draw the triangles ! | |
| glDrawElements( | |
| GL_TRIANGLES, // mode | |
| indices.size(), // count | |
| GL_UNSIGNED_SHORT, // type | |
| (void*)0 // element array buffer offset | |
| ); | |
| } | |
| glDisableVertexAttribArray(0); | |
| glDisableVertexAttribArray(1); | |
| glDisableVertexAttribArray(2); | |
| // Draw GUI | |
| TwDraw(); | |
| // Swap buffers | |
| glfwSwapBuffers(window); | |
| glfwPollEvents(); | |
| } // Check if the ESC key was pressed or the window was closed | |
| while( glfwGetKey(window, GLFW_KEY_ESCAPE ) != GLFW_PRESS && | |
| glfwWindowShouldClose(window) == 0 ); | |
| // Cleanup VBO and shader | |
| glDeleteBuffers(1, &vertexbuffer); | |
| glDeleteBuffers(1, &uvbuffer); | |
| glDeleteBuffers(1, &normalbuffer); | |
| glDeleteBuffers(1, &elementbuffer); | |
| glDeleteProgram(programID); | |
| glDeleteTextures(1, &Texture); | |
| glDeleteVertexArrays(1, &VertexArrayID); | |
| // Close OpenGL window and terminate GLFW | |
| glfwTerminate(); | |
| return 0; | |
| } | |