Permalink
Fetching contributors…
Cannot retrieve contributors at this time
523 lines (403 sloc) 16.4 KB
// Include standard headers
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <sstream>
// Include GLEW
#include <GL/glew.h>
// Include GLFW
#include <GLFW/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 use 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;
}