diff --git a/CMakeLists.txt b/CMakeLists.txt index 75592736..6d052d6f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ SET(OUR_EXECUTABLE_OUTPUT_PATH "${PROJECT_BINARY_DIR}/bin") ENABLE_TESTING() # Options -OPTION(COMPILE_EXAMPLES "Select this if you want to build the examples" OFF) +OPTION(COMPILE_TESTBED "Select this if you want to build the testbed application" OFF) OPTION(COMPILE_TESTS "Select this if you want to build the tests" OFF) OPTION(PROFILING_ENABLED "Select this if you want to compile with enabled profiling" OFF) OPTION(DOUBLE_PRECISION_ENABLED "Select this if you want to compile using double precision floating @@ -119,8 +119,6 @@ SET (REACTPHYSICS3D_SOURCES "src/engine/OverlappingPair.cpp" "src/engine/Profiler.h" "src/engine/Profiler.cpp" - "src/engine/Timer.h" - "src/engine/Timer.cpp" "src/mathematics/mathematics.h" "src/mathematics/mathematics_functions.h" "src/mathematics/Matrix2x2.h" @@ -144,10 +142,10 @@ SET (REACTPHYSICS3D_SOURCES # Create the library ADD_LIBRARY (reactphysics3d STATIC ${REACTPHYSICS3D_SOURCES}) -# If we need to compile the examples -IF(COMPILE_EXAMPLES) - add_subdirectory(examples/) -ENDIF(COMPILE_EXAMPLES) +# If we need to compile the testbed application +IF(COMPILE_TESTBED) + add_subdirectory(testbed/) +ENDIF(COMPILE_TESTBED) # If we need to compile the tests IF(COMPILE_TESTS) diff --git a/documentation/UserManual/ReactPhysics3D-UserManual.pdf b/documentation/UserManual/ReactPhysics3D-UserManual.pdf index aa1391a5..ad935b3f 100644 Binary files a/documentation/UserManual/ReactPhysics3D-UserManual.pdf and b/documentation/UserManual/ReactPhysics3D-UserManual.pdf differ diff --git a/documentation/UserManual/ReactPhysics3D-UserManual.tex b/documentation/UserManual/ReactPhysics3D-UserManual.tex index 2205e678..ddf4c830 100644 --- a/documentation/UserManual/ReactPhysics3D-UserManual.tex +++ b/documentation/UserManual/ReactPhysics3D-UserManual.tex @@ -228,7 +228,10 @@ rp3d::CollisionWorld world; Do not forget to destroy the \texttt{CollisionWorld} instance at the end of your program in order to release the allocated memory. If the object has been created statically, it will be destroyed automatically at the end of the scope in which it has been created. If the object has been created dynamically (using the \texttt{new} - operator), you need to destroy it with the \texttt{delete} operator. + operator), you need to destroy it with the \texttt{delete} operator. \\ + + When the \texttt{CollisionWorld} is destroyed, all the bodies that have been added into it and that have not been destroyed already will be destroyed. + Therefore, the pointers to the bodies of the world will become invalid after the existence of their \texttt{CollisionWorld}. \section{Collision Bodies} @@ -311,12 +314,8 @@ world.destroyCollisionBody(body); \subsection{Creating the Dynamics World} The first thing you have to do when you want to simulate the dynamics of rigid bodies in time is to create an instance - of the \texttt{DynamicsWorld}. You need to specify two parameters when constructing the world. The first one is the gravity acceleration vector (in $m / s^2$) in the - world and - the second one is the simulation time step (in seconds). Note that gravity is activated by default when you create the world. The time step is the fixed amount of time - used at each internal - physics tick. Note that multiple internal physics ticks can be taken at each frame. For real-time applications, a time step of $\frac{1}{60}$ seconds (60 Hz) is usually - used. Using a smaller time step makes the simulation more precise but also more expensive to compute. \\ + of the \texttt{DynamicsWorld}. You need to specify the gravity acceleration vector (in $m / s^2$) in the world as parameter. Note that gravity is + activated by default when you create the world. \\ Here is how to create the Dynamics World: \\ @@ -324,11 +323,8 @@ world.destroyCollisionBody(body); // Gravity vector rp3d::Vector3 gravity(0.0, -9.81, 0.0); -// Time step (in seconds) -rp3d::decimal timeStep = 1.0 / 60.0; - // Create the dynamics world -rp3d::DynamicsWorld world(gravity, timeStep); +rp3d::DynamicsWorld world(gravity); \end{lstlisting} \subsection{Customizing the Dynamics World} @@ -384,39 +380,67 @@ world.enableSleeping(false); \subsection{Updating the Dynamics World} - The first thing you have to do to simulate the dynamics of your world is to start the simulation using the following method: \\ + The \texttt{DynamicsWorld} is used to simulate physics through time. It has to be updated each time you want to simulate a step forward in time. Most of the time, + you want to update the world right before rendering a new frame in a real-time application. \\ + + To update the physics world, you need to use the \texttt{DynamicsWorld::update()} method. This method will perform collision detection and update the + position and orientation of the bodies and joints. After updating the world, you will be able to get the new position and orientation of your bodies for the next + frame to render. This method requires a \emph{timeStep} parameter. This is the amount of time you want to advance the physics simulation (in seconds). \\ + + The smaller the time step you pick, the more precise the simulation will be but it can also be more expensive to compute. For a real-time application, you + probably want a time step of at most $\frac{1}{60}$ seconds to + have at least a 60 Hz framerate. Most of the time, physics engines prefer to work with a constant time step. It means that you should always call + the \texttt{DynamicsWorld::update()} method with the same time step parameter. You do not want to use the time between two frames as your time step because it will + not be constant. \\ + + You can use the following technique. First, you choose a constant time step for the physics. Let say the time step is $\frac{1}{60}$ seconds. Then, at each frame, + you compute the time difference between the current frame and the previous one and you accumulate this difference in a variable called \emph{accumulator}. The accumulator + is initialized to zero at the beginning of your application and is updated at each frame. The idea is to divide the time in the accumulator in several constant time steps. + For instance, if your accumulator contains $0.145$ seconds, it means that we can take $8$ physics steps of $\frac{1}{60}$ seconds during the current frame. Note that + $0.012$ seconds will remain in the accumulator and will probably be used in the next frame. As you can see, multiple physics steps can be taken at each frame. It is + important to understand that each call to the \texttt{DynamicsWorld::update()} method is done using a constant time step that is not varying with the framerate. \\ + + Here is what the code looks like at each frame: \\ \begin{lstlisting} -// Start the simulation -world.start(); + +// Constant physics time step +const float timeStep = 1.0 / 60.0; + +// Get the current system time +long double currentFrameTime = getCurrentSystemTime(); + +// Compute the time difference between the two frames +long double deltaTime = currentFrameTime - previousFrameTime; + +// Update the previous time +previousFrameTime = currentFrameTime; + +// Add the time difference in the accumulator +accumulator += mDeltaTime; + +// While there is enough accumulated time to take +// one or several physics steps +while (accumulator >= timeStep) { + + // Update the Dynamics world with a constant time step + dynamicsWorld->update(timeStep); + + // Decrease the accumulated time + accumulator -= timeStep; +} + \end{lstlisting} - - \vspace{0.6cm} - - Then, each time you have to compute the next frame to render in your application, you need to update the state of the world. To do that, you simply need to call this method: \\ - - \begin{lstlisting} -// Update the world by taking a simulation step -world.update(); - \end{lstlisting} - - \vspace{0.6cm} - - When the \texttt{DynamicsWorld::update()} method is called, collision detection is performed and the position and orientation of the bodies are updated accordingly. - After updating the world, you will be able to get the updated position and orientation of the bodies for the next frame. Make sure that you call - the \texttt{DynamicsWorld::start()} method before calling the \texttt{DynamicsWorld::update()} method. \\ - - You can also use the \texttt{DynamicsWorld::stop()} method to stop the simulation. You will then be able to start it again and continue updating it. \\ - - Note that you can get the elapsed time (in seconds) from the beginning of the physics simulation using the \texttt{DynamicsWorld::getPhysicsTime()} method. - This can be useful to - create some animations. + \subsection{Destroying the Dynamics World} - Do not forget to destroy the \texttt{DynamicsWorld} instance at the end of your program in order to release the allocated memory. If the object has been created statically, it will - automatically be destroyed at the end of the scope in which it has been created. If the object has been created dynamically (using the \texttt{new} operator), you need to destroy - it with the \texttt{delete} operator. + Do not forget to destroy the \texttt{DynamicsWorld} instance at the end of your program in order to release the allocated memory. If the object has been created + statically, it will automatically be destroyed at the end of the scope in which it has been created. If the object has been created dynamically (using the + \texttt{new} operator), you need to destroy it with the \texttt{delete} operator. \\ + + When the \texttt{DynamicsWorld} is destroyed, all the bodies and joints that have been added into it and that have not been destroyed already will be destroyed. + Therefore, the pointers to the bodies and joints of the world will become invalid after the existence of their \texttt{DynamicsWorld}. \section{Rigid Bodies} \label{sec:rigidbody} @@ -1472,8 +1496,91 @@ bool isHit = proxyShape->raycast(ray, raycastInfo); In this example, you will see how to use the ray casting methods of the library. Several rays are thrown against the different collision shapes. It is possible to switch from a collision shape to another using the space key. - \section{Receiving Feedback} + \section{Retrieving contacts} + There are several ways to get the contacts information (contact point, normal, penetration depth, \dots) from the \texttt{DynamicsWorld}. \\ + + \subsection{Contacts of a given rigid body} + + If you are interested to retrieve all the contacts of a single rigid body, you can use the \texttt{RigidBody::getContactManifoldsList()} method. This method will + return a linked list with all the current contact manifolds of the body. A contact manifold can contains several contact points. \\ + + Here is an example showing how to get the contact points of a given rigid body: \\ + + \begin{lstlisting} +const ContactManifoldListElement* listElem; + +// Get the head of the linked list of contact manifolds of the body +listElem = rigidbody->getContactManifoldsList(); + +// For each contact manifold of the body +for (; listElem != NULL; listElem = listElem->next) { + ContactManifold* manifold = listElem->contactManifold; + + // For each contact point of the manifold + for (int i=0; igetNbContactPoints(); i++) { + + // Get the contact point + ContactPoint* point = manifold->getContactPoint(i); + + // Get the world-space contact point on body 1 + Vector3 pos = point->getWorldPointOnBody1(); + + // Get the world-space contact normal + Vector3 normal = point->getNormal(); + } +} + \end{lstlisting} + + \vspace{0.6cm} + + Note that this technique to retrieve the contacts, if you use it between the \texttt{DynamicsWorld::update()} calls, will only give you the contacts are the end of + each frame. You will probably miss several contacts that have occured in the physics internal sub-steps. In section \ref{sec:receiving_feedback}, you will + see how to get all the contact occuring in the physis sub-steps of the engine. Also note that a contact manifold contains some persistent contact points that + have may have been there for several frames. + + \subsection{All the contacts of the world} + + If you want to retrieve all the contacts of any rigid body in the world, you can use the \texttt{DynamicsWorld::getContactsList()} method. This method will + a \texttt{std::vector} with the list of all the current contact manifolds of the world. A contact manifold may contain several contact points. \\ + + The following example shows how to get all the contacts of the world using this method: \\ + + \begin{lstlisting} +std::vector manifolds; + +// Get all the contacts of the world +manifolds = dynamicsWorld->getContactsList(); +std::vector::iterator it; + +// For each contact manifold of the body +for (it = manifolds.begin(); it != manifolds.end(); ++it) { + ContactManifold* manifold = *it; + + // For each contact point of the manifold + for (int i=0; igetNbContactPoints(); i++) { + + // Get the contact point + ContactPoint* point = manifold->getContactPoint(i); + + // Get the world-space contact point on body 1 + Vector3 pos = point->getWorldPointOnBody1(); + + // Get the world-space contact normal + Vector3 normal = point->getNormal(); + } +} + \end{lstlisting} + + \vspace{0.6cm} + + Note that this technique to retrieve the contacts, if you use it between the \texttt{DynamicsWorld::update()} calls, will only give you the contacts are the end of + each frame. You will probably miss several contacts that have occured in the physics internal sub-steps. In section \ref{sec:receiving_feedback}, you will + see how to get all the contact occuring in the physis sub-steps of the engine. Also note that a contact manifold contains some persistent contact points that + have may have been there for several frames. + + \section{Receiving Feedback} + \label{sec:receiving_feedback} Sometimes, you want to receive notifications from the physics engine when a given event happens. The \texttt{EventListener} class can be used for that purpose. In order to use it, you need to create a new class that inherits from the \texttt{EventListener} class and overrides some methods that will be called by the ReactPhysics3D library when some events occur. You also need to register your class in the physics world using the \texttt{DynamicsWorld::setEventListener()} as in the following code: \\ diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt deleted file mode 100644 index 3743dc56..00000000 --- a/examples/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -# Minimum cmake version required -CMAKE_MINIMUM_REQUIRED(VERSION 2.6) - -# Set a variable for the directory of the opengl-framework -SET(OPENGLFRAMEWORK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/common/opengl-framework") - -ADD_SUBDIRECTORY(common/) -ADD_SUBDIRECTORY(cubes/) -ADD_SUBDIRECTORY(joints/) -ADD_SUBDIRECTORY(collisionshapes/) -ADD_SUBDIRECTORY(raycast/) diff --git a/examples/collisionshapes/CMakeLists.txt b/examples/collisionshapes/CMakeLists.txt deleted file mode 100644 index 6bdd5d3e..00000000 --- a/examples/collisionshapes/CMakeLists.txt +++ /dev/null @@ -1,42 +0,0 @@ -# Minimum cmake version required -cmake_minimum_required(VERSION 2.6) - -# Project configuration -PROJECT(CollisionShapes) - -# Where to build the executables -SET(EXECUTABLE_OUTPUT_PATH "${OUR_EXECUTABLE_OUTPUT_PATH}/collisionshapes") -SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${EXECUTABLE_OUTPUT_PATH}) -SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${EXECUTABLE_OUTPUT_PATH}) -SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${EXECUTABLE_OUTPUT_PATH}) - -# Copy the shaders used for the demo into the build directory -FILE(COPY "${OPENGLFRAMEWORK_DIR}/src/shaders/" DESTINATION "${EXECUTABLE_OUTPUT_PATH}/shaders/") - -# Copy the meshes used for the demo into the build directory -FILE(COPY "../common/meshes/" DESTINATION "${EXECUTABLE_OUTPUT_PATH}/meshes/") - -# Headers -INCLUDE_DIRECTORIES("${OPENGLFRAMEWORK_DIR}/src/" "../common/glfw/include/" "../common/") - -# Source files -SET(COLLISION_SHAPES_SOURCES - CollisionShapes.cpp - Scene.cpp - Scene.h - "../common/VisualContactPoint.cpp" - "../common/ConvexMesh.cpp" - "../common/Capsule.cpp" - "../common/Sphere.cpp" - "../common/Cylinder.cpp" - "../common/Cone.cpp" - "../common/Dumbbell.cpp" - "../common/Box.cpp" - "../common/Viewer.cpp" -) - -# Create the executable -ADD_EXECUTABLE(collisionshapes ${COLLISION_SHAPES_SOURCES}) - -# Link with libraries -TARGET_LINK_LIBRARIES(collisionshapes reactphysics3d openglframework glfw ${GLFW_LIBRARIES}) diff --git a/examples/collisionshapes/CollisionShapes.cpp b/examples/collisionshapes/CollisionShapes.cpp deleted file mode 100644 index f8affc1d..00000000 --- a/examples/collisionshapes/CollisionShapes.cpp +++ /dev/null @@ -1,152 +0,0 @@ -/******************************************************************************** -* ReactPhysics3D physics library, http://www.reactphysics3d.com * -* Copyright (c) 2010-2015 Daniel Chappuis * -********************************************************************************* -* * -* This software is provided 'as-is', without any express or implied warranty. * -* In no event will the authors be held liable for any damages arising from the * -* use of this software. * -* * -* Permission is granted to anyone to use this software for any purpose, * -* including commercial applications, and to alter it and redistribute it * -* freely, subject to the following restrictions: * -* * -* 1. The origin of this software must not be misrepresented; you must not claim * -* that you wrote the original software. If you use this software in a * -* product, an acknowledgment in the product documentation would be * -* appreciated but is not required. * -* * -* 2. Altered source versions must be plainly marked as such, and must not be * -* misrepresented as being the original software. * -* * -* 3. This notice may not be removed or altered from any source distribution. * -* * -********************************************************************************/ - -// Libraries -#include "Scene.h" -#include "../common/Viewer.h" - -// Declarations -void simulate(); -void render(); -void update(); -void mouseButton(GLFWwindow* window, int button, int action, int mods); -void mouseMotion(GLFWwindow* window, double x, double y); -void keyboard(GLFWwindow* window, int key, int scancode, int action, int mods); -void scroll(GLFWwindow* window, double xAxis, double yAxis); -void init(); - -// Namespaces -using namespace openglframework; - -// Global variables -Viewer* viewer; -Scene* scene; - -// Main function -int main(int argc, char** argv) { - - // Create and initialize the Viewer - viewer = new Viewer(); - Vector2 windowsSize = Vector2(800, 600); - Vector2 windowsPosition = Vector2(100, 100); - viewer->init(argc, argv, "ReactPhysics3D Examples - Collision Shapes", - windowsSize, windowsPosition, true); - - // If the shaders and meshes folders are not specified as an argument - if (argc < 3) { - std::cerr << "Error : You need to specify the shaders folder as the first argument" - << " and the meshes folder as the second argument" << std::endl; - return 1; - } - - // Get the path of the shaders folder - std::string shaderFolderPath(argv[1]); - std::string meshFolderPath(argv[2]); - - // Register callback methods - viewer->registerUpdateFunction(update); - viewer->registerKeyboardCallback(keyboard); - viewer->registerMouseButtonCallback(mouseButton); - viewer->registerMouseCursorCallback(mouseMotion); - viewer->registerScrollingCallback(scroll); - - // Create the scene - scene = new Scene(viewer, shaderFolderPath, meshFolderPath); - - init(); - - viewer->startMainLoop(); - - delete viewer; - delete scene; - - return 0; -} - -// Update function that is called each frame -void update() { - - // Take a simulation step - simulate(); - - // Render - render(); -} - -// Simulate function -void simulate() { - - // Physics simulation - scene->simulate(); - - viewer->computeFPS(); -} - -// Initialization -void init() { - - // Define the background color (black) - glClearColor(0.0, 0.0, 0.0, 1.0); -} - -// Callback method to receive keyboard events -void keyboard(GLFWwindow* window, int key, int scancode, int action, int mods) { - if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) { - glfwSetWindowShouldClose(window, GL_TRUE); - } - else if (key == GLFW_KEY_SPACE && action == GLFW_PRESS) { - scene->pauseContinueSimulation(); - } -} - -// Callback method to receive scrolling events -void scroll(GLFWwindow* window, double xAxis, double yAxis) { - viewer->scrollingEvent(static_cast(yAxis)); -} - -// Called when a mouse button event occurs -void mouseButton(GLFWwindow* window, int button, int action, int mods) { - viewer->mouseButtonEvent(button, action); -} - -// Called when a mouse motion event occurs -void mouseMotion(GLFWwindow* window, double x, double y) { - viewer->mouseMotionEvent(x, y); -} - -// Display the scene -void render() { - - // Render the scene - scene->render(); - - // Display the FPS - viewer->displayGUI(); - - // Check the OpenGL errors - Viewer::checkOpenGLErrors(); -} - - diff --git a/examples/common/CMakeLists.txt b/examples/common/CMakeLists.txt deleted file mode 100644 index 0c4f652b..00000000 --- a/examples/common/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -# Minimum cmake version required -CMAKE_MINIMUM_REQUIRED(VERSION 2.6) - -ADD_SUBDIRECTORY(opengl-framework/) -ADD_SUBDIRECTORY(glfw/) diff --git a/examples/common/VisualContactPoint.cpp b/examples/common/VisualContactPoint.cpp deleted file mode 100644 index 77337192..00000000 --- a/examples/common/VisualContactPoint.cpp +++ /dev/null @@ -1,115 +0,0 @@ -/******************************************************************************** -* ReactPhysics3D physics library, http://www.reactphysics3d.com * -* Copyright (c) 2010-2015 Daniel Chappuis * -********************************************************************************* -* * -* This software is provided 'as-is', without any express or implied warranty. * -* In no event will the authors be held liable for any damages arising from the * -* use of this software. * -* * -* Permission is granted to anyone to use this software for any purpose, * -* including commercial applications, and to alter it and redistribute it * -* freely, subject to the following restrictions: * -* * -* 1. The origin of this software must not be misrepresented; you must not claim * -* that you wrote the original software. If you use this software in a * -* product, an acknowledgment in the product documentation would be * -* appreciated but is not required. * -* * -* 2. Altered source versions must be plainly marked as such, and must not be * -* misrepresented as being the original software. * -* * -* 3. This notice may not be removed or altered from any source distribution. * -* * -********************************************************************************/ - -// Libraries -#include "VisualContactPoint.h" - -// Initialization of static variables -int VisualContactPoint::mNbTotalPoints = 0; -bool VisualContactPoint::mIsMeshInitialized = false; -openglframework::Mesh VisualContactPoint::mMesh; - -// Constructor -VisualContactPoint::VisualContactPoint(const openglframework::Vector3& position) { - - assert(mIsMeshInitialized); - - // Initialize the position where the sphere will be rendered - translateWorld(position); -} - -// Destructor -VisualContactPoint::~VisualContactPoint() { - -} - -// Load and initialize the mesh for all the contact points -void VisualContactPoint::createStaticData(const std::string& meshFolderPath) { - - if (!mIsMeshInitialized) { - - // Load the mesh from a file - openglframework::MeshReaderWriter::loadMeshFromFile(meshFolderPath + "sphere.obj", mMesh); - - // Calculate the normals of the mesh - mMesh.calculateNormals(); - - mMesh.scaleVertices(VISUAL_CONTACT_POINT_RADIUS); - - mIsMeshInitialized = true; - } -} - -// Destroy the mesh for the contact points -void VisualContactPoint::destroyStaticData() { - - mMesh.destroy(); - mIsMeshInitialized = false; -} - -// Render the sphere at the correct position and with the correct orientation -void VisualContactPoint::render(openglframework::Shader& shader, - const openglframework::Matrix4& worldToCameraMatrix) { - - // Bind the shader - shader.bind(); - - // Set the model to camera matrix - const openglframework::Matrix4 localToCameraMatrix = worldToCameraMatrix * mTransformMatrix; - shader.setMatrix4x4Uniform("localToCameraMatrix", localToCameraMatrix); - - // Set the normal matrix (inverse transpose of the 3x3 upper-left sub matrix of the - // model-view matrix) - const openglframework::Matrix3 normalMatrix = - localToCameraMatrix.getUpperLeft3x3Matrix().getInverse().getTranspose(); - shader.setMatrix3x3Uniform("normalMatrix", normalMatrix); - - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_NORMAL_ARRAY); - if (mMesh.hasTexture()) { - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - } - - glVertexPointer(3, GL_FLOAT, 0, mMesh.getVerticesPointer()); - glNormalPointer(GL_FLOAT, 0, mMesh.getNormalsPointer()); - if(mMesh.hasTexture()) { - glTexCoordPointer(2, GL_FLOAT, 0, mMesh.getUVTextureCoordinatesPointer()); - } - - // For each part of the mesh - for (unsigned int i=0; iinit(argc, argv, "ReactPhysics3D Examples - Cubes", windowsSize, windowsPosition, true); - - // If the shaders folder is not specified as an argument - if (argc < 2) { - std::cerr << "Error : You need to specify the shaders folder as argument !" << std::endl; - return 1; - } - - // Get the path of the shaders folder - std::string shaderFolderPath(argv[1]); - - // Register callback methods - viewer->registerUpdateFunction(update); - viewer->registerKeyboardCallback(keyboard); - viewer->registerMouseButtonCallback(mouseButton); - viewer->registerMouseCursorCallback(mouseMotion); - viewer->registerScrollingCallback(scroll); - - // Create the scene - scene = new Scene(viewer, shaderFolderPath); - - init(); - - viewer->startMainLoop(); - - delete viewer; - delete scene; - - return 0; -} - -// Update function that is called each frame -void update() { - - // Take a simulation step - simulate(); - - // Render - render(); -} - -// Simulate function -void simulate() { - - // Physics simulation - scene->simulate(); - - // Compute the current framerate - viewer->computeFPS(); -} - -// Initialization -void init() { - - // Define the background color (black) - glClearColor(0.0, 0.0, 0.0, 1.0); -} - -// Callback method to receive keyboard events -void keyboard(GLFWwindow* window, int key, int scancode, int action, int mods) { - if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) { - glfwSetWindowShouldClose(window, GL_TRUE); - } - else if (key == GLFW_KEY_SPACE && action == GLFW_PRESS) { - scene->pauseContinueSimulation(); - } -} - -// Callback method to receive scrolling events -void scroll(GLFWwindow* window, double xAxis, double yAxis) { - viewer->scrollingEvent(static_cast(yAxis)); -} - -// Called when a mouse button event occurs -void mouseButton(GLFWwindow* window, int button, int action, int mods) { - viewer->mouseButtonEvent(button, action); -} - -// Called when a mouse motion event occurs -void mouseMotion(GLFWwindow* window, double x, double y) { - viewer->mouseMotionEvent(x, y); -} - -// Display the scene -void render() { - - // Render the scene - scene->render(); - - // Display the FPS - viewer->displayGUI(); - - // Check the OpenGL errors - Viewer::checkOpenGLErrors(); -} diff --git a/examples/joints/CMakeLists.txt b/examples/joints/CMakeLists.txt deleted file mode 100644 index ba304b9d..00000000 --- a/examples/joints/CMakeLists.txt +++ /dev/null @@ -1,34 +0,0 @@ -# Minimum cmake version required -cmake_minimum_required(VERSION 2.6) - -# Project configuration -PROJECT(Joints) - -# Where to build the executables -SET(EXECUTABLE_OUTPUT_PATH "${OUR_EXECUTABLE_OUTPUT_PATH}/joints") -SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${EXECUTABLE_OUTPUT_PATH}) -SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${EXECUTABLE_OUTPUT_PATH}) -SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${EXECUTABLE_OUTPUT_PATH}) - -# Copy the shaders used for the demo into the build directory -FILE(COPY "${OPENGLFRAMEWORK_DIR}/src/shaders/" DESTINATION "${EXECUTABLE_OUTPUT_PATH}/shaders/") - -# Headers -INCLUDE_DIRECTORIES("${OPENGLFRAMEWORK_DIR}/src/" "../common/glfw/include/" "../common/") - -# Source files -SET(JOINTS_SOURCES - Joints.cpp - Scene.cpp - Scene.h - "../common/Box.cpp" - "../common/Box.h" - "../common/Viewer.cpp" - "../common/Viewer.h" -) - -# Create the executable -ADD_EXECUTABLE(joints ${JOINTS_SOURCES}) - -# Link with libraries -TARGET_LINK_LIBRARIES(joints reactphysics3d openglframework glfw ${GLFW_LIBRARIES}) diff --git a/examples/joints/Joints.cpp b/examples/joints/Joints.cpp deleted file mode 100644 index 1b1b7e00..00000000 --- a/examples/joints/Joints.cpp +++ /dev/null @@ -1,149 +0,0 @@ -/******************************************************************************** -* ReactPhysics3D physics library, http://www.reactphysics3d.com * -* Copyright (c) 2010-2015 Daniel Chappuis * -********************************************************************************* -* * -* This software is provided 'as-is', without any express or implied warranty. * -* In no event will the authors be held liable for any damages arising from the * -* use of this software. * -* * -* Permission is granted to anyone to use this software for any purpose, * -* including commercial applications, and to alter it and redistribute it * -* freely, subject to the following restrictions: * -* * -* 1. The origin of this software must not be misrepresented; you must not claim * -* that you wrote the original software. If you use this software in a * -* product, an acknowledgment in the product documentation would be * -* appreciated but is not required. * -* * -* 2. Altered source versions must be plainly marked as such, and must not be * -* misrepresented as being the original software. * -* * -* 3. This notice may not be removed or altered from any source distribution. * -* * -********************************************************************************/ - -// Libraries -#include "Scene.h" -#include "../common/Viewer.h" - -// Declarations -void simulate(); -void update(); -void render(); -void mouseButton(GLFWwindow* window, int button, int action, int mods); -void mouseMotion(GLFWwindow* window, double x, double y); -void keyboard(GLFWwindow* window, int key, int scancode, int action, int mods); -void scroll(GLFWwindow* window, double xAxis, double yAxis); -void init(); - -// Namespaces -using namespace openglframework; - -// Global variables -Viewer* viewer; -Scene* scene; - -// Main function -int main(int argc, char** argv) { - - // Create and initialize the Viewer - viewer = new Viewer(); - Vector2 windowsSize = Vector2(800, 600); - Vector2 windowsPosition = Vector2(100, 100); - viewer->init(argc, argv, "ReactPhysics3D Examples - Joints", windowsSize, windowsPosition, true); - - // If the shaders folder is not specified as an argument - if (argc < 2) { - std::cerr << "Error : You need to specify the shaders folder as argument !" << std::endl; - return 1; - } - - // Get the path of the shaders folder - std::string shaderFolderPath(argv[1]); - - // Register callback methods - viewer->registerUpdateFunction(update); - viewer->registerKeyboardCallback(keyboard); - viewer->registerMouseButtonCallback(mouseButton); - viewer->registerMouseCursorCallback(mouseMotion); - viewer->registerScrollingCallback(scroll); - - // Create the scene - scene = new Scene(viewer, shaderFolderPath); - - init(); - - viewer->startMainLoop(); - - delete viewer; - delete scene; - - return 0; -} - -// Update function that is called each frame -void update() { - - // Take a simulation step - simulate(); - - // Render - render(); -} - -// Simulate function -void simulate() { - - // Physics simulation - scene->simulate(); - - viewer->computeFPS(); -} - -// Initialization -void init() { - - // Define the background color (black) - glClearColor(0.0, 0.0, 0.0, 1.0); -} - -// Callback method to receive keyboard events -void keyboard(GLFWwindow* window, int key, int scancode, int action, int mods) { - if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) { - glfwSetWindowShouldClose(window, GL_TRUE); - } - else if (key == GLFW_KEY_SPACE && action == GLFW_PRESS) { - scene->pauseContinueSimulation(); - } -} - -// Callback method to receive scrolling events -void scroll(GLFWwindow* window, double xAxis, double yAxis) { - viewer->scrollingEvent(static_cast(yAxis)); -} - -// Called when a mouse button event occurs -void mouseButton(GLFWwindow* window, int button, int action, int mods) { - viewer->mouseButtonEvent(button, action); -} - -// Called when a mouse motion event occurs -void mouseMotion(GLFWwindow* window, double x, double y) { - viewer->mouseMotionEvent(x, y); -} - -// Display the scene -void render() { - - // Render the scene - scene->render(); - - // Display the FPS - viewer->displayGUI(); - - // Check the OpenGL errors - Viewer::checkOpenGLErrors(); -} - - diff --git a/examples/raycast/CMakeLists.txt b/examples/raycast/CMakeLists.txt deleted file mode 100644 index 01440b00..00000000 --- a/examples/raycast/CMakeLists.txt +++ /dev/null @@ -1,43 +0,0 @@ -# Minimum cmake version required -cmake_minimum_required(VERSION 2.6) - -# Project configuration -PROJECT(Raycast) - -# Where to build the executables -SET(EXECUTABLE_OUTPUT_PATH "${OUR_EXECUTABLE_OUTPUT_PATH}/raycast") -SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${EXECUTABLE_OUTPUT_PATH}) -SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${EXECUTABLE_OUTPUT_PATH}) -SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${EXECUTABLE_OUTPUT_PATH}) - -# Copy the shaders used for the demo into the build directory -FILE(COPY "${OPENGLFRAMEWORK_DIR}/src/shaders/" DESTINATION "${EXECUTABLE_OUTPUT_PATH}/shaders/") - -# Copy the meshes used for the demo into the build directory -FILE(COPY "../common/meshes/" DESTINATION "${EXECUTABLE_OUTPUT_PATH}/meshes/") - -# Headers -INCLUDE_DIRECTORIES("${OPENGLFRAMEWORK_DIR}/src/" "../common/glfw/include/" "../common/") - -# Source files -SET(RAYCAST_SOURCES - Raycast.cpp - Scene.cpp - Scene.h - "../common/VisualContactPoint.cpp" - "../common/ConvexMesh.cpp" - "../common/Capsule.cpp" - "../common/Sphere.cpp" - "../common/Line.cpp" - "../common/Cylinder.cpp" - "../common/Cone.cpp" - "../common/Dumbbell.cpp" - "../common/Box.cpp" - "../common/Viewer.cpp" -) - -# Create the executable -ADD_EXECUTABLE(raycast ${RAYCAST_SOURCES}) - -# Link with libraries -TARGET_LINK_LIBRARIES(raycast reactphysics3d openglframework glfw ${GLFW_LIBRARIES}) diff --git a/examples/raycast/Raycast.cpp b/examples/raycast/Raycast.cpp deleted file mode 100644 index e971069a..00000000 --- a/examples/raycast/Raycast.cpp +++ /dev/null @@ -1,155 +0,0 @@ -/******************************************************************************** -* ReactPhysics3D physics library, http://www.reactphysics3d.com * -* Copyright (c) 2010-2015 Daniel Chappuis * -********************************************************************************* -* * -* This software is provided 'as-is', without any express or implied warranty. * -* In no event will the authors be held liable for any damages arising from the * -* use of this software. * -* * -* Permission is granted to anyone to use this software for any purpose, * -* including commercial applications, and to alter it and redistribute it * -* freely, subject to the following restrictions: * -* * -* 1. The origin of this software must not be misrepresented; you must not claim * -* that you wrote the original software. If you use this software in a * -* product, an acknowledgment in the product documentation would be * -* appreciated but is not required. * -* * -* 2. Altered source versions must be plainly marked as such, and must not be * -* misrepresented as being the original software. * -* * -* 3. This notice may not be removed or altered from any source distribution. * -* * -********************************************************************************/ - -// Libraries -#include "Scene.h" -#include "../common/Viewer.h" - -// Declarations -void simulate(); -void render(); -void update(); -void mouseButton(GLFWwindow* window, int button, int action, int mods); -void mouseMotion(GLFWwindow* window, double x, double y); -void keyboard(GLFWwindow* window, int key, int scancode, int action, int mods); -void scroll(GLFWwindow* window, double xAxis, double yAxis); -void init(); - -// Namespaces -using namespace openglframework; - -// Global variables -Viewer* viewer; -Scene* scene; - -// Main function -int main(int argc, char** argv) { - - // Create and initialize the Viewer - viewer = new Viewer(); - Vector2 windowsSize = Vector2(800, 600); - Vector2 windowsPosition = Vector2(100, 100); - viewer->init(argc, argv, "ReactPhysics3D Examples - Raycast", - windowsSize, windowsPosition, true); - - // If the shaders and meshes folders are not specified as an argument - if (argc < 3) { - std::cerr << "Error : You need to specify the shaders folder as the first argument" - << " and the meshes folder as the second argument" << std::endl; - return 1; - } - - // Get the path of the shaders folder - std::string shaderFolderPath(argv[1]); - std::string meshFolderPath(argv[2]); - - // Register callback methods - viewer->registerUpdateFunction(update); - viewer->registerKeyboardCallback(keyboard); - viewer->registerMouseButtonCallback(mouseButton); - viewer->registerMouseCursorCallback(mouseMotion); - viewer->registerScrollingCallback(scroll); - - // Create the scene - scene = new Scene(viewer, shaderFolderPath, meshFolderPath); - - init(); - - viewer->startMainLoop(); - - delete viewer; - delete scene; - - return 0; -} - -// Update function that is called each frame -void update() { - - // Take a simulation step - simulate(); - - // Render - render(); -} - -// Simulate function -void simulate() { - - // Physics simulation - scene->simulate(); - - viewer->computeFPS(); -} - -// Initialization -void init() { - - // Define the background color (black) - glClearColor(0.0, 0.0, 0.0, 1.0); -} - -// Callback method to receive keyboard events -void keyboard(GLFWwindow* window, int key, int scancode, int action, int mods) { - if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) { - glfwSetWindowShouldClose(window, GL_TRUE); - } - if (key == GLFW_KEY_SPACE && action == GLFW_PRESS) { - scene->changeBody(); - } - if (key == GLFW_KEY_N && action == GLFW_PRESS) { - scene->showHideNormals(); - } -} - -// Callback method to receive scrolling events -void scroll(GLFWwindow* window, double xAxis, double yAxis) { - viewer->scrollingEvent(static_cast(yAxis)); -} - -// Called when a mouse button event occurs -void mouseButton(GLFWwindow* window, int button, int action, int mods) { - viewer->mouseButtonEvent(button, action); -} - -// Called when a mouse motion event occurs -void mouseMotion(GLFWwindow* window, double x, double y) { - viewer->mouseMotionEvent(x, y); -} - -// Display the scene -void render() { - - // Render the scene - scene->render(); - - // Display the FPS - viewer->displayGUI(); - - // Check the OpenGL errors - Viewer::checkOpenGLErrors(); -} - - diff --git a/src/body/Body.h b/src/body/Body.h index 946aa61f..e16a15c4 100644 --- a/src/body/Body.h +++ b/src/body/Body.h @@ -83,9 +83,6 @@ class Body { /// Private assignment operator Body& operator=(const Body& body); - /// Set the variable to know whether or not the body is sleeping - virtual void setIsSleeping(bool isSleeping); - public : // -------------------- Methods -------------------- // @@ -105,6 +102,9 @@ class Body { /// Set whether or not the body is allowed to go to sleep void setIsAllowedToSleep(bool isAllowedToSleep); + /// Set the variable to know whether or not the body is sleeping + virtual void setIsSleeping(bool isSleeping); + /// Return whether or not the body is sleeping bool isSleeping() const; diff --git a/src/body/CollisionBody.cpp b/src/body/CollisionBody.cpp index 0c782d31..779cc747 100644 --- a/src/body/CollisionBody.cpp +++ b/src/body/CollisionBody.cpp @@ -41,10 +41,6 @@ CollisionBody::CollisionBody(const Transform& transform, CollisionWorld& world, : Body(id), mType(DYNAMIC), mTransform(transform), mProxyCollisionShapes(NULL), mNbCollisionShapes(0), mContactManifoldsList(NULL), mWorld(world) { - mInterpolationFactor = 0.0; - - // Initialize the old transform - mOldTransform = transform; } // Destructor diff --git a/src/body/CollisionBody.h b/src/body/CollisionBody.h index c0d5547b..955a2130 100644 --- a/src/body/CollisionBody.h +++ b/src/body/CollisionBody.h @@ -73,12 +73,6 @@ class CollisionBody : public Body { /// Position and orientation of the body Transform mTransform; - /// Last position and orientation of the body - Transform mOldTransform; - - /// Interpolation factor used for the state interpolation - decimal mInterpolationFactor; - /// First element of the linked list of proxy collision shapes of this body ProxyShape* mProxyCollisionShapes; @@ -105,9 +99,6 @@ class CollisionBody : public Body { /// Remove all the collision shapes void removeAllCollisionShapes(); - /// Update the old transform with the current one. - void updateOldTransform(); - /// Update the broad-phase state for this body (because it has moved for instance) virtual void updateBroadPhaseState() const; @@ -118,9 +109,6 @@ class CollisionBody : public Body { /// Reset the mIsAlreadyInIsland variable of the body and contact manifolds int resetIsAlreadyInIslandAndCountManifolds(); - /// Set the interpolation factor of the body - void setInterpolationFactor(decimal factor); - public : // -------------------- Methods -------------------- // @@ -144,7 +132,7 @@ class CollisionBody : public Body { const Transform& getTransform() const; /// Set the current position and orientation - void setTransform(const Transform& transform); + virtual void setTransform(const Transform& transform); /// Add a collision shape to the body. virtual ProxyShape* addCollisionShape(const CollisionShape& collisionShape, @@ -153,9 +141,6 @@ class CollisionBody : public Body { /// Remove a collision shape from the body virtual void removeCollisionShape(const ProxyShape* proxyShape); - /// Return the interpolated transform for rendering - Transform getInterpolatedTransform() const; - /// Return the first element of the linked list of contact manifolds involving this body const ContactManifoldListElement* getContactManifoldsList() const; @@ -227,20 +212,6 @@ inline void CollisionBody::setType(BodyType type) { } } -// Return the interpolated transform for rendering -/** - * @return The current interpolated transformation (between previous and current frame) - */ -inline Transform CollisionBody::getInterpolatedTransform() const { - return Transform::interpolateTransforms(mOldTransform, mTransform, mInterpolationFactor); -} - -// Set the interpolation factor of the body -inline void CollisionBody::setInterpolationFactor(decimal factor) { - // Set the factor - mInterpolationFactor = factor; -} - // Return the current position and orientation /** * @return The current transformation of the body that transforms the local-space @@ -264,12 +235,6 @@ inline void CollisionBody::setTransform(const Transform& transform) { updateBroadPhaseState(); } -// Update the old transform with the current one. -/// This is used to compute the interpolated position and orientation of the body -inline void CollisionBody::updateOldTransform() { - mOldTransform = mTransform; -} - // Return the first element of the linked list of contact manifolds involving this body /** * @return A pointer to the first element of the linked-list with the contact diff --git a/src/body/RigidBody.cpp b/src/body/RigidBody.cpp index a2a70f74..a057ef08 100644 --- a/src/body/RigidBody.cpp +++ b/src/body/RigidBody.cpp @@ -265,6 +265,64 @@ void RigidBody::removeCollisionShape(const ProxyShape* proxyShape) { recomputeMassInformation(); } +// Set the linear velocity of the rigid body. +/** + * @param linearVelocity Linear velocity vector of the body + */ +void RigidBody::setLinearVelocity(const Vector3& linearVelocity) { + + // If it is a static body, we do nothing + if (mType == STATIC) return; + + // Update the linear velocity of the current body state + mLinearVelocity = linearVelocity; + + // If the linear velocity is not zero, awake the body + if (mLinearVelocity.lengthSquare() > decimal(0.0)) { + setIsSleeping(false); + } +} + +// Set the angular velocity. +/** +* @param angularVelocity The angular velocity vector of the body +*/ +void RigidBody::setAngularVelocity(const Vector3& angularVelocity) { + + // If it is a static body, we do nothing + if (mType == STATIC) return; + + // Set the angular velocity + mAngularVelocity = angularVelocity; + + // If the velocity is not zero, awake the body + if (mAngularVelocity.lengthSquare() > decimal(0.0)) { + setIsSleeping(false); + } +} + +// Set the current position and orientation +/** + * @param transform The transformation of the body that transforms the local-space + * of the body into world-space + */ +void RigidBody::setTransform(const Transform& transform) { + + // Update the transform of the body + mTransform = transform; + + const Vector3 oldCenterOfMass = mCenterOfMassWorld; + + // Compute the new center of mass in world-space coordinates + mCenterOfMassWorld = mTransform * mCenterOfMassLocal; + + // Update the linear velocity of the center of mass + mLinearVelocity += mAngularVelocity.cross(mCenterOfMassWorld - oldCenterOfMass); + + // Update the broad-phase state of the body + updateBroadPhaseState(); +} + // Recompute the center of mass, total mass and inertia tensor of the body using all // the collision shapes attached to the body. void RigidBody::recomputeMassInformation() { @@ -342,8 +400,13 @@ void RigidBody::updateBroadPhaseState() const { PROFILE("RigidBody::updateBroadPhaseState()"); +<<<<<<< HEAD DynamicsWorld& world = static_cast(mWorld); const Vector3 displacement = world.mTimer.getTimeStep() * mLinearVelocity; +======= + DynamicsWorld& world = dynamic_cast(mWorld); + const Vector3 displacement = world.mTimeStep* mLinearVelocity; +>>>>>>> 1bde11f245806de07a12b1cf6c33cdb83046b882 // For all the proxy collision shapes of the body for (ProxyShape* shape = mProxyCollisionShapes; shape != NULL; shape = shape->mNext) { diff --git a/src/body/RigidBody.h b/src/body/RigidBody.h index 964fa286..e8cc573b 100644 --- a/src/body/RigidBody.h +++ b/src/body/RigidBody.h @@ -118,9 +118,6 @@ class RigidBody : public CollisionBody { /// Update the broad-phase state for this body (because it has moved for instance) virtual void updateBroadPhaseState() const; - /// Set the variable to know whether or not the body is sleeping - virtual void setIsSleeping(bool isSleeping); - public : // -------------------- Methods -------------------- // @@ -134,6 +131,9 @@ class RigidBody : public CollisionBody { /// Set the type of the body (static, kinematic or dynamic) void setType(BodyType type); + /// Set the current position and orientation + virtual void setTransform(const Transform& transform); + /// Return the mass of the body decimal getMass() const; @@ -149,6 +149,9 @@ class RigidBody : public CollisionBody { /// Set the angular velocity. void setAngularVelocity(const Vector3& angularVelocity); + /// Set the variable to know whether or not the body is sleeping + virtual void setIsSleeping(bool isSleeping); + /// Return the local inertia tensor of the body (in body coordinates) const Matrix3x3& getInertiaTensorLocal() const; @@ -252,20 +255,6 @@ inline Vector3 RigidBody::getAngularVelocity() const { return mAngularVelocity; } -// Set the angular velocity. -/// You should only call this method for a kinematic body. Otherwise, it -/// will do nothing. -/** -* @param angularVelocity The angular velocity vector of the body -*/ -inline void RigidBody::setAngularVelocity(const Vector3& angularVelocity) { - - // If it is a kinematic body - if (mType == KINEMATIC) { - mAngularVelocity = angularVelocity; - } -} - // Return the local inertia tensor of the body (in local-space coordinates) /** * @return The 3x3 inertia tensor matrix of the body (in local-space coordinates) @@ -310,22 +299,6 @@ inline Matrix3x3 RigidBody::getInertiaTensorInverseWorld() const { mTransform.getOrientation().getMatrix().getTranspose(); } -// Set the linear velocity of the rigid body. -/// You should only call this method for a kinematic body. Otherwise, it -/// will do nothing. -/** - * @param linearVelocity Linear velocity vector of the body - */ -inline void RigidBody::setLinearVelocity(const Vector3& linearVelocity) { - - // If it is a kinematic body - if (mType == KINEMATIC) { - - // Update the linear velocity of the current body state - mLinearVelocity = linearVelocity; - } -} - // Return true if the gravity needs to be applied to this rigid body /** * @return True if the gravity is applied to the body diff --git a/src/configuration.h b/src/configuration.h index b4c91f62..50d1872c 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -83,9 +83,6 @@ const decimal PI = decimal(3.14159265); /// 2*Pi constant const decimal PI_TIMES_2 = decimal(6.28318530); -/// Default internal constant timestep in seconds -const decimal DEFAULT_TIMESTEP = decimal(1.0 / 60.0); - /// Default friction coefficient for a rigid body const decimal DEFAULT_FRICTION_COEFFICIENT = decimal(0.3); diff --git a/src/engine/CollisionWorld.cpp b/src/engine/CollisionWorld.cpp index c129c92a..41b8bf8b 100644 --- a/src/engine/CollisionWorld.cpp +++ b/src/engine/CollisionWorld.cpp @@ -40,6 +40,15 @@ CollisionWorld::CollisionWorld() // Destructor CollisionWorld::~CollisionWorld() { + + // Destroy all the collision bodies that have not been removed + std::set::iterator itBodies; + for (itBodies = mBodies.begin(); itBodies != mBodies.end(); ) { + std::set::iterator itToRemove = itBodies; + ++itBodies; + destroyCollisionBody(*itToRemove); + } + assert(mCollisionShapes.empty()); assert(mBodies.empty()); } diff --git a/src/engine/DynamicsWorld.cpp b/src/engine/DynamicsWorld.cpp index 159f84cf..2c5f7359 100644 --- a/src/engine/DynamicsWorld.cpp +++ b/src/engine/DynamicsWorld.cpp @@ -37,10 +37,9 @@ using namespace std; // Constructor /** * @param gravity Gravity vector in the world (in meters per second squared) - * @param timeStep Time step for an internal physics tick (in seconds) */ -DynamicsWorld::DynamicsWorld(const Vector3 &gravity, decimal timeStep = DEFAULT_TIMESTEP) - : CollisionWorld(), mTimer(timeStep), +DynamicsWorld::DynamicsWorld(const Vector3 &gravity) + : CollisionWorld(), mContactSolver(mMapBodyToConstrainedVelocityIndex), mConstraintSolver(mMapBodyToConstrainedVelocityIndex), mNbVelocitySolverIterations(DEFAULT_VELOCITY_SOLVER_NB_ITERATIONS), @@ -60,6 +59,22 @@ DynamicsWorld::DynamicsWorld(const Vector3 &gravity, decimal timeStep = DEFAULT_ // Destructor DynamicsWorld::~DynamicsWorld() { + // Destroy all the joints that have not been removed + std::set::iterator itJoints; + for (itJoints = mJoints.begin(); itJoints != mJoints.end();) { + std::set::iterator itToRemove = itJoints; + ++itJoints; + destroyJoint(*itToRemove); + } + + // Destroy all the rigid bodies that have not been removed + std::set::iterator itRigidBodies; + for (itRigidBodies = mRigidBodies.begin(); itRigidBodies != mRigidBodies.end();) { + std::set::iterator itToRemove = itRigidBodies; + ++itRigidBodies; + destroyRigidBody(*itToRemove); + } + // Release the memory allocated for the islands for (uint i=0; ibeginInternalTick(); - // Notify the event listener about the beginning of an internal tick - if (mEventListener != NULL) mEventListener->beginInternalTick(); + // Reset all the contact manifolds lists of each body + resetContactManifoldListsOfBodies(); - // Reset all the contact manifolds lists of each body - resetContactManifoldListsOfBodies(); - - // Compute the collision detection - mCollisionDetection.computeCollisionDetection(); + // Compute the collision detection + mCollisionDetection.computeCollisionDetection(); - // Compute the islands (separate groups of bodies with constraints between each others) - computeIslands(); + // Compute the islands (separate groups of bodies with constraints between each others) + computeIslands(); - // Integrate the velocities - integrateRigidBodiesVelocities(); + // Integrate the velocities + integrateRigidBodiesVelocities(); - // Update the timer - mTimer.nextStep(); + // Solve the contacts and constraints + solveContactsAndConstraints(); - // Solve the contacts and constraints - solveContactsAndConstraints(); + // Integrate the position and orientation of each body + integrateRigidBodiesPositions(); - // Integrate the position and orientation of each body - integrateRigidBodiesPositions(); + // Solve the position correction for constraints + solvePositionCorrection(); - // Solve the position correction for constraints - solvePositionCorrection(); + // Update the state (positions and velocities) of the bodies + updateBodiesState(); - // Update the state (positions and velocities) of the bodies - updateBodiesState(); + if (mIsSleepingEnabled) updateSleepingBodies(); - if (mIsSleepingEnabled) updateSleepingBodies(); - - // Notify the event listener about the end of an internal tick - if (mEventListener != NULL) mEventListener->endInternalTick(); - } + // Notify the event listener about the end of an internal tick + if (mEventListener != NULL) mEventListener->endInternalTick(); // Reset the external force and torque applied to the bodies resetBodiesForceAndTorque(); - - // Compute and set the interpolation factor to all the bodies - setInterpolationFactorToAllBodies(); } // Integrate position and orientation of the rigid bodies. @@ -161,8 +169,6 @@ void DynamicsWorld::update() { void DynamicsWorld::integrateRigidBodiesPositions() { PROFILE("DynamicsWorld::integrateRigidBodiesPositions()"); - - decimal dt = static_cast(mTimer.getTimeStep()); // For each island of the world for (uint i=0; i < mNbIslands; i++) { @@ -190,10 +196,10 @@ void DynamicsWorld::integrateRigidBodiesPositions() { const Quaternion& currentOrientation = bodies[b]->getTransform().getOrientation(); // Update the new constrained position and orientation of the body - mConstrainedPositions[indexArray] = currentPosition + newLinVelocity * dt; + mConstrainedPositions[indexArray] = currentPosition + newLinVelocity * mTimeStep; mConstrainedOrientations[indexArray] = currentOrientation + Quaternion(0, newAngVelocity) * - currentOrientation * decimal(0.5) * dt; + currentOrientation * decimal(0.5) * mTimeStep; } } } @@ -232,23 +238,6 @@ void DynamicsWorld::updateBodiesState() { } } -// Compute and set the interpolation factor to all bodies -void DynamicsWorld::setInterpolationFactorToAllBodies() { - - PROFILE("DynamicsWorld::setInterpolationFactorToAllBodies()"); - - // Compute the interpolation factor - decimal factor = mTimer.computeInterpolationFactor(); - assert(factor >= 0.0 && factor <= 1.0); - - // Set the factor to all bodies - set::iterator it; - for (it = mRigidBodies.begin(); it != mRigidBodies.end(); ++it) { - - (*it)->setInterpolationFactor(factor); - } -} - // Initialize the bodies velocities arrays for the next simulation step. void DynamicsWorld::initVelocityArrays() { @@ -305,8 +294,6 @@ void DynamicsWorld::integrateRigidBodiesVelocities() { // Initialize the bodies velocity arrays initVelocityArrays(); - decimal dt = static_cast(mTimer.getTimeStep()); - // For each island of the world for (uint i=0; i < mNbIslands; i++) { @@ -323,16 +310,16 @@ void DynamicsWorld::integrateRigidBodiesVelocities() { // Integrate the external force to get the new velocity of the body mConstrainedLinearVelocities[indexBody] = bodies[b]->getLinearVelocity() + - dt * bodies[b]->mMassInverse * bodies[b]->mExternalForce; + mTimeStep * bodies[b]->mMassInverse * bodies[b]->mExternalForce; mConstrainedAngularVelocities[indexBody] = bodies[b]->getAngularVelocity() + - dt * bodies[b]->getInertiaTensorInverseWorld() * + mTimeStep * bodies[b]->getInertiaTensorInverseWorld() * bodies[b]->mExternalTorque; // If the gravity has to be applied to this rigid body if (bodies[b]->isGravityEnabled() && mIsGravityEnabled) { // Integrate the gravity force - mConstrainedLinearVelocities[indexBody] += dt * bodies[b]->mMassInverse * + mConstrainedLinearVelocities[indexBody] += mTimeStep * bodies[b]->mMassInverse * bodies[b]->getMass() * mGravity; } @@ -351,18 +338,15 @@ void DynamicsWorld::integrateRigidBodiesVelocities() { // => v2 = v1 * (1 - c * dt) decimal linDampingFactor = bodies[b]->getLinearDamping(); decimal angDampingFactor = bodies[b]->getAngularDamping(); - decimal linearDamping = clamp(decimal(1.0) - dt * linDampingFactor, + decimal linearDamping = clamp(decimal(1.0) - mTimeStep * linDampingFactor, decimal(0.0), decimal(1.0)); - decimal angularDamping = clamp(decimal(1.0) - dt * angDampingFactor, + decimal angularDamping = clamp(decimal(1.0) - mTimeStep * angDampingFactor, decimal(0.0), decimal(1.0)); mConstrainedLinearVelocities[indexBody] *= clamp(linearDamping, decimal(0.0), decimal(1.0)); mConstrainedAngularVelocities[indexBody] *= clamp(angularDamping, decimal(0.0), decimal(1.0)); - // Update the old Transform of the body - bodies[b]->updateOldTransform(); - indexBody++; } } @@ -373,9 +357,6 @@ void DynamicsWorld::solveContactsAndConstraints() { PROFILE("DynamicsWorld::solveContactsAndConstraints()"); - // Get the current time step - decimal dt = static_cast(mTimer.getTimeStep()); - // Set the velocities arrays mContactSolver.setSplitVelocitiesArrays(mSplitLinearVelocities, mSplitAngularVelocities); mContactSolver.setConstrainedVelocitiesArrays(mConstrainedLinearVelocities, @@ -399,7 +380,7 @@ void DynamicsWorld::solveContactsAndConstraints() { if (isContactsToSolve) { // Initialize the solver - mContactSolver.initializeForIsland(dt, mIslands[islandIndex]); + mContactSolver.initializeForIsland(mTimeStep, mIslands[islandIndex]); // Warm start the contact solver mContactSolver.warmStart(); @@ -409,7 +390,7 @@ void DynamicsWorld::solveContactsAndConstraints() { if (isConstraintsToSolve) { // Initialize the constraint solver - mConstraintSolver.initializeForIsland(dt, mIslands[islandIndex]); + mConstraintSolver.initializeForIsland(mTimeStep, mIslands[islandIndex]); } // For each iteration of the velocity solver @@ -811,7 +792,6 @@ void DynamicsWorld::updateSleepingBodies() { PROFILE("DynamicsWorld::updateSleepingBodies()"); - const decimal dt = static_cast(mTimer.getTimeStep()); const decimal sleepLinearVelocitySquare = mSleepLinearVelocity * mSleepLinearVelocity; const decimal sleepAngularVelocitySquare = mSleepAngularVelocity * mSleepAngularVelocity; @@ -839,7 +819,7 @@ void DynamicsWorld::updateSleepingBodies() { else { // If the body velocity is bellow the sleeping velocity threshold // Increase the sleep time - bodies[b]->mSleepTime += dt; + bodies[b]->mSleepTime += mTimeStep; if (bodies[b]->mSleepTime < minSleepTime) { minSleepTime = bodies[b]->mSleepTime; } @@ -991,3 +971,23 @@ void DynamicsWorld::testCollision(CollisionCallback* callback) { // Perform the collision detection and report contacts mCollisionDetection.reportCollisionBetweenShapes(callback, emptySet, emptySet); } + +/// Return the list of all contacts of the world +std::vector DynamicsWorld::getContactsList() const { + + std::vector contactManifolds; + + // For each currently overlapping pair of bodies + std::map::const_iterator it; + for (it = mCollisionDetection.mOverlappingPairs.begin(); + it != mCollisionDetection.mOverlappingPairs.end(); ++it) { + + OverlappingPair* pair = it->second; + + // Get the contact manifold + contactManifolds.push_back(pair->getContactManifold()); + } + + // Return all the contact manifold + return contactManifolds; +} diff --git a/src/engine/DynamicsWorld.h b/src/engine/DynamicsWorld.h index b03df610..519ff6d9 100644 --- a/src/engine/DynamicsWorld.h +++ b/src/engine/DynamicsWorld.h @@ -32,7 +32,6 @@ #include "ContactSolver.h" #include "ConstraintSolver.h" #include "body/RigidBody.h" -#include "Timer.h" #include "Island.h" #include "configuration.h" @@ -51,9 +50,6 @@ class DynamicsWorld : public CollisionWorld { // -------------------- Attributes -------------------- // - /// Timer of the physics engine - Timer mTimer; - /// Contact solver ContactSolver mContactSolver; @@ -78,6 +74,9 @@ class DynamicsWorld : public CollisionWorld { /// Gravity vector of the world Vector3 mGravity; + /// Current frame time step (in seconds) + decimal mTimeStep; + /// True if the gravity force is on bool mIsGravityEnabled; @@ -182,23 +181,23 @@ class DynamicsWorld : public CollisionWorld { // -------------------- Methods -------------------- // /// Constructor - DynamicsWorld(const Vector3& mGravity, decimal timeStep); + DynamicsWorld(const Vector3& mGravity); /// Destructor virtual ~DynamicsWorld(); - /// Start the physics simulation - void start(); - - /// Stop the physics simulation - void stop(); - /// Update the physics simulation - void update(); + void update(decimal timeStep); + + /// Get the number of iterations for the velocity constraint solver + uint getNbIterationsVelocitySolver() const; /// Set the number of iterations for the velocity constraint solver void setNbIterationsVelocitySolver(uint nbIterations); + /// Get the number of iterations for the position constraint solver + uint getNbIterationsPositionSolver() const; + /// Set the number of iterations for the position constraint solver void setNbIterationsPositionSolver(uint nbIterations); @@ -227,6 +226,9 @@ class DynamicsWorld : public CollisionWorld { /// Return the gravity vector of the world Vector3 getGravity() const; + /// Set the gravity vector of the world + void setGravity(Vector3& gravity); + /// Return if the gravity is on bool isGravityEnabled() const; @@ -239,9 +241,6 @@ class DynamicsWorld : public CollisionWorld { /// Return the number of joints in the world uint getNbJoints() const; - /// Return the current physics time (in seconds) - long double getPhysicsTime() const; - /// Return an iterator to the beginning of the rigid bodies of the physics world std::set::iterator getRigidBodiesBeginIterator(); @@ -298,6 +297,9 @@ class DynamicsWorld : public CollisionWorld { /// Test and report collisions between all shapes of the world virtual void testCollision(CollisionCallback* callback); + /// Return the list of all contacts of the world + std::vector getContactsList() const; + // -------------------- Friendship -------------------- // friend class RigidBody; @@ -314,15 +316,11 @@ inline void DynamicsWorld::resetBodiesForceAndTorque() { } } -// Start the physics simulation -inline void DynamicsWorld::start() { - mTimer.start(); +// Get the number of iterations for the velocity constraint solver +inline uint DynamicsWorld::getNbIterationsVelocitySolver() const { + return mNbVelocitySolverIterations; } -inline void DynamicsWorld::stop() { - mTimer.stop(); -} - // Set the number of iterations for the velocity constraint solver /** * @param nbIterations Number of iterations for the velocity solver @@ -331,6 +329,11 @@ inline void DynamicsWorld::setNbIterationsVelocitySolver(uint nbIterations) { mNbVelocitySolverIterations = nbIterations; } +// Get the number of iterations for the position constraint solver +inline uint DynamicsWorld::getNbIterationsPositionSolver() const { + return mNbPositionSolverIterations; +} + // Set the number of iterations for the position constraint solver /** * @param nbIterations Number of iterations for the position solver @@ -385,6 +388,14 @@ inline Vector3 DynamicsWorld::getGravity() const { return mGravity; } +// Set the gravity vector of the world +/** + * @param gravity The gravity vector (in meter per seconds squared) + */ +inline void DynamicsWorld::setGravity(Vector3& gravity) { + mGravity = gravity; +} + // Return if the gravity is enaled /** * @return True if the gravity is enabled in the world @@ -434,14 +445,6 @@ inline std::set::iterator DynamicsWorld::getRigidBodiesEndIterator() return mRigidBodies.end(); } -// Return the current physics time (in seconds) -/** - * @return The current physics time (in seconds) - */ -inline long double DynamicsWorld::getPhysicsTime() const { - return mTimer.getPhysicsTime(); -} - // Return true if the sleeping technique is enabled /** * @return True if the sleeping technique is enabled and false otherwise diff --git a/src/mathematics/Vector2.cpp b/src/mathematics/Vector2.cpp index ccad70dc..80c0ca34 100644 --- a/src/mathematics/Vector2.cpp +++ b/src/mathematics/Vector2.cpp @@ -54,7 +54,9 @@ Vector2::~Vector2() { Vector2 Vector2::getUnit() const { decimal lengthVector = length(); - assert(lengthVector > MACHINE_EPSILON); + if (lengthVector < MACHINE_EPSILON) { + return *this; + } // Compute and return the unit vector decimal lengthInv = decimal(1.0) / lengthVector; diff --git a/src/mathematics/Vector2.h b/src/mathematics/Vector2.h index 666d60c7..1083d26a 100644 --- a/src/mathematics/Vector2.h +++ b/src/mathematics/Vector2.h @@ -178,7 +178,9 @@ inline decimal Vector2::dot(const Vector2& vector) const { // Normalize the vector inline void Vector2::normalize() { decimal l = length(); - assert(l > std::numeric_limits::epsilon()); + if (l < MACHINE_EPSILON) { + return; + } x /= l; y /= l; } diff --git a/src/mathematics/Vector3.cpp b/src/mathematics/Vector3.cpp index 1e33136a..48bb714f 100644 --- a/src/mathematics/Vector3.cpp +++ b/src/mathematics/Vector3.cpp @@ -55,7 +55,9 @@ Vector3::~Vector3() { Vector3 Vector3::getUnit() const { decimal lengthVector = length(); - assert(lengthVector > MACHINE_EPSILON); + if (lengthVector < MACHINE_EPSILON) { + return *this; + } // Compute and return the unit vector decimal lengthInv = decimal(1.0) / lengthVector; diff --git a/src/mathematics/Vector3.h b/src/mathematics/Vector3.h index f8710fe3..f8ffb77f 100644 --- a/src/mathematics/Vector3.h +++ b/src/mathematics/Vector3.h @@ -193,7 +193,9 @@ inline Vector3 Vector3::cross(const Vector3& vector) const { // Normalize the vector inline void Vector3::normalize() { decimal l = length(); - assert(l > std::numeric_limits::epsilon()); + if (l < MACHINE_EPSILON) { + return; + } x /= l; y /= l; z /= l; diff --git a/testbed/CMakeLists.txt b/testbed/CMakeLists.txt new file mode 100644 index 00000000..cd66c695 --- /dev/null +++ b/testbed/CMakeLists.txt @@ -0,0 +1,97 @@ +# Minimum cmake version required +cmake_minimum_required(VERSION 2.6) + +# Project configuration +PROJECT(Testbed) + +# Where to build the executables +SET(EXECUTABLE_OUTPUT_PATH "${OUR_EXECUTABLE_OUTPUT_PATH}/testbed") +SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${EXECUTABLE_OUTPUT_PATH}) +SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${EXECUTABLE_OUTPUT_PATH}) +SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${EXECUTABLE_OUTPUT_PATH}) + +ADD_SUBDIRECTORY(opengl-framework/) +ADD_SUBDIRECTORY(glfw/) + +# Copy the shaders used for the demo into the build directory +FILE(COPY "shaders/" DESTINATION "${EXECUTABLE_OUTPUT_PATH}/shaders/") + +# Copy the meshes used for the demo into the build directory +FILE(COPY "meshes/" DESTINATION "${EXECUTABLE_OUTPUT_PATH}/meshes/") + +# Copy the fonts used for the GUI into the build directory +FILE(COPY "imgui/DroidSans.ttf" DESTINATION "${EXECUTABLE_OUTPUT_PATH}") + +# Enable C++11 features +SET(CMAKE_CXX_FLAGS "-std=c++0x") + +#ADD_DEFINITIONS(-DGL3) + +# Headers +INCLUDE_DIRECTORIES(${GLEW_INCLUDE_PATH} "src/" "opengl-framework/src/" "glfw/include/" "common/" "scenes/" "imgui/") + +# Testbed source files +SET(TESTBED_SOURCES + src/Main.cpp + src/TestbedApplication.h + src/TestbedApplication.cpp + src/Gui.h + src/Gui.cpp + src/Scene.h + src/Scene.cpp + src/SceneDemo.h + src/SceneDemo.cpp + src/Timer.h + src/Timer.cpp +) + +# IMGUI source files +SET(IMGUI_SOURCES + imgui/imgui.cpp + imgui/imgui.h + imgui/imguiRenderGL3.h + imgui/imguiRenderGL3.cpp + imgui/stb_truetype.h +) + +# Common source files +SET(COMMON_SOURCES + common/Box.h + common/Box.cpp + common/Cone.h + common/Cone.cpp + common/Sphere.h + common/Sphere.cpp + common/Line.h + common/Line.cpp + common/Capsule.h + common/Capsule.cpp + common/ConvexMesh.h + common/ConvexMesh.cpp + common/Cylinder.h + common/Cylinder.cpp + common/Dumbbell.h + common/Dumbbell.cpp + common/PhysicsObject.h + common/PhysicsObject.cpp + common/VisualContactPoint.h + common/VisualContactPoint.cpp +) + +# Examples scenes source files +SET(SCENES_SOURCES + scenes/cubes/CubesScene.h + scenes/cubes/CubesScene.cpp + scenes/joints/JointsScene.h + scenes/joints/JointsScene.cpp + scenes/raycast/RaycastScene.h + scenes/raycast/RaycastScene.cpp + scenes/collisionshapes/CollisionShapesScene.h + scenes/collisionshapes/CollisionShapesScene.cpp +) + +# Create the executable +ADD_EXECUTABLE(testbed ${TESTBED_SOURCES} ${SCENES_SOURCES} ${COMMON_SOURCES} ${IMGUI_SOURCES}) + +# Link with libraries +TARGET_LINK_LIBRARIES(testbed reactphysics3d openglframework glfw ${GLFW_LIBRARIES}) diff --git a/examples/common/Box.cpp b/testbed/common/Box.cpp similarity index 56% rename from examples/common/Box.cpp rename to testbed/common/Box.cpp index d0c7a1df..dc5e9d94 100644 --- a/examples/common/Box.cpp +++ b/testbed/common/Box.cpp @@ -31,35 +31,89 @@ // Initialize static variables openglframework::VertexBufferObject Box::mVBOVertices(GL_ARRAY_BUFFER); -openglframework::VertexBufferObject Box::mVBOIndices(GL_ELEMENT_ARRAY_BUFFER); -bool Box::areVBOsCreated = false; -VertexData Box::mCubeVertices[8] = { - {openglframework::Vector3(1,1,1),openglframework::Vector3(1,1,1),openglframework::Color(1,0,0,1)}, - {openglframework::Vector3(-1,1,1),openglframework::Vector3(-1,1,1),openglframework::Color(1,0,0,1)}, - {openglframework::Vector3(-1,-1,1),openglframework::Vector3(-1,-1,1),openglframework::Color(1,0,0,1)}, - {openglframework::Vector3(1,-1,1),openglframework::Vector3(1,-1,1),openglframework::Color(1,0,0,1)}, - {openglframework::Vector3(1,-1,-1),openglframework::Vector3(1,-1,-1),openglframework::Color(1,0,0,1)}, - {openglframework::Vector3(-1,-1,-1),openglframework::Vector3(-1,-1,-1),openglframework::Color(1,0,0,1)}, - {openglframework::Vector3(-1,1,-1),openglframework::Vector3(-1,1,-1),openglframework::Color(1,0,0,1)}, - {openglframework::Vector3(1,1,-1),openglframework::Vector3(1,1,-1),openglframework::Color(1,0,0,1)} +openglframework::VertexBufferObject Box::mVBONormals(GL_ARRAY_BUFFER); +openglframework::VertexArrayObject Box::mVAO; +int Box::totalNbBoxes = 0; +GLfloat Box::mCubeVertices[108] = { + -1.0f,-1.0f,-1.0f, // triangle 1 : begin + -1.0f,-1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, // triangle 1 : end + 1.0f, 1.0f,-1.0f, // triangle 2 : begin + -1.0f,-1.0f,-1.0f, + -1.0f, 1.0f,-1.0f, // triangle 2 : end + 1.0f,-1.0f, 1.0f, + -1.0f,-1.0f,-1.0f, + 1.0f,-1.0f,-1.0f, + 1.0f, 1.0f,-1.0f, + 1.0f,-1.0f,-1.0f, + -1.0f,-1.0f,-1.0f, + -1.0f,-1.0f,-1.0f, + -1.0f, 1.0f, 1.0f, + -1.0f, 1.0f,-1.0f, + 1.0f,-1.0f, 1.0f, + -1.0f,-1.0f, 1.0f, + -1.0f,-1.0f,-1.0f, + -1.0f, 1.0f, 1.0f, + -1.0f,-1.0f, 1.0f, + 1.0f,-1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f,-1.0f,-1.0f, + 1.0f, 1.0f,-1.0f, + 1.0f,-1.0f,-1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f,-1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f,-1.0f, + -1.0f, 1.0f,-1.0f, + 1.0f, 1.0f, 1.0f, + -1.0f, 1.0f,-1.0f, + -1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, + 1.0f,-1.0f, 1.0f +}; +GLfloat Box::mCubeNormals[108] = { + -1.0f, 0.0f, 0.0f, // triangle 1 : begin + -1.0f, 0.0f, 0.0f, + -1.0f, 0.0f, 0.0f, // triangle 1 : end + 0.0f, 0.0f,-1.0f, // triangle 2 : begin + 0.0f, 0.0f,-1.0f, + 0.0f, 0.0f,-1.0f, // triangle 2 : end + 0.0f,-1.0f, 0.0f, + 0.0f,-1.0f, 0.0f, + 0.0f,-1.0f, 0.0f,// + 0.0f, 0.0f,-1.0f, + 0.0f, 0.0f,-1.0f, + 0.0f, 0.0f,-1.0f,// + -1.0f, 0.0f, 0.0f, + -1.0f, 0.0f, 0.0f, + -1.0f, 0.0f,0.0f,// + 0.0f,-1.0f, 0.0f, + 0.0f,-1.0f, 0.0f, + 0.0f,-1.0f, 0.0f,// + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f,// + 1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f,// + 1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f,// + 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 0.0f,// + 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 0.0f,// + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f// }; -GLuint Box::mCubeIndices[36] = { 0, 1, 2, - 2, 3, 0, - 7, 4, 5, - 5, 6, 7, - 6, 5, 2, - 2, 1, 6, - 7, 0, 3, - 3, 4, 7, - 7, 6, 1, - 1, 0, 7, - 3, 2, 5, - 5, 4, 3}; - // Constructor Box::Box(const openglframework::Vector3& size, const openglframework::Vector3 &position, reactphysics3d::CollisionWorld* world) - : openglframework::Object3D(), mColor(0.5f, 0.5f, 0.5f, 1.0f) { + : openglframework::Object3D() { // Initialize the size of the box mSize[0] = size.x * 0.5f; @@ -85,25 +139,30 @@ Box::Box(const openglframework::Vector3& size, const openglframework::Vector3 &p rp3d::Quaternion initOrientation = rp3d::Quaternion::identity(); rp3d::Transform transform(initPosition, initOrientation); + mPreviousTransform = transform; + // Create a rigid body in the dynamics world - mRigidBody = world->createCollisionBody(transform); + mBody = world->createCollisionBody(transform); // Add the collision shape to the body - mRigidBody->addCollisionShape(collisionShape, rp3d::Transform::identity()); + mBody->addCollisionShape(collisionShape, rp3d::Transform::identity()); // If the Vertex Buffer object has not been created yet - if (!areVBOsCreated) { + if (totalNbBoxes == 0) { + // Create the Vertex Buffer - createVBO(); + createVBOAndVAO(); } + totalNbBoxes++; + mTransformMatrix = mTransformMatrix * mScalingMatrix; } // Constructor -Box::Box(const openglframework::Vector3& size, const openglframework::Vector3 &position, +Box::Box(const openglframework::Vector3& size, const openglframework::Vector3& position, float mass, reactphysics3d::DynamicsWorld* world) - : openglframework::Object3D(), mColor(0.5f, 0.5f, 0.5f, 1.0f) { + : openglframework::Object3D() { // Initialize the size of the box mSize[0] = size.x * 0.5f; @@ -129,111 +188,145 @@ Box::Box(const openglframework::Vector3& size, const openglframework::Vector3 &p rp3d::Quaternion initOrientation = rp3d::Quaternion::identity(); rp3d::Transform transform(initPosition, initOrientation); + mPreviousTransform = transform; + // Create a rigid body in the dynamics world rp3d::RigidBody* body = world->createRigidBody(transform); // Add the collision shape to the body body->addCollisionShape(collisionShape, rp3d::Transform::identity(), mass); - mRigidBody = body; + mBody = body; // If the Vertex Buffer object has not been created yet - if (!areVBOsCreated) { + if (totalNbBoxes == 0) { + // Create the Vertex Buffer - createVBO(); + createVBOAndVAO(); } + totalNbBoxes++; + mTransformMatrix = mTransformMatrix * mScalingMatrix; } // Destructor Box::~Box() { + if (totalNbBoxes == 1) { + + // Destroy the VBOs and VAO + mVBOVertices.destroy(); + mVBONormals.destroy(); + mVAO.destroy(); + } + + totalNbBoxes--; } // Render the cube at the correct position and with the correct orientation void Box::render(openglframework::Shader& shader, const openglframework::Matrix4& worldToCameraMatrix) { + // Bind the VAO + mVAO.bind(); + // Bind the shader shader.bind(); + mVBOVertices.bind(); + // Set the model to camera matrix - const openglframework::Matrix4 localToCameraMatrix = worldToCameraMatrix * mTransformMatrix; - shader.setMatrix4x4Uniform("localToCameraMatrix", localToCameraMatrix); + shader.setMatrix4x4Uniform("localToWorldMatrix", mTransformMatrix); + shader.setMatrix4x4Uniform("worldToCameraMatrix", worldToCameraMatrix); // Set the normal matrix (inverse transpose of the 3x3 upper-left sub matrix of the // model-view matrix) + const openglframework::Matrix4 localToCameraMatrix = worldToCameraMatrix * mTransformMatrix; const openglframework::Matrix3 normalMatrix = localToCameraMatrix.getUpperLeft3x3Matrix().getInverse().getTranspose(); - shader.setMatrix3x3Uniform("normalMatrix", normalMatrix); + shader.setMatrix3x3Uniform("normalMatrix", normalMatrix, false); // Set the vertex color - openglframework::Vector4 color(mColor.r, mColor.g, mColor.b, mColor.a); - shader.setVector4Uniform("vertexColor", color); + openglframework::Color currentColor = mBody->isSleeping() ? mSleepingColor : mColor; + openglframework::Vector4 color(currentColor.r, currentColor.g, currentColor.b, currentColor.a); + shader.setVector4Uniform("vertexColor", color, false); - // Bind the vertices VBO - mVBOVertices.bind(); + // Get the location of shader attribute variables + GLint vertexPositionLoc = shader.getAttribLocation("vertexPosition"); + GLint vertexNormalLoc = shader.getAttribLocation("vertexNormal", false); - // Enable the vertex, normal and color arrays - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_COLOR_ARRAY); - glEnableClientState(GL_NORMAL_ARRAY); + glEnableVertexAttribArray(vertexPositionLoc); + glVertexAttribPointer(vertexPositionLoc, 3, GL_FLOAT, GL_FALSE, 0, NULL); - // Set the arrays pointers - glVertexPointer(3, GL_FLOAT, sizeof(VertexData), MEMBER_OFFSET(VertexData, position)); - glNormalPointer(GL_FLOAT, sizeof(VertexData), MEMBER_OFFSET(VertexData, normal)); - glColorPointer(3, GL_FLOAT, sizeof(VertexData), MEMBER_OFFSET(VertexData, color)); + mVBONormals.bind(); - // Bind the indices VBO - mVBOIndices.bind(); + if (vertexNormalLoc != -1) glEnableVertexAttribArray(vertexNormalLoc); + if (vertexNormalLoc != -1) glVertexAttribPointer(vertexNormalLoc, 3, GL_FLOAT, GL_FALSE, 0, NULL); // Draw the geometry of the box - glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, (char*)NULL); + glDrawArrays(GL_TRIANGLES, 0, 36); - // Unbind the VBOs + glDisableVertexAttribArray(vertexPositionLoc); + if (vertexNormalLoc != -1) glDisableVertexAttribArray(vertexNormalLoc); + + mVBONormals.unbind(); mVBOVertices.unbind(); - mVBOIndices.unbind(); - // Disable the arrays - glDisableClientState(GL_VERTEX_ARRAY); - glDisableClientState(GL_COLOR_ARRAY); - glDisableClientState(GL_NORMAL_ARRAY); + // Unbind the VAO + mVAO.unbind(); // Unbind the shader shader.unbind(); } -// Update the transform matrix of the box -void Box::updateTransform() { - // Get the interpolated transform of the rigid body - rp3d::Transform transform = mRigidBody->getInterpolatedTransform(); - - // Compute the transform used for rendering the box - rp3d::decimal matrix[16]; - transform.getOpenGLMatrix(matrix); - openglframework::Matrix4 newMatrix(matrix[0], matrix[4], matrix[8], matrix[12], - matrix[1], matrix[5], matrix[9], matrix[13], - matrix[2], matrix[6], matrix[10], matrix[14], - matrix[3], matrix[7], matrix[11], matrix[15]); - - // Apply the scaling matrix to have the correct box dimensions - mTransformMatrix = newMatrix * mScalingMatrix; -} // Create the Vertex Buffer Objects used to render to box with OpenGL. /// We create two VBOs (one for vertices and one for indices) to render all the boxes /// in the simulation. -void Box::createVBO() { +void Box::createVBOAndVAO() { - // Create the VBOs + // Create the VBO for the vertices data mVBOVertices.create(); - mVBOIndices.create(); - - // Copy the data into the VBOs + mVBOVertices.bind(); mVBOVertices.copyDataIntoVBO(sizeof(mCubeVertices), mCubeVertices, GL_STATIC_DRAW); - mVBOIndices.copyDataIntoVBO(sizeof(mCubeIndices), mCubeIndices, GL_STATIC_DRAW); + mVBOVertices.unbind(); - areVBOsCreated = true; + // Create th VBO for the normals data + mVBONormals.create(); + mVBONormals.bind(); + mVBONormals.copyDataIntoVBO(sizeof(mCubeNormals), mCubeNormals, GL_STATIC_DRAW); + mVBONormals.unbind(); + + // Create the VAO for both VBOs + mVAO.create(); + mVAO.bind(); + + // Bind the VBO of vertices + mVBOVertices.bind(); + + // Bind the VBO of indices + mVBONormals.bind(); + + // Unbind the VAO + mVAO.unbind(); +} + +// Reset the transform +void Box::resetTransform(const rp3d::Transform& transform) { + + // Reset the transform + mBody->setTransform(transform); + + mBody->setIsSleeping(false); + + // Reset the velocity of the rigid body + rp3d::RigidBody* rigidBody = dynamic_cast(mBody); + if (rigidBody != NULL) { + rigidBody->setLinearVelocity(rp3d::Vector3(0, 0, 0)); + rigidBody->setAngularVelocity(rp3d::Vector3(0, 0, 0)); + } + + updateTransform(1.0f); } diff --git a/examples/common/Box.h b/testbed/common/Box.h similarity index 67% rename from examples/common/Box.h rename to testbed/common/Box.h index ee0fb85d..d2d13c30 100644 --- a/examples/common/Box.h +++ b/testbed/common/Box.h @@ -29,22 +29,10 @@ // Libraries #include "openglframework.h" #include "reactphysics3d.h" - -// Structure VertexData -struct VertexData { - - /// Vertex position - openglframework::Vector3 position; - - /// Vertex normal - openglframework::Vector3 normal; - - // Vertex color - openglframework::Color color; -}; +#include "PhysicsObject.h" // Class Box -class Box : public openglframework::Object3D { +class Box : public openglframework::Object3D, public PhysicsObject { private : @@ -53,34 +41,31 @@ class Box : public openglframework::Object3D { /// Size of each side of the box float mSize[3]; - /// Rigid body used to simulate the dynamics of the box - rp3d::CollisionBody* mRigidBody; - /// Scaling matrix (applied to a cube to obtain the correct box dimensions) openglframework::Matrix4 mScalingMatrix; /// Vertex Buffer Object for the vertices data used to render the box with OpenGL static openglframework::VertexBufferObject mVBOVertices; - /// Vertex Buffer Object for the indices used to render the box with OpenGL - static openglframework::VertexBufferObject mVBOIndices; + /// Vertex Buffer Object for the normales used to render the box with OpenGL + static openglframework::VertexBufferObject mVBONormals; - /// Vertex data for each vertex of the cube (used to render the box) - static VertexData mCubeVertices[8]; + /// Vertex Array Object for the vertex data + static openglframework::VertexArrayObject mVAO; - /// Indices of the cube (used to render the box) - static GLuint mCubeIndices[36]; + /// Vertices coordinates of the triangles of the box + static GLfloat mCubeVertices[108]; - /// True if the VBOs have already been created - static bool areVBOsCreated; + /// Vertices normals of the triangles of the box + static GLfloat mCubeNormals[108]; - /// Main color of the box - openglframework::Color mColor; + /// Total number of boxes created + static int totalNbBoxes; // -------------------- Methods -------------------- // - /// Create a Vertex Buffer Object to render to box with OpenGL - static void createVBO(); + /// Create a the VAO and VBOs to render to box with OpenGL + static void createVBOAndVAO(); public : @@ -97,35 +82,26 @@ class Box : public openglframework::Object3D { /// Destructor ~Box(); - /// Return a pointer to the collision body of the box - reactphysics3d::CollisionBody* getCollisionBody(); - - /// Return a pointer to the rigid body of the box - reactphysics3d::RigidBody* getRigidBody(); - - /// Update the transform matrix of the box - void updateTransform(); - /// Render the cube at the correct position and with the correct orientation void render(openglframework::Shader& shader, const openglframework::Matrix4& worldToCameraMatrix); - /// Set the color of the box - void setColor(const openglframework::Color& color); + /// Set the position of the box + void resetTransform(const rp3d::Transform& transform); + + /// Update the transform matrix of the object + virtual void updateTransform(float interpolationFactor); }; -// Return a pointer to the collision body of the box -inline rp3d::CollisionBody* Box::getCollisionBody() { - return mRigidBody; -} - +<<<<<<< HEAD:examples/common/Box.h // Return a pointer to the rigid body of the box inline rp3d::RigidBody* Box::getRigidBody() { return static_cast(mRigidBody); +======= +// Update the transform matrix of the object +inline void Box::updateTransform(float interpolationFactor) { + mTransformMatrix = computeTransform(interpolationFactor, mScalingMatrix); +>>>>>>> 1bde11f245806de07a12b1cf6c33cdb83046b882:testbed/common/Box.h } -// Set the color of the box -inline void Box::setColor(const openglframework::Color& color) { - mColor = color; -} #endif diff --git a/examples/common/Capsule.cpp b/testbed/common/Capsule.cpp similarity index 57% rename from examples/common/Capsule.cpp rename to testbed/common/Capsule.cpp index faf8484e..b65146d4 100644 --- a/examples/common/Capsule.cpp +++ b/testbed/common/Capsule.cpp @@ -26,6 +26,13 @@ // Libraries #include "Capsule.h" +openglframework::VertexBufferObject Capsule::mVBOVertices(GL_ARRAY_BUFFER); +openglframework::VertexBufferObject Capsule::mVBONormals(GL_ARRAY_BUFFER); +openglframework::VertexBufferObject Capsule::mVBOTextureCoords(GL_ARRAY_BUFFER); +openglframework::VertexBufferObject Capsule::mVBOIndices(GL_ELEMENT_ARRAY_BUFFER); +openglframework::VertexArrayObject Capsule::mVAO; +int Capsule::totalNbCapsules = 0; + // Constructor Capsule::Capsule(float radius, float height, const openglframework::Vector3& position, reactphysics3d::CollisionWorld* world, @@ -57,13 +64,22 @@ Capsule::Capsule(float radius, float height, const openglframework::Vector3& pos rp3d::Quaternion initOrientation = rp3d::Quaternion::identity(); rp3d::Transform transform(initPosition, initOrientation); + mPreviousTransform = transform; + // Create a rigid body corresponding in the dynamics world - mRigidBody = world->createCollisionBody(transform); + mBody = world->createCollisionBody(transform); // Add a collision shape to the body and specify the mass of the shape - mRigidBody->addCollisionShape(collisionShape, rp3d::Transform::identity()); + mBody->addCollisionShape(collisionShape, rp3d::Transform::identity()); mTransformMatrix = mTransformMatrix * mScalingMatrix; + + // Create the VBOs and VAO + if (totalNbCapsules == 0) { + createVBOAndVAO(); + } + + totalNbCapsules++; } // Constructor @@ -103,16 +119,35 @@ Capsule::Capsule(float radius, float height, const openglframework::Vector3& pos // Add a collision shape to the body and specify the mass of the shape body->addCollisionShape(collisionShape, rp3d::Transform::identity(), mass); - mRigidBody = body; + mBody = body; mTransformMatrix = mTransformMatrix * mScalingMatrix; + + // Create the VBOs and VAO + if (totalNbCapsules == 0) { + createVBOAndVAO(); + } + + totalNbCapsules++; } // Destructor Capsule::~Capsule() { - // Destroy the mesh - destroy(); + if (totalNbCapsules == 1) { + + // Destroy the mesh + destroy(); + + // Destroy the VBOs and VAO + mVBOIndices.destroy(); + mVBOVertices.destroy(); + mVBONormals.destroy(); + mVBOTextureCoords.destroy(); + mVAO.destroy(); + } + + totalNbCapsules--; } // Render the sphere at the correct position and with the correct orientation @@ -123,57 +158,126 @@ void Capsule::render(openglframework::Shader& shader, shader.bind(); // Set the model to camera matrix - const openglframework::Matrix4 localToCameraMatrix = worldToCameraMatrix * mTransformMatrix; - shader.setMatrix4x4Uniform("localToCameraMatrix", localToCameraMatrix); + shader.setMatrix4x4Uniform("localToWorldMatrix", mTransformMatrix); + shader.setMatrix4x4Uniform("worldToCameraMatrix", worldToCameraMatrix); // Set the normal matrix (inverse transpose of the 3x3 upper-left sub matrix of the // model-view matrix) + const openglframework::Matrix4 localToCameraMatrix = worldToCameraMatrix * mTransformMatrix; const openglframework::Matrix3 normalMatrix = localToCameraMatrix.getUpperLeft3x3Matrix().getInverse().getTranspose(); - shader.setMatrix3x3Uniform("normalMatrix", normalMatrix); + shader.setMatrix3x3Uniform("normalMatrix", normalMatrix, false); - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_NORMAL_ARRAY); - if (hasTexture()) { - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - } + // Set the vertex color + openglframework::Color currentColor = mBody->isSleeping() ? mSleepingColor : mColor; + openglframework::Vector4 color(currentColor.r, currentColor.g, currentColor.b, currentColor.a); + shader.setVector4Uniform("vertexColor", color, false); - glVertexPointer(3, GL_FLOAT, 0, getVerticesPointer()); - glNormalPointer(GL_FLOAT, 0, getNormalsPointer()); - if(hasTexture()) { - glTexCoordPointer(2, GL_FLOAT, 0, getUVTextureCoordinatesPointer()); - } + // Bind the VAO + mVAO.bind(); + + mVBOVertices.bind(); + + // Get the location of shader attribute variables + GLint vertexPositionLoc = shader.getAttribLocation("vertexPosition"); + GLint vertexNormalLoc = shader.getAttribLocation("vertexNormal", false); + + glEnableVertexAttribArray(vertexPositionLoc); + glVertexAttribPointer(vertexPositionLoc, 3, GL_FLOAT, GL_FALSE, 0, (char*)NULL); + + mVBONormals.bind(); + + if (vertexNormalLoc != -1) glVertexAttribPointer(vertexNormalLoc, 3, GL_FLOAT, GL_FALSE, 0, (char*)NULL); + if (vertexNormalLoc != -1) glEnableVertexAttribArray(vertexNormalLoc); // For each part of the mesh for (unsigned int i=0; igetInterpolatedTransform(); + // Create the VBO for the vertices data + mVBOVertices.create(); + mVBOVertices.bind(); + size_t sizeVertices = mVertices.size() * sizeof(openglframework::Vector3); + mVBOVertices.copyDataIntoVBO(sizeVertices, getVerticesPointer(), GL_STATIC_DRAW); + mVBOVertices.unbind(); - // Compute the transform used for rendering the sphere - rp3d::decimal matrix[16]; - transform.getOpenGLMatrix(matrix); - openglframework::Matrix4 newMatrix(matrix[0], matrix[4], matrix[8], matrix[12], - matrix[1], matrix[5], matrix[9], matrix[13], - matrix[2], matrix[6], matrix[10], matrix[14], - matrix[3], matrix[7], matrix[11], matrix[15]); + // Create the VBO for the normals data + mVBONormals.create(); + mVBONormals.bind(); + size_t sizeNormals = mNormals.size() * sizeof(openglframework::Vector3); + mVBONormals.copyDataIntoVBO(sizeNormals, getNormalsPointer(), GL_STATIC_DRAW); + mVBONormals.unbind(); - // Apply the scaling matrix to have the correct sphere dimensions - mTransformMatrix = newMatrix * mScalingMatrix; + if (hasTexture()) { + // Create the VBO for the texture co data + mVBOTextureCoords.create(); + mVBOTextureCoords.bind(); + size_t sizeTextureCoords = mUVs.size() * sizeof(openglframework::Vector2); + mVBOTextureCoords.copyDataIntoVBO(sizeTextureCoords, getUVTextureCoordinatesPointer(), GL_STATIC_DRAW); + mVBOTextureCoords.unbind(); + } + + // Create th VBO for the indices data + mVBOIndices.create(); + mVBOIndices.bind(); + size_t sizeIndices = mIndices[0].size() * sizeof(uint); + mVBOIndices.copyDataIntoVBO(sizeIndices, getIndicesPointer(), GL_STATIC_DRAW); + mVBOIndices.unbind(); + + // Create the VAO for both VBOs + mVAO.create(); + mVAO.bind(); + + // Bind the VBO of vertices + mVBOVertices.bind(); + + // Bind the VBO of normals + mVBONormals.bind(); + + if (hasTexture()) { + // Bind the VBO of texture coords + mVBOTextureCoords.bind(); + } + + // Bind the VBO of indices + mVBOIndices.bind(); + + // Unbind the VAO + mVAO.unbind(); +} + +// Reset the transform +void Capsule::resetTransform(const rp3d::Transform& transform) { + + // Reset the transform + mBody->setTransform(transform); + + mBody->setIsSleeping(false); + + // Reset the velocity of the rigid body + rp3d::RigidBody* rigidBody = dynamic_cast(mBody); + if (rigidBody != NULL) { + rigidBody->setLinearVelocity(rp3d::Vector3(0, 0, 0)); + rigidBody->setAngularVelocity(rp3d::Vector3(0, 0, 0)); + } + + updateTransform(1.0f); } diff --git a/examples/common/Capsule.h b/testbed/common/Capsule.h similarity index 69% rename from examples/common/Capsule.h rename to testbed/common/Capsule.h index 6dbf0181..91d05861 100644 --- a/examples/common/Capsule.h +++ b/testbed/common/Capsule.h @@ -29,9 +29,10 @@ // Libraries #include "openglframework.h" #include "reactphysics3d.h" +#include "PhysicsObject.h" // Class Sphere -class Capsule : public openglframework::Mesh { +class Capsule : public openglframework::Mesh, public PhysicsObject { private : @@ -43,14 +44,35 @@ class Capsule : public openglframework::Mesh { /// Height of the capsule float mHeight; - /// Rigid body used to simulate the dynamics of the sphere - rp3d::CollisionBody* mRigidBody; - /// Scaling matrix (applied to a sphere to obtain the correct sphere dimensions) openglframework::Matrix4 mScalingMatrix; + /// Previous transform (for interpolation) + rp3d::Transform mPreviousTransform; + + /// Vertex Buffer Object for the vertices data + static openglframework::VertexBufferObject mVBOVertices; + + /// Vertex Buffer Object for the normals data + static openglframework::VertexBufferObject mVBONormals; + + /// Vertex Buffer Object for the texture coords + static openglframework::VertexBufferObject mVBOTextureCoords; + + /// Vertex Buffer Object for the indices + static openglframework::VertexBufferObject mVBOIndices; + + /// Vertex Array Object for the vertex data + static openglframework::VertexArrayObject mVAO; + + // Total number of capsules created + static int totalNbCapsules; + // -------------------- Methods -------------------- // + // Create the Vertex Buffer Objects used to render with OpenGL. + void createVBOAndVAO(); + public : // -------------------- Methods -------------------- // @@ -67,28 +89,26 @@ class Capsule : public openglframework::Mesh { /// Destructor ~Capsule(); - /// Return a pointer to the collision body of the box - reactphysics3d::CollisionBody* getCollisionBody(); - - /// Return a pointer to the rigid body of the box - reactphysics3d::RigidBody* getRigidBody(); - - /// Update the transform matrix of the sphere - void updateTransform(); - /// Render the sphere at the correct position and with the correct orientation void render(openglframework::Shader& shader, const openglframework::Matrix4& worldToCameraMatrix); + + /// Set the position of the box + void resetTransform(const rp3d::Transform& transform); + + /// Update the transform matrix of the object + virtual void updateTransform(float interpolationFactor); }; -// Return a pointer to the collision body of the box -inline rp3d::CollisionBody* Capsule::getCollisionBody() { - return mRigidBody; -} - +<<<<<<< HEAD:examples/common/Capsule.h // Return a pointer to the rigid body of the box inline rp3d::RigidBody* Capsule::getRigidBody() { return static_cast(mRigidBody); +======= +// Update the transform matrix of the object +inline void Capsule::updateTransform(float interpolationFactor) { + mTransformMatrix = computeTransform(interpolationFactor, mScalingMatrix); +>>>>>>> 1bde11f245806de07a12b1cf6c33cdb83046b882:testbed/common/Capsule.h } #endif diff --git a/examples/common/Cone.cpp b/testbed/common/Cone.cpp similarity index 58% rename from examples/common/Cone.cpp rename to testbed/common/Cone.cpp index 9e3f24b6..079a6189 100644 --- a/examples/common/Cone.cpp +++ b/testbed/common/Cone.cpp @@ -26,6 +26,13 @@ // Libraries #include "Cone.h" +openglframework::VertexBufferObject Cone::mVBOVertices(GL_ARRAY_BUFFER); +openglframework::VertexBufferObject Cone::mVBONormals(GL_ARRAY_BUFFER); +openglframework::VertexBufferObject Cone::mVBOTextureCoords(GL_ARRAY_BUFFER); +openglframework::VertexBufferObject Cone::mVBOIndices(GL_ELEMENT_ARRAY_BUFFER); +openglframework::VertexArrayObject Cone::mVAO; +int Cone::totalNbCones = 0; + // Constructor Cone::Cone(float radius, float height, const openglframework::Vector3 &position, reactphysics3d::CollisionWorld* world, @@ -57,13 +64,22 @@ Cone::Cone(float radius, float height, const openglframework::Vector3 &position, rp3d::Quaternion initOrientation = rp3d::Quaternion::identity(); rp3d::Transform transform(initPosition, initOrientation); + mPreviousTransform = transform; + // Create a rigid body corresponding to the cone in the dynamics world - mRigidBody = world->createCollisionBody(transform); + mBody = world->createCollisionBody(transform); // Add a collision shape to the body and specify the mass of the shape - mRigidBody->addCollisionShape(collisionShape, rp3d::Transform::identity()); + mBody->addCollisionShape(collisionShape, rp3d::Transform::identity()); mTransformMatrix = mTransformMatrix * mScalingMatrix; + + // Create the VBOs and VAO + if (totalNbCones == 0) { + createVBOAndVAO(); + } + + totalNbCones++; } // Constructor @@ -103,16 +119,34 @@ Cone::Cone(float radius, float height, const openglframework::Vector3 &position, // Add a collision shape to the body and specify the mass of the shape body->addCollisionShape(collisionShape, rp3d::Transform::identity(), mass); - mRigidBody = body; + mBody = body; mTransformMatrix = mTransformMatrix * mScalingMatrix; + + // Create the VBOs and VAO + if (totalNbCones == 0) { + createVBOAndVAO(); + } + + totalNbCones++; } // Destructor Cone::~Cone() { - // Destroy the mesh - destroy(); + if (totalNbCones == 1) { + // Destroy the mesh + destroy(); + + // Destroy the VBOs and VAO + mVBOIndices.destroy(); + mVBOVertices.destroy(); + mVBONormals.destroy(); + mVBOTextureCoords.destroy(); + mVAO.destroy(); + } + + totalNbCones--; } // Render the cone at the correct position and with the correct orientation @@ -123,57 +157,126 @@ void Cone::render(openglframework::Shader& shader, shader.bind(); // Set the model to camera matrix - const openglframework::Matrix4 localToCameraMatrix = worldToCameraMatrix * mTransformMatrix; - shader.setMatrix4x4Uniform("localToCameraMatrix", localToCameraMatrix); + shader.setMatrix4x4Uniform("localToWorldMatrix", mTransformMatrix); + shader.setMatrix4x4Uniform("worldToCameraMatrix", worldToCameraMatrix); // Set the normal matrix (inverse transpose of the 3x3 upper-left sub matrix of the // model-view matrix) + const openglframework::Matrix4 localToCameraMatrix = worldToCameraMatrix * mTransformMatrix; const openglframework::Matrix3 normalMatrix = localToCameraMatrix.getUpperLeft3x3Matrix().getInverse().getTranspose(); - shader.setMatrix3x3Uniform("normalMatrix", normalMatrix); + shader.setMatrix3x3Uniform("normalMatrix", normalMatrix, false); - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_NORMAL_ARRAY); - if (hasTexture()) { - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - } + // Set the vertex color + openglframework::Vector4 color(mColor.r, mColor.g, mColor.b, mColor.a); + shader.setVector4Uniform("vertexColor", color, false); - glVertexPointer(3, GL_FLOAT, 0, getVerticesPointer()); - glNormalPointer(GL_FLOAT, 0, getNormalsPointer()); - if(hasTexture()) { - glTexCoordPointer(2, GL_FLOAT, 0, getUVTextureCoordinatesPointer()); - } + // Bind the VAO + mVAO.bind(); + + mVBOVertices.bind(); + + // Get the location of shader attribute variables + GLint vertexPositionLoc = shader.getAttribLocation("vertexPosition"); + GLint vertexNormalLoc = shader.getAttribLocation("vertexNormal", false); + + glEnableVertexAttribArray(vertexPositionLoc); + glVertexAttribPointer(vertexPositionLoc, 3, GL_FLOAT, GL_FALSE, 0, (char*)NULL); + + mVBONormals.bind(); + + if (vertexNormalLoc != -1) glVertexAttribPointer(vertexNormalLoc, 3, GL_FLOAT, GL_FALSE, 0, (char*)NULL); + if (vertexNormalLoc != -1) glEnableVertexAttribArray(vertexNormalLoc); // For each part of the mesh for (unsigned int i=0; igetInterpolatedTransform(); + // Create the VBO for the vertices data + mVBOVertices.create(); + mVBOVertices.bind(); + size_t sizeVertices = mVertices.size() * sizeof(openglframework::Vector3); + mVBOVertices.copyDataIntoVBO(sizeVertices, getVerticesPointer(), GL_STATIC_DRAW); + mVBOVertices.unbind(); - // Compute the transform used for rendering the cone - rp3d::decimal matrix[16]; - transform.getOpenGLMatrix(matrix); - openglframework::Matrix4 newMatrix(matrix[0], matrix[4], matrix[8], matrix[12], - matrix[1], matrix[5], matrix[9], matrix[13], - matrix[2], matrix[6], matrix[10], matrix[14], - matrix[3], matrix[7], matrix[11], matrix[15]); + // Create the VBO for the normals data + mVBONormals.create(); + mVBONormals.bind(); + size_t sizeNormals = mNormals.size() * sizeof(openglframework::Vector3); + mVBONormals.copyDataIntoVBO(sizeNormals, getNormalsPointer(), GL_STATIC_DRAW); + mVBONormals.unbind(); - // Apply the scaling matrix to have the correct cone dimensions - mTransformMatrix = newMatrix * mScalingMatrix; + if (hasTexture()) { + // Create the VBO for the texture co data + mVBOTextureCoords.create(); + mVBOTextureCoords.bind(); + size_t sizeTextureCoords = mUVs.size() * sizeof(openglframework::Vector2); + mVBOTextureCoords.copyDataIntoVBO(sizeTextureCoords, getUVTextureCoordinatesPointer(), GL_STATIC_DRAW); + mVBOTextureCoords.unbind(); + } + + // Create th VBO for the indices data + mVBOIndices.create(); + mVBOIndices.bind(); + size_t sizeIndices = mIndices[0].size() * sizeof(unsigned int); + mVBOIndices.copyDataIntoVBO(sizeIndices, getIndicesPointer(), GL_STATIC_DRAW); + mVBOIndices.unbind(); + + // Create the VAO for both VBOs + mVAO.create(); + mVAO.bind(); + + // Bind the VBO of vertices + mVBOVertices.bind(); + + // Bind the VBO of normals + mVBONormals.bind(); + + if (hasTexture()) { + // Bind the VBO of texture coords + mVBOTextureCoords.bind(); + } + + // Bind the VBO of indices + mVBOIndices.bind(); + + // Unbind the VAO + mVAO.unbind(); } + +// Reset the transform +void Cone::resetTransform(const rp3d::Transform& transform) { + + // Reset the transform + mBody->setTransform(transform); + + mBody->setIsSleeping(false); + + // Reset the velocity of the rigid body + rp3d::RigidBody* rigidBody = dynamic_cast(mBody); + if (rigidBody != NULL) { + rigidBody->setLinearVelocity(rp3d::Vector3(0, 0, 0)); + rigidBody->setAngularVelocity(rp3d::Vector3(0, 0, 0)); + } + + updateTransform(1.0f); +} + diff --git a/examples/common/Cone.h b/testbed/common/Cone.h similarity index 69% rename from examples/common/Cone.h rename to testbed/common/Cone.h index 6368ec2b..28998d41 100644 --- a/examples/common/Cone.h +++ b/testbed/common/Cone.h @@ -29,9 +29,10 @@ // Libraries #include "openglframework.h" #include "reactphysics3d.h" +#include "PhysicsObject.h" // Class Cone -class Cone : public openglframework::Mesh { +class Cone : public openglframework::Mesh, public PhysicsObject { private : @@ -43,14 +44,35 @@ class Cone : public openglframework::Mesh { /// Height of the cone float mHeight; - /// Rigid body used to simulate the dynamics of the cone - rp3d::CollisionBody* mRigidBody; - /// Scaling matrix (applied to a sphere to obtain the correct cone dimensions) openglframework::Matrix4 mScalingMatrix; + /// Previous transform (for interpolation) + rp3d::Transform mPreviousTransform; + + /// Vertex Buffer Object for the vertices data + static openglframework::VertexBufferObject mVBOVertices; + + /// Vertex Buffer Object for the normals data + static openglframework::VertexBufferObject mVBONormals; + + /// Vertex Buffer Object for the texture coords + static openglframework::VertexBufferObject mVBOTextureCoords; + + /// Vertex Buffer Object for the indices + static openglframework::VertexBufferObject mVBOIndices; + + /// Vertex Array Object for the vertex data + static openglframework::VertexArrayObject mVAO; + + // Total number of cones created + static int totalNbCones; + // -------------------- Methods -------------------- // + // Create the Vertex Buffer Objects used to render with OpenGL. + void createVBOAndVAO(); + public : // -------------------- Methods -------------------- // @@ -66,28 +88,26 @@ class Cone : public openglframework::Mesh { /// Destructor ~Cone(); - /// Return a pointer to the collision body of the box - reactphysics3d::CollisionBody* getCollisionBody(); - - /// Return a pointer to the rigid body of the box - reactphysics3d::RigidBody* getRigidBody(); - - /// Update the transform matrix of the cone - void updateTransform(); - /// Render the cone at the correct position and with the correct orientation void render(openglframework::Shader& shader, const openglframework::Matrix4& worldToCameraMatrix); + + /// Set the position of the box + void resetTransform(const rp3d::Transform& transform); + + /// Update the transform matrix of the object + virtual void updateTransform(float interpolationFactor); }; -// Return a pointer to the collision body of the box -inline rp3d::CollisionBody* Cone::getCollisionBody() { - return mRigidBody; -} - +<<<<<<< HEAD:examples/common/Cone.h // Return a pointer to the rigid body of the box inline rp3d::RigidBody* Cone::getRigidBody() { return static_cast(mRigidBody); +======= +// Update the transform matrix of the object +inline void Cone::updateTransform(float interpolationFactor) { + mTransformMatrix = computeTransform(interpolationFactor, mScalingMatrix); +>>>>>>> 1bde11f245806de07a12b1cf6c33cdb83046b882:testbed/common/Cone.h } #endif diff --git a/examples/common/ConvexMesh.cpp b/testbed/common/ConvexMesh.cpp similarity index 65% rename from examples/common/ConvexMesh.cpp rename to testbed/common/ConvexMesh.cpp index dbb2c28a..182c5940 100644 --- a/examples/common/ConvexMesh.cpp +++ b/testbed/common/ConvexMesh.cpp @@ -30,7 +30,9 @@ ConvexMesh::ConvexMesh(const openglframework::Vector3 &position, reactphysics3d::CollisionWorld* world, const std::string& meshFolderPath) - : openglframework::Mesh() { + : openglframework::Mesh(), mVBOVertices(GL_ARRAY_BUFFER), + mVBONormals(GL_ARRAY_BUFFER), mVBOTextureCoords(GL_ARRAY_BUFFER), + mVBOIndices(GL_ELEMENT_ARRAY_BUFFER) { // Load the mesh from a file openglframework::MeshReaderWriter::loadMeshFromFile(meshFolderPath + "convexmesh.obj", *this); @@ -79,18 +81,25 @@ ConvexMesh::ConvexMesh(const openglframework::Vector3 &position, rp3d::Quaternion initOrientation = rp3d::Quaternion::identity(); rp3d::Transform transform(initPosition, initOrientation); + mPreviousTransform = transform; + // Create a rigid body corresponding to the sphere in the dynamics world - mRigidBody = world->createCollisionBody(transform); + mBody = world->createCollisionBody(transform); // Add a collision shape to the body and specify the mass of the collision shape - mRigidBody->addCollisionShape(collisionShape, rp3d::Transform::identity()); + mBody->addCollisionShape(collisionShape, rp3d::Transform::identity()); + + // Create the VBOs and VAO + createVBOAndVAO(); } // Constructor ConvexMesh::ConvexMesh(const openglframework::Vector3 &position, float mass, reactphysics3d::DynamicsWorld* dynamicsWorld, const std::string& meshFolderPath) - : openglframework::Mesh() { + : openglframework::Mesh(), mVBOVertices(GL_ARRAY_BUFFER), + mVBONormals(GL_ARRAY_BUFFER), mVBOTextureCoords(GL_ARRAY_BUFFER), + mVBOIndices(GL_ELEMENT_ARRAY_BUFFER) { // Load the mesh from a file openglframework::MeshReaderWriter::loadMeshFromFile(meshFolderPath + "convexmesh.obj", *this); @@ -144,7 +153,10 @@ ConvexMesh::ConvexMesh(const openglframework::Vector3 &position, float mass, // Add a collision shape to the body and specify the mass of the collision shape body->addCollisionShape(collisionShape, rp3d::Transform::identity(), mass); - mRigidBody = body; + mBody = body; + + // Create the VBOs and VAO + createVBOAndVAO(); } // Destructor @@ -152,6 +164,13 @@ ConvexMesh::~ConvexMesh() { // Destroy the mesh destroy(); + + // Destroy the VBOs and VAO + mVBOIndices.destroy(); + mVBOVertices.destroy(); + mVBONormals.destroy(); + mVBOTextureCoords.destroy(); + mVAO.destroy(); } // Render the sphere at the correct position and with the correct orientation @@ -162,57 +181,125 @@ void ConvexMesh::render(openglframework::Shader& shader, shader.bind(); // Set the model to camera matrix - const openglframework::Matrix4 localToCameraMatrix = worldToCameraMatrix * mTransformMatrix; - shader.setMatrix4x4Uniform("localToCameraMatrix", localToCameraMatrix); + shader.setMatrix4x4Uniform("localToWorldMatrix", mTransformMatrix); + shader.setMatrix4x4Uniform("worldToCameraMatrix", worldToCameraMatrix); // Set the normal matrix (inverse transpose of the 3x3 upper-left sub matrix of the // model-view matrix) + const openglframework::Matrix4 localToCameraMatrix = worldToCameraMatrix * mTransformMatrix; const openglframework::Matrix3 normalMatrix = localToCameraMatrix.getUpperLeft3x3Matrix().getInverse().getTranspose(); - shader.setMatrix3x3Uniform("normalMatrix", normalMatrix); + shader.setMatrix3x3Uniform("normalMatrix", normalMatrix, false); - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_NORMAL_ARRAY); - if (hasTexture()) { - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - } + // Set the vertex color + openglframework::Vector4 color(mColor.r, mColor.g, mColor.b, mColor.a); + shader.setVector4Uniform("vertexColor", color, false); - glVertexPointer(3, GL_FLOAT, 0, getVerticesPointer()); - glNormalPointer(GL_FLOAT, 0, getNormalsPointer()); - if(hasTexture()) { - glTexCoordPointer(2, GL_FLOAT, 0, getUVTextureCoordinatesPointer()); - } + // Bind the VAO + mVAO.bind(); + + mVBOVertices.bind(); + + // Get the location of shader attribute variables + GLint vertexPositionLoc = shader.getAttribLocation("vertexPosition"); + GLint vertexNormalLoc = shader.getAttribLocation("vertexNormal", false); + + glEnableVertexAttribArray(vertexPositionLoc); + glVertexAttribPointer(vertexPositionLoc, 3, GL_FLOAT, GL_FALSE, 0, (char*)NULL); + + mVBONormals.bind(); + + if (vertexNormalLoc != -1) glVertexAttribPointer(vertexNormalLoc, 3, GL_FLOAT, GL_FALSE, 0, (char*)NULL); + if (vertexNormalLoc != -1) glEnableVertexAttribArray(vertexNormalLoc); // For each part of the mesh for (unsigned int i=0; igetInterpolatedTransform(); + // Create the VBO for the vertices data + mVBOVertices.create(); + mVBOVertices.bind(); + size_t sizeVertices = mVertices.size() * sizeof(openglframework::Vector3); + mVBOVertices.copyDataIntoVBO(sizeVertices, getVerticesPointer(), GL_STATIC_DRAW); + mVBOVertices.unbind(); - // Compute the transform used for rendering the sphere - rp3d::decimal matrix[16]; - transform.getOpenGLMatrix(matrix); - openglframework::Matrix4 newMatrix(matrix[0], matrix[4], matrix[8], matrix[12], - matrix[1], matrix[5], matrix[9], matrix[13], - matrix[2], matrix[6], matrix[10], matrix[14], - matrix[3], matrix[7], matrix[11], matrix[15]); + // Create the VBO for the normals data + mVBONormals.create(); + mVBONormals.bind(); + size_t sizeNormals = mNormals.size() * sizeof(openglframework::Vector3); + mVBONormals.copyDataIntoVBO(sizeNormals, getNormalsPointer(), GL_STATIC_DRAW); + mVBONormals.unbind(); - // Apply the scaling matrix to have the correct sphere dimensions - mTransformMatrix = newMatrix; + if (hasTexture()) { + // Create the VBO for the texture co data + mVBOTextureCoords.create(); + mVBOTextureCoords.bind(); + size_t sizeTextureCoords = mUVs.size() * sizeof(openglframework::Vector2); + mVBOTextureCoords.copyDataIntoVBO(sizeTextureCoords, getUVTextureCoordinatesPointer(), GL_STATIC_DRAW); + mVBOTextureCoords.unbind(); + } + + // Create th VBO for the indices data + mVBOIndices.create(); + mVBOIndices.bind(); + size_t sizeIndices = mIndices[0].size() * sizeof(unsigned int); + mVBOIndices.copyDataIntoVBO(sizeIndices, getIndicesPointer(), GL_STATIC_DRAW); + mVBOIndices.unbind(); + + // Create the VAO for both VBOs + mVAO.create(); + mVAO.bind(); + + // Bind the VBO of vertices + mVBOVertices.bind(); + + // Bind the VBO of normals + mVBONormals.bind(); + + if (hasTexture()) { + // Bind the VBO of texture coords + mVBOTextureCoords.bind(); + } + + // Bind the VBO of indices + mVBOIndices.bind(); + + // Unbind the VAO + mVAO.unbind(); +} + +// Reset the transform +void ConvexMesh::resetTransform(const rp3d::Transform& transform) { + + // Reset the transform + mBody->setTransform(transform); + + mBody->setIsSleeping(false); + + // Reset the velocity of the rigid body + rp3d::RigidBody* rigidBody = dynamic_cast(mBody); + if (rigidBody != NULL) { + rigidBody->setLinearVelocity(rp3d::Vector3(0, 0, 0)); + rigidBody->setAngularVelocity(rp3d::Vector3(0, 0, 0)); + } + + updateTransform(1.0f); } diff --git a/examples/common/ConvexMesh.h b/testbed/common/ConvexMesh.h similarity index 69% rename from examples/common/ConvexMesh.h rename to testbed/common/ConvexMesh.h index f97ec5ec..4a4cdcdb 100644 --- a/examples/common/ConvexMesh.h +++ b/testbed/common/ConvexMesh.h @@ -29,19 +29,38 @@ // Libraries #include "openglframework.h" #include "reactphysics3d.h" +#include "PhysicsObject.h" // Class ConvexMesh -class ConvexMesh : public openglframework::Mesh { +class ConvexMesh : public openglframework::Mesh, public PhysicsObject { private : // -------------------- Attributes -------------------- // - /// Rigid body used to simulate the dynamics of the mesh - rp3d::CollisionBody* mRigidBody; + /// Previous transform (for interpolation) + rp3d::Transform mPreviousTransform; + + /// Vertex Buffer Object for the vertices data + openglframework::VertexBufferObject mVBOVertices; + + /// Vertex Buffer Object for the normals data + openglframework::VertexBufferObject mVBONormals; + + /// Vertex Buffer Object for the texture coords + openglframework::VertexBufferObject mVBOTextureCoords; + + /// Vertex Buffer Object for the indices + openglframework::VertexBufferObject mVBOIndices; + + /// Vertex Array Object for the vertex data + openglframework::VertexArrayObject mVAO; // -------------------- Methods -------------------- // + // Create the Vertex Buffer Objects used to render with OpenGL. + void createVBOAndVAO(); + public : // -------------------- Methods -------------------- // @@ -57,28 +76,20 @@ class ConvexMesh : public openglframework::Mesh { /// Destructor ~ConvexMesh(); - /// Return a pointer to the collision body of the box - reactphysics3d::CollisionBody* getCollisionBody(); - - /// Return a pointer to the rigid body of the box - reactphysics3d::RigidBody* getRigidBody(); - - /// Update the transform matrix of the mesh - void updateTransform(); - /// Render the mesh at the correct position and with the correct orientation void render(openglframework::Shader& shader, const openglframework::Matrix4& worldToCameraMatrix); + + /// Set the position of the box + void resetTransform(const rp3d::Transform& transform); + + /// Update the transform matrix of the object + virtual void updateTransform(float interpolationFactor); }; -// Return a pointer to the collision body of the box -inline rp3d::CollisionBody* ConvexMesh::getCollisionBody() { - return mRigidBody; -} - -// Return a pointer to the rigid body of the box -inline rp3d::RigidBody* ConvexMesh::getRigidBody() { - return dynamic_cast(mRigidBody); +// Update the transform matrix of the object +inline void ConvexMesh::updateTransform(float interpolationFactor) { + mTransformMatrix = computeTransform(interpolationFactor, openglframework::Matrix4::identity()); } #endif diff --git a/examples/common/Cylinder.cpp b/testbed/common/Cylinder.cpp similarity index 58% rename from examples/common/Cylinder.cpp rename to testbed/common/Cylinder.cpp index 7c2b7690..bfc39092 100644 --- a/examples/common/Cylinder.cpp +++ b/testbed/common/Cylinder.cpp @@ -26,6 +26,13 @@ // Libraries #include "Cylinder.h" +openglframework::VertexBufferObject Cylinder::mVBOVertices(GL_ARRAY_BUFFER); +openglframework::VertexBufferObject Cylinder::mVBONormals(GL_ARRAY_BUFFER); +openglframework::VertexBufferObject Cylinder::mVBOTextureCoords(GL_ARRAY_BUFFER); +openglframework::VertexBufferObject Cylinder::mVBOIndices(GL_ELEMENT_ARRAY_BUFFER); +openglframework::VertexArrayObject Cylinder::mVAO; +int Cylinder::totalNbCylinders = 0; + // Constructor Cylinder::Cylinder(float radius, float height, const openglframework::Vector3& position, reactphysics3d::CollisionWorld* world, @@ -57,13 +64,22 @@ Cylinder::Cylinder(float radius, float height, const openglframework::Vector3& p rp3d::Quaternion initOrientation = rp3d::Quaternion::identity(); rp3d::Transform transform(initPosition, initOrientation); + mPreviousTransform = transform; + // Create a rigid body corresponding to the cylinder in the dynamics world - mRigidBody = world->createCollisionBody(transform); + mBody = world->createCollisionBody(transform); // Add a collision shape to the body and specify the mass of the shape - mRigidBody->addCollisionShape(collisionShape, rp3d::Transform::identity()); + mBody->addCollisionShape(collisionShape, rp3d::Transform::identity()); mTransformMatrix = mTransformMatrix * mScalingMatrix; + + // Create the VBOs and VAO + if (totalNbCylinders == 0) { + createVBOAndVAO(); + } + + totalNbCylinders++; } // Constructor @@ -105,14 +121,33 @@ Cylinder::Cylinder(float radius, float height, const openglframework::Vector3& p mTransformMatrix = mTransformMatrix * mScalingMatrix; - mRigidBody = body; + mBody = body; + + // Create the VBOs and VAO + if (totalNbCylinders == 0) { + createVBOAndVAO(); + } + + totalNbCylinders++; } // Destructor Cylinder::~Cylinder() { - // Destroy the mesh - destroy(); + if (totalNbCylinders == 1) { + + // Destroy the mesh + destroy(); + + // Destroy the VBOs and VAO + mVBOIndices.destroy(); + mVBOVertices.destroy(); + mVBONormals.destroy(); + mVBOTextureCoords.destroy(); + mVAO.destroy(); + } + + totalNbCylinders--; } // Render the cylinder at the correct position and with the correct orientation @@ -123,57 +158,125 @@ void Cylinder::render(openglframework::Shader& shader, shader.bind(); // Set the model to camera matrix - const openglframework::Matrix4 localToCameraMatrix = worldToCameraMatrix * mTransformMatrix; - shader.setMatrix4x4Uniform("localToCameraMatrix", localToCameraMatrix); + shader.setMatrix4x4Uniform("localToWorldMatrix", mTransformMatrix); + shader.setMatrix4x4Uniform("worldToCameraMatrix", worldToCameraMatrix); // Set the normal matrix (inverse transpose of the 3x3 upper-left sub matrix of the // model-view matrix) + const openglframework::Matrix4 localToCameraMatrix = worldToCameraMatrix * mTransformMatrix; const openglframework::Matrix3 normalMatrix = localToCameraMatrix.getUpperLeft3x3Matrix().getInverse().getTranspose(); - shader.setMatrix3x3Uniform("normalMatrix", normalMatrix); + shader.setMatrix3x3Uniform("normalMatrix", normalMatrix, false); - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_NORMAL_ARRAY); - if (hasTexture()) { - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - } + // Set the vertex color + openglframework::Vector4 color(mColor.r, mColor.g, mColor.b, mColor.a); + shader.setVector4Uniform("vertexColor", color, false); - glVertexPointer(3, GL_FLOAT, 0, getVerticesPointer()); - glNormalPointer(GL_FLOAT, 0, getNormalsPointer()); - if(hasTexture()) { - glTexCoordPointer(2, GL_FLOAT, 0, getUVTextureCoordinatesPointer()); - } + // Bind the VAO + mVAO.bind(); + + mVBOVertices.bind(); + + // Get the location of shader attribute variables + GLint vertexPositionLoc = shader.getAttribLocation("vertexPosition"); + GLint vertexNormalLoc = shader.getAttribLocation("vertexNormal", false); + + glEnableVertexAttribArray(vertexPositionLoc); + glVertexAttribPointer(vertexPositionLoc, 3, GL_FLOAT, GL_FALSE, 0, (char*)NULL); + + mVBONormals.bind(); + + if (vertexNormalLoc != -1) glVertexAttribPointer(vertexNormalLoc, 3, GL_FLOAT, GL_FALSE, 0, (char*)NULL); + if (vertexNormalLoc != -1) glEnableVertexAttribArray(vertexNormalLoc); // For each part of the mesh for (unsigned int i=0; igetInterpolatedTransform(); + // Create the VBO for the vertices data + mVBOVertices.create(); + mVBOVertices.bind(); + size_t sizeVertices = mVertices.size() * sizeof(openglframework::Vector3); + mVBOVertices.copyDataIntoVBO(sizeVertices, getVerticesPointer(), GL_STATIC_DRAW); + mVBOVertices.unbind(); - // Compute the transform used for rendering the cylinder - rp3d::decimal matrix[16]; - transform.getOpenGLMatrix(matrix); - openglframework::Matrix4 newMatrix(matrix[0], matrix[4], matrix[8], matrix[12], - matrix[1], matrix[5], matrix[9], matrix[13], - matrix[2], matrix[6], matrix[10], matrix[14], - matrix[3], matrix[7], matrix[11], matrix[15]); + // Create the VBO for the normals data + mVBONormals.create(); + mVBONormals.bind(); + size_t sizeNormals = mNormals.size() * sizeof(openglframework::Vector3); + mVBONormals.copyDataIntoVBO(sizeNormals, getNormalsPointer(), GL_STATIC_DRAW); + mVBONormals.unbind(); - // Apply the scaling matrix to have the correct cylinder dimensions - mTransformMatrix = newMatrix * mScalingMatrix; + if (hasTexture()) { + // Create the VBO for the texture co data + mVBOTextureCoords.create(); + mVBOTextureCoords.bind(); + size_t sizeTextureCoords = mUVs.size() * sizeof(openglframework::Vector2); + mVBOTextureCoords.copyDataIntoVBO(sizeTextureCoords, getUVTextureCoordinatesPointer(), GL_STATIC_DRAW); + mVBOTextureCoords.unbind(); + } + + // Create th VBO for the indices data + mVBOIndices.create(); + mVBOIndices.bind(); + size_t sizeIndices = mIndices[0].size() * sizeof(unsigned int); + mVBOIndices.copyDataIntoVBO(sizeIndices, getIndicesPointer(), GL_STATIC_DRAW); + mVBOIndices.unbind(); + + // Create the VAO for both VBOs + mVAO.create(); + mVAO.bind(); + + // Bind the VBO of vertices + mVBOVertices.bind(); + + // Bind the VBO of normals + mVBONormals.bind(); + + if (hasTexture()) { + // Bind the VBO of texture coords + mVBOTextureCoords.bind(); + } + + // Bind the VBO of indices + mVBOIndices.bind(); + + // Unbind the VAO + mVAO.unbind(); +} + +// Reset the transform +void Cylinder::resetTransform(const rp3d::Transform& transform) { + + // Reset the transform + mBody->setTransform(transform); + + mBody->setIsSleeping(false); + + // Reset the velocity of the rigid body + rp3d::RigidBody* rigidBody = dynamic_cast(mBody); + if (rigidBody != NULL) { + rigidBody->setLinearVelocity(rp3d::Vector3(0, 0, 0)); + rigidBody->setAngularVelocity(rp3d::Vector3(0, 0, 0)); + } + + updateTransform(1.0f); } diff --git a/examples/common/Cylinder.h b/testbed/common/Cylinder.h similarity index 68% rename from examples/common/Cylinder.h rename to testbed/common/Cylinder.h index 9236fe20..17379e7c 100644 --- a/examples/common/Cylinder.h +++ b/testbed/common/Cylinder.h @@ -29,9 +29,10 @@ // Libraries #include "openglframework.h" #include "reactphysics3d.h" +#include "PhysicsObject.h" // Class Cylinder -class Cylinder : public openglframework::Mesh { +class Cylinder : public openglframework::Mesh, public PhysicsObject { private : @@ -43,14 +44,35 @@ class Cylinder : public openglframework::Mesh { /// Height of the cylinder float mHeight; - /// Rigid body used to simulate the dynamics of the cylinder - rp3d::CollisionBody* mRigidBody; - /// Scaling matrix (applied to a sphere to obtain the correct cylinder dimensions) openglframework::Matrix4 mScalingMatrix; + /// Previous transform (for interpolation) + rp3d::Transform mPreviousTransform; + + /// Vertex Buffer Object for the vertices data + static openglframework::VertexBufferObject mVBOVertices; + + /// Vertex Buffer Object for the normals data + static openglframework::VertexBufferObject mVBONormals; + + /// Vertex Buffer Object for the texture coords + static openglframework::VertexBufferObject mVBOTextureCoords; + + /// Vertex Buffer Object for the indices + static openglframework::VertexBufferObject mVBOIndices; + + /// Vertex Array Object for the vertex data + static openglframework::VertexArrayObject mVAO; + + // Total number of capsules created + static int totalNbCylinders; + // -------------------- Methods -------------------- // + // Create the Vertex Buffer Objects used to render with OpenGL. + void createVBOAndVAO(); + public : // -------------------- Methods -------------------- // @@ -61,33 +83,25 @@ class Cylinder : public openglframework::Mesh { /// Constructor Cylinder(float radius, float height, const openglframework::Vector3& position, - float mass, rp3d::DynamicsWorld* dynamicsWorld, const std::string &meshFolderPath); + float mass, rp3d::DynamicsWorld* dynamicsWorld, const std::string &meshFolderPath); /// Destructor ~Cylinder(); - /// Return a pointer to the collision body of the box - reactphysics3d::CollisionBody* getCollisionBody(); - - /// Return a pointer to the rigid body of the box - reactphysics3d::RigidBody* getRigidBody(); - - /// Update the transform matrix of the cylinder - void updateTransform(); - /// Render the cylinder at the correct position and with the correct orientation void render(openglframework::Shader& shader, const openglframework::Matrix4& worldToCameraMatrix); + + /// Set the position of the box + void resetTransform(const rp3d::Transform& transform); + + /// Update the transform matrix of the object + virtual void updateTransform(float interpolationFactor); }; -// Return a pointer to the collision body of the box -inline rp3d::CollisionBody* Cylinder::getCollisionBody() { - return mRigidBody; -} - -// Return a pointer to the rigid body of the box -inline rp3d::RigidBody* Cylinder::getRigidBody() { - return dynamic_cast(mRigidBody); +// Update the transform matrix of the object +inline void Cylinder::updateTransform(float interpolationFactor) { + mTransformMatrix = computeTransform(interpolationFactor, mScalingMatrix); } #endif diff --git a/examples/common/Dumbbell.cpp b/testbed/common/Dumbbell.cpp similarity index 66% rename from examples/common/Dumbbell.cpp rename to testbed/common/Dumbbell.cpp index a358db61..12bf37e7 100644 --- a/examples/common/Dumbbell.cpp +++ b/testbed/common/Dumbbell.cpp @@ -26,6 +26,13 @@ // Libraries #include "Dumbbell.h" +openglframework::VertexBufferObject Dumbbell::mVBOVertices(GL_ARRAY_BUFFER); +openglframework::VertexBufferObject Dumbbell::mVBONormals(GL_ARRAY_BUFFER); +openglframework::VertexBufferObject Dumbbell::mVBOTextureCoords(GL_ARRAY_BUFFER); +openglframework::VertexBufferObject Dumbbell::mVBOIndices(GL_ELEMENT_ARRAY_BUFFER); +openglframework::VertexArrayObject Dumbbell::mVAO; +int Dumbbell::totalNbDumbbells = 0; + // Constructor Dumbbell::Dumbbell(const openglframework::Vector3 &position, reactphysics3d::DynamicsWorld* dynamicsWorld, const std::string& meshFolderPath) @@ -64,6 +71,8 @@ Dumbbell::Dumbbell(const openglframework::Vector3 &position, rp3d::Quaternion initOrientation(angleAroundX, 0, 0); rp3d::Transform transformBody(initPosition, initOrientation); + mPreviousTransform = transformBody; + // Initial transform of the first sphere collision shape of the dumbbell (in local-space) rp3d::Transform transformSphereShape1(rp3d::Vector3(0, 4.0, 0), rp3d::Quaternion::identity()); @@ -84,6 +93,13 @@ Dumbbell::Dumbbell(const openglframework::Vector3 &position, mBody = body; mTransformMatrix = mTransformMatrix * mScalingMatrix; + + // Create the VBOs and VAO + if (totalNbDumbbells == 0) { + createVBOAndVAO(); + } + + totalNbDumbbells++; } // Constructor @@ -142,13 +158,32 @@ Dumbbell::Dumbbell(const openglframework::Vector3 &position, mBody->addCollisionShape(cylinderCollisionShape, transformCylinderShape); mTransformMatrix = mTransformMatrix * mScalingMatrix; + + // Create the VBOs and VAO + if (totalNbDumbbells == 0) { + createVBOAndVAO(); + } + + totalNbDumbbells++; } // Destructor Dumbbell::~Dumbbell() { - // Destroy the mesh - destroy(); + if (totalNbDumbbells == 1) { + + // Destroy the mesh + destroy(); + + // Destroy the VBOs and VAO + mVBOIndices.destroy(); + mVBOVertices.destroy(); + mVBONormals.destroy(); + mVBOTextureCoords.destroy(); + mVAO.destroy(); + } + + totalNbDumbbells--; } // Render the sphere at the correct position and with the correct orientation @@ -159,58 +194,125 @@ void Dumbbell::render(openglframework::Shader& shader, shader.bind(); // Set the model to camera matrix - const openglframework::Matrix4 localToCameraMatrix = worldToCameraMatrix * mTransformMatrix; - shader.setMatrix4x4Uniform("localToCameraMatrix", localToCameraMatrix); + shader.setMatrix4x4Uniform("localToWorldMatrix", mTransformMatrix); + shader.setMatrix4x4Uniform("worldToCameraMatrix", worldToCameraMatrix); // Set the normal matrix (inverse transpose of the 3x3 upper-left sub matrix of the // model-view matrix) + const openglframework::Matrix4 localToCameraMatrix = worldToCameraMatrix * mTransformMatrix; const openglframework::Matrix3 normalMatrix = localToCameraMatrix.getUpperLeft3x3Matrix().getInverse().getTranspose(); - shader.setMatrix3x3Uniform("normalMatrix", normalMatrix); + shader.setMatrix3x3Uniform("normalMatrix", normalMatrix, false); - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_NORMAL_ARRAY); - if (hasTexture()) { - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - } + // Set the vertex color + openglframework::Vector4 color(mColor.r, mColor.g, mColor.b, mColor.a); + shader.setVector4Uniform("vertexColor", color, false); - glVertexPointer(3, GL_FLOAT, 0, getVerticesPointer()); - glNormalPointer(GL_FLOAT, 0, getNormalsPointer()); - if(hasTexture()) { - glTexCoordPointer(2, GL_FLOAT, 0, getUVTextureCoordinatesPointer()); - } + // Bind the VAO + mVAO.bind(); + + mVBOVertices.bind(); + + // Get the location of shader attribute variables + GLint vertexPositionLoc = shader.getAttribLocation("vertexPosition"); + GLint vertexNormalLoc = shader.getAttribLocation("vertexNormal", false); + + glEnableVertexAttribArray(vertexPositionLoc); + glVertexAttribPointer(vertexPositionLoc, 3, GL_FLOAT, GL_FALSE, 0, (char*)NULL); + + mVBONormals.bind(); + + if (vertexNormalLoc != -1) glEnableVertexAttribArray(vertexNormalLoc); + if (vertexNormalLoc != -1) glVertexAttribPointer(vertexNormalLoc, 3, GL_FLOAT, GL_FALSE, 0, (char*)NULL); // For each part of the mesh for (unsigned int i=0; igetInterpolatedTransform(); + // Create the VBO for the vertices data + mVBOVertices.create(); + mVBOVertices.bind(); + size_t sizeVertices = mVertices.size() * sizeof(openglframework::Vector3); + mVBOVertices.copyDataIntoVBO(sizeVertices, getVerticesPointer(), GL_STATIC_DRAW); + mVBOVertices.unbind(); - // Compute the transform used for rendering the sphere - rp3d::decimal matrix[16]; - transform.getOpenGLMatrix(matrix); - openglframework::Matrix4 newMatrix(matrix[0], matrix[4], matrix[8], matrix[12], - matrix[1], matrix[5], matrix[9], matrix[13], - matrix[2], matrix[6], matrix[10], matrix[14], - matrix[3], matrix[7], matrix[11], matrix[15]); + // Create the VBO for the normals data + mVBONormals.create(); + mVBONormals.bind(); + size_t sizeNormals = mNormals.size() * sizeof(openglframework::Vector3); + mVBONormals.copyDataIntoVBO(sizeNormals, getNormalsPointer(), GL_STATIC_DRAW); + mVBONormals.unbind(); - // Apply the scaling matrix to have the correct sphere dimensions - mTransformMatrix = newMatrix * mScalingMatrix; + if (hasTexture()) { + // Create the VBO for the texture co data + mVBOTextureCoords.create(); + mVBOTextureCoords.bind(); + size_t sizeTextureCoords = mUVs.size() * sizeof(openglframework::Vector2); + mVBOTextureCoords.copyDataIntoVBO(sizeTextureCoords, getUVTextureCoordinatesPointer(), GL_STATIC_DRAW); + mVBOTextureCoords.unbind(); + } + + // Create th VBO for the indices data + mVBOIndices.create(); + mVBOIndices.bind(); + size_t sizeIndices = mIndices[0].size() * sizeof(unsigned int); + mVBOIndices.copyDataIntoVBO(sizeIndices, getIndicesPointer(), GL_STATIC_DRAW); + mVBOIndices.unbind(); + + // Create the VAO for both VBOs + mVAO.create(); + mVAO.bind(); + + // Bind the VBO of vertices + mVBOVertices.bind(); + + // Bind the VBO of normals + mVBONormals.bind(); + + if (hasTexture()) { + // Bind the VBO of texture coords + mVBOTextureCoords.bind(); + } + + // Bind the VBO of indices + mVBOIndices.bind(); + + // Unbind the VAO + mVAO.unbind(); } +// Reset the transform +void Dumbbell::resetTransform(const rp3d::Transform& transform) { + + // Reset the transform + mBody->setTransform(transform); + + mBody->setIsSleeping(false); + + // Reset the velocity of the rigid body + rp3d::RigidBody* rigidBody = dynamic_cast(mBody); + if (rigidBody != NULL) { + rigidBody->setLinearVelocity(rp3d::Vector3(0, 0, 0)); + rigidBody->setAngularVelocity(rp3d::Vector3(0, 0, 0)); + } + + updateTransform(1.0f); +} diff --git a/examples/common/Dumbbell.h b/testbed/common/Dumbbell.h similarity index 69% rename from examples/common/Dumbbell.h rename to testbed/common/Dumbbell.h index ff673951..cde7cea9 100644 --- a/examples/common/Dumbbell.h +++ b/testbed/common/Dumbbell.h @@ -29,9 +29,10 @@ // Libraries #include "openglframework.h" #include "reactphysics3d.h" +#include "PhysicsObject.h" // Class Sphere -class Dumbbell : public openglframework::Mesh { +class Dumbbell : public openglframework::Mesh, public PhysicsObject { private : @@ -40,14 +41,35 @@ class Dumbbell : public openglframework::Mesh { /// Radius of the spheres float mRadius; - /// Rigid body used to simulate the dynamics of the sphere - rp3d::CollisionBody* mBody; - /// Scaling matrix (applied to a sphere to obtain the correct sphere dimensions) openglframework::Matrix4 mScalingMatrix; + /// Previous transform (for interpolation) + rp3d::Transform mPreviousTransform; + + /// Vertex Buffer Object for the vertices data + static openglframework::VertexBufferObject mVBOVertices; + + /// Vertex Buffer Object for the normals data + static openglframework::VertexBufferObject mVBONormals; + + /// Vertex Buffer Object for the texture coords + static openglframework::VertexBufferObject mVBOTextureCoords; + + /// Vertex Buffer Object for the indices + static openglframework::VertexBufferObject mVBOIndices; + + /// Vertex Array Object for the vertex data + static openglframework::VertexArrayObject mVAO; + + // Total number of capsules created + static int totalNbDumbbells; + // -------------------- Methods -------------------- // + // Create the Vertex Buffer Objects used to render with OpenGL. + void createVBOAndVAO(); + public : // -------------------- Methods -------------------- // @@ -64,28 +86,20 @@ class Dumbbell : public openglframework::Mesh { /// Destructor ~Dumbbell(); - /// Return a pointer to the rigid body - rp3d::RigidBody* getRigidBody(); - - /// Return a pointer to the body - rp3d::CollisionBody* getCollisionBody(); - - /// Update the transform matrix of the sphere - void updateTransform(); - /// Render the sphere at the correct position and with the correct orientation void render(openglframework::Shader& shader, const openglframework::Matrix4& worldToCameraMatrix); + + /// Set the position of the box + void resetTransform(const rp3d::Transform& transform); + + /// Update the transform matrix of the object + virtual void updateTransform(float interpolationFactor); }; -// Return a pointer to the rigid body of the sphere -inline rp3d::RigidBody* Dumbbell::getRigidBody() { - return dynamic_cast(mBody); -} - -// Return a pointer to the body -inline rp3d::CollisionBody* Dumbbell::getCollisionBody() { - return mBody; +// Update the transform matrix of the object +inline void Dumbbell::updateTransform(float interpolationFactor) { + mTransformMatrix = computeTransform(interpolationFactor, mScalingMatrix); } #endif diff --git a/examples/common/Line.cpp b/testbed/common/Line.cpp similarity index 90% rename from examples/common/Line.cpp rename to testbed/common/Line.cpp index 9263c1d6..f95aedaa 100644 --- a/examples/common/Line.cpp +++ b/testbed/common/Line.cpp @@ -47,7 +47,12 @@ void Line::render(openglframework::Shader& shader, shader.bind(); // Set the model to camera matrix - shader.setMatrix4x4Uniform("localToCameraMatrix", worldToCameraMatrix); + shader.setMatrix4x4Uniform("localToWorldMatrix", openglframework::Matrix4::identity()); + shader.setMatrix4x4Uniform("worldToCameraMatrix", worldToCameraMatrix); + + // Set the vertex color + openglframework::Vector4 color(1, 0, 0, 1); + shader.setVector4Uniform("vertexColor", color, false); glBegin(GL_LINES); glVertex3f(mWorldPoint1.x, mWorldPoint1.y, mWorldPoint1.z); diff --git a/examples/common/Line.h b/testbed/common/Line.h similarity index 97% rename from examples/common/Line.h rename to testbed/common/Line.h index 41de3266..5f6b0d1f 100644 --- a/examples/common/Line.h +++ b/testbed/common/Line.h @@ -58,9 +58,6 @@ class Line : public openglframework::Object3D { /// Return the second point of the line openglframework::Vector3 getPoint2() const; - /// Update the transform matrix of the sphere - void updateTransform(); - /// Render the line at the correct position and with the correct orientation void render(openglframework::Shader& shader, const openglframework::Matrix4& worldToCameraMatrix); diff --git a/testbed/common/PhysicsObject.cpp b/testbed/common/PhysicsObject.cpp new file mode 100644 index 00000000..3bc7be77 --- /dev/null +++ b/testbed/common/PhysicsObject.cpp @@ -0,0 +1,59 @@ +/******************************************************************************** +* ReactPhysics3D physics library, http://www.reactphysics3d.com * +* Copyright (c) 2010-2015 Daniel Chappuis * +********************************************************************************* +* * +* This software is provided 'as-is', without any express or implied warranty. * +* In no event will the authors be held liable for any damages arising from the * +* use of this software. * +* * +* Permission is granted to anyone to use this software for any purpose, * +* including commercial applications, and to alter it and redistribute it * +* freely, subject to the following restrictions: * +* * +* 1. The origin of this software must not be misrepresented; you must not claim * +* that you wrote the original software. If you use this software in a * +* product, an acknowledgment in the product documentation would be * +* appreciated but is not required. * +* * +* 2. Altered source versions must be plainly marked as such, and must not be * +* misrepresented as being the original software. * +* * +* 3. This notice may not be removed or altered from any source distribution. * +* * +********************************************************************************/ + +// Libraries +#include "PhysicsObject.h" + +/// Constructor +PhysicsObject::PhysicsObject() { + + mColor = openglframework::Color(1, 1, 1, 1); + mSleepingColor = openglframework::Color(1, 0, 0, 1); +} + +// Compute the new transform matrix +openglframework::Matrix4 PhysicsObject::computeTransform(float interpolationFactor, + const openglframework::Matrix4& scalingMatrix) { + + // Get the transform of the rigid body + rp3d::Transform transform = mBody->getTransform(); + + // Interpolate the transform between the previous one and the new one + rp3d::Transform interpolatedTransform = rp3d::Transform::interpolateTransforms(mPreviousTransform, + transform, + interpolationFactor); + mPreviousTransform = transform; + + // Compute the transform used for rendering the box + rp3d::decimal matrix[16]; + interpolatedTransform.getOpenGLMatrix(matrix); + openglframework::Matrix4 newMatrix(matrix[0], matrix[4], matrix[8], matrix[12], + matrix[1], matrix[5], matrix[9], matrix[13], + matrix[2], matrix[6], matrix[10], matrix[14], + matrix[3], matrix[7], matrix[11], matrix[15]); + + // Apply the scaling matrix to have the correct box dimensions + return newMatrix * scalingMatrix; +} diff --git a/testbed/common/PhysicsObject.h b/testbed/common/PhysicsObject.h new file mode 100644 index 00000000..1e1ef69d --- /dev/null +++ b/testbed/common/PhysicsObject.h @@ -0,0 +1,96 @@ +/******************************************************************************** +* ReactPhysics3D physics library, http://www.reactphysics3d.com * +* Copyright (c) 2010-2015 Daniel Chappuis * +********************************************************************************* +* * +* This software is provided 'as-is', without any express or implied warranty. * +* In no event will the authors be held liable for any damages arising from the * +* use of this software. * +* * +* Permission is granted to anyone to use this software for any purpose, * +* including commercial applications, and to alter it and redistribute it * +* freely, subject to the following restrictions: * +* * +* 1. The origin of this software must not be misrepresented; you must not claim * +* that you wrote the original software. If you use this software in a * +* product, an acknowledgment in the product documentation would be * +* appreciated but is not required. * +* * +* 2. Altered source versions must be plainly marked as such, and must not be * +* misrepresented as being the original software. * +* * +* 3. This notice may not be removed or altered from any source distribution. * +* * +********************************************************************************/ + +#ifndef PHYSICSOBJECT_H +#define PHYSICSOBJECT_H + +// Libraries +#include "openglframework.h" +#include "reactphysics3d.h" + +// Class PhysicsObject +class PhysicsObject { + + protected: + + /// Body used to simulate the dynamics of the box + rp3d::CollisionBody* mBody; + + /// Previous transform of the body (for interpolation) + rp3d::Transform mPreviousTransform; + + /// Main color of the box + openglframework::Color mColor; + + /// Sleeping color + openglframework::Color mSleepingColor; + + // Compute the new transform matrix + openglframework::Matrix4 computeTransform(float interpolationFactor, + const openglframework::Matrix4 &scalingMatrix); + + public: + + /// Constructor + PhysicsObject(); + + /// Update the transform matrix of the object + virtual void updateTransform(float interpolationFactor)=0; + + /// Set the color of the box + void setColor(const openglframework::Color& color); + + /// Set the sleeping color of the box + void setSleepingColor(const openglframework::Color& color); + + /// Return a pointer to the collision body of the box + reactphysics3d::CollisionBody* getCollisionBody(); + + /// Return a pointer to the rigid body of the box + reactphysics3d::RigidBody* getRigidBody(); +}; + +// Set the color of the box +inline void PhysicsObject::setColor(const openglframework::Color& color) { + mColor = color; +} + +// Set the sleeping color of the box +inline void PhysicsObject::setSleepingColor(const openglframework::Color& color) { + mSleepingColor = color; +} + +// Return a pointer to the collision body of the box +inline rp3d::CollisionBody* PhysicsObject::getCollisionBody() { + return mBody; +} + +// Return a pointer to the rigid body of the box (NULL if it's not a rigid body) +inline rp3d::RigidBody* PhysicsObject::getRigidBody() { + return dynamic_cast(mBody); +} + +#endif + diff --git a/examples/common/Sphere.cpp b/testbed/common/Sphere.cpp similarity index 57% rename from examples/common/Sphere.cpp rename to testbed/common/Sphere.cpp index 56882569..61fe9671 100644 --- a/examples/common/Sphere.cpp +++ b/testbed/common/Sphere.cpp @@ -26,6 +26,13 @@ // Libraries #include "Sphere.h" +openglframework::VertexBufferObject Sphere::mVBOVertices(GL_ARRAY_BUFFER); +openglframework::VertexBufferObject Sphere::mVBONormals(GL_ARRAY_BUFFER); +openglframework::VertexBufferObject Sphere::mVBOTextureCoords(GL_ARRAY_BUFFER); +openglframework::VertexBufferObject Sphere::mVBOIndices(GL_ELEMENT_ARRAY_BUFFER); +openglframework::VertexArrayObject Sphere::mVAO; +int Sphere::totalNbSpheres = 0; + // Constructor Sphere::Sphere(float radius, const openglframework::Vector3 &position, reactphysics3d::CollisionWorld* world, @@ -57,13 +64,22 @@ Sphere::Sphere(float radius, const openglframework::Vector3 &position, rp3d::Quaternion initOrientation = rp3d::Quaternion::identity(); rp3d::Transform transform(initPosition, initOrientation); + mPreviousTransform = transform; + // Create a rigid body corresponding to the sphere in the dynamics world - mRigidBody = world->createCollisionBody(transform); + mBody = world->createCollisionBody(transform); // Add a collision shape to the body and specify the mass of the shape - mRigidBody->addCollisionShape(collisionShape, rp3d::Transform::identity()); + mBody->addCollisionShape(collisionShape, rp3d::Transform::identity()); mTransformMatrix = mTransformMatrix * mScalingMatrix; + + // Create the VBOs and VAO + if (totalNbSpheres == 0) { + createVBOAndVAO(); + } + + totalNbSpheres++; } // Constructor @@ -103,16 +119,34 @@ Sphere::Sphere(float radius, const openglframework::Vector3 &position, // Add a collision shape to the body and specify the mass of the shape body->addCollisionShape(collisionShape, rp3d::Transform::identity(), mass); - mRigidBody = body; + mBody = body; mTransformMatrix = mTransformMatrix * mScalingMatrix; + + // Create the VBOs and VAO + if (totalNbSpheres == 0) { + createVBOAndVAO(); + } + + totalNbSpheres++; } // Destructor Sphere::~Sphere() { - // Destroy the mesh - destroy(); + if (totalNbSpheres == 1) { + // Destroy the mesh + destroy(); + + // Destroy the VBOs and VAO + mVBOIndices.destroy(); + mVBOVertices.destroy(); + mVBONormals.destroy(); + mVBOTextureCoords.destroy(); + mVAO.destroy(); + } + + totalNbSpheres--; } // Render the sphere at the correct position and with the correct orientation @@ -123,57 +157,125 @@ void Sphere::render(openglframework::Shader& shader, shader.bind(); // Set the model to camera matrix - const openglframework::Matrix4 localToCameraMatrix = worldToCameraMatrix * mTransformMatrix; - shader.setMatrix4x4Uniform("localToCameraMatrix", localToCameraMatrix); + shader.setMatrix4x4Uniform("localToWorldMatrix", mTransformMatrix); + shader.setMatrix4x4Uniform("worldToCameraMatrix", worldToCameraMatrix); // Set the normal matrix (inverse transpose of the 3x3 upper-left sub matrix of the // model-view matrix) + const openglframework::Matrix4 localToCameraMatrix = worldToCameraMatrix * mTransformMatrix; const openglframework::Matrix3 normalMatrix = localToCameraMatrix.getUpperLeft3x3Matrix().getInverse().getTranspose(); - shader.setMatrix3x3Uniform("normalMatrix", normalMatrix); + shader.setMatrix3x3Uniform("normalMatrix", normalMatrix, false); - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_NORMAL_ARRAY); - if (hasTexture()) { - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - } + // Set the vertex color + openglframework::Vector4 color(mColor.r, mColor.g, mColor.b, mColor.a); + shader.setVector4Uniform("vertexColor", color, false); - glVertexPointer(3, GL_FLOAT, 0, getVerticesPointer()); - glNormalPointer(GL_FLOAT, 0, getNormalsPointer()); - if(hasTexture()) { - glTexCoordPointer(2, GL_FLOAT, 0, getUVTextureCoordinatesPointer()); - } + // Bind the VAO + mVAO.bind(); + + mVBOVertices.bind(); + + // Get the location of shader attribute variables + GLint vertexPositionLoc = shader.getAttribLocation("vertexPosition"); + GLint vertexNormalLoc = shader.getAttribLocation("vertexNormal", false); + + glEnableVertexAttribArray(vertexPositionLoc); + glVertexAttribPointer(vertexPositionLoc, 3, GL_FLOAT, GL_FALSE, 0, (char*)NULL); + + mVBONormals.bind(); + + if (vertexNormalLoc != -1) glVertexAttribPointer(vertexNormalLoc, 3, GL_FLOAT, GL_FALSE, 0, (char*)NULL); + if (vertexNormalLoc != -1) glEnableVertexAttribArray(vertexNormalLoc); // For each part of the mesh for (unsigned int i=0; igetInterpolatedTransform(); + // Create the VBO for the vertices data + mVBOVertices.create(); + mVBOVertices.bind(); + size_t sizeVertices = mVertices.size() * sizeof(openglframework::Vector3); + mVBOVertices.copyDataIntoVBO(sizeVertices, getVerticesPointer(), GL_STATIC_DRAW); + mVBOVertices.unbind(); - // Compute the transform used for rendering the sphere - rp3d::decimal matrix[16]; - transform.getOpenGLMatrix(matrix); - openglframework::Matrix4 newMatrix(matrix[0], matrix[4], matrix[8], matrix[12], - matrix[1], matrix[5], matrix[9], matrix[13], - matrix[2], matrix[6], matrix[10], matrix[14], - matrix[3], matrix[7], matrix[11], matrix[15]); + // Create the VBO for the normals data + mVBONormals.create(); + mVBONormals.bind(); + size_t sizeNormals = mNormals.size() * sizeof(openglframework::Vector3); + mVBONormals.copyDataIntoVBO(sizeNormals, getNormalsPointer(), GL_STATIC_DRAW); + mVBONormals.unbind(); - // Apply the scaling matrix to have the correct sphere dimensions - mTransformMatrix = newMatrix * mScalingMatrix; + if (hasTexture()) { + // Create the VBO for the texture co data + mVBOTextureCoords.create(); + mVBOTextureCoords.bind(); + size_t sizeTextureCoords = mUVs.size() * sizeof(openglframework::Vector2); + mVBOTextureCoords.copyDataIntoVBO(sizeTextureCoords, getUVTextureCoordinatesPointer(), GL_STATIC_DRAW); + mVBOTextureCoords.unbind(); + } + + // Create th VBO for the indices data + mVBOIndices.create(); + mVBOIndices.bind(); + size_t sizeIndices = mIndices[0].size() * sizeof(uint); + mVBOIndices.copyDataIntoVBO(sizeIndices, getIndicesPointer(), GL_STATIC_DRAW); + mVBOIndices.unbind(); + + // Create the VAO for both VBOs + mVAO.create(); + mVAO.bind(); + + // Bind the VBO of vertices + mVBOVertices.bind(); + + // Bind the VBO of normals + mVBONormals.bind(); + + if (hasTexture()) { + // Bind the VBO of texture coords + mVBOTextureCoords.bind(); + } + + // Bind the VBO of indices + mVBOIndices.bind(); + + // Unbind the VAO + mVAO.unbind(); +} + +// Reset the transform +void Sphere::resetTransform(const rp3d::Transform& transform) { + + // Reset the transform + mBody->setTransform(transform); + + mBody->setIsSleeping(false); + + // Reset the velocity of the rigid body + rp3d::RigidBody* rigidBody = dynamic_cast(mBody); + if (rigidBody != NULL) { + rigidBody->setLinearVelocity(rp3d::Vector3(0, 0, 0)); + rigidBody->setAngularVelocity(rp3d::Vector3(0, 0, 0)); + } + + updateTransform(1.0f); } diff --git a/examples/common/Sphere.h b/testbed/common/Sphere.h similarity index 68% rename from examples/common/Sphere.h rename to testbed/common/Sphere.h index 0401d52c..41d159e3 100644 --- a/examples/common/Sphere.h +++ b/testbed/common/Sphere.h @@ -29,9 +29,10 @@ // Libraries #include "openglframework.h" #include "reactphysics3d.h" +#include "PhysicsObject.h" // Class Sphere -class Sphere : public openglframework::Mesh { +class Sphere : public openglframework::Mesh, public PhysicsObject { private : @@ -40,14 +41,35 @@ class Sphere : public openglframework::Mesh { /// Radius of the sphere float mRadius; - /// Rigid body used to simulate the dynamics of the sphere - rp3d::CollisionBody* mRigidBody; - /// Scaling matrix (applied to a sphere to obtain the correct sphere dimensions) openglframework::Matrix4 mScalingMatrix; + /// Previous transform (for interpolation) + rp3d::Transform mPreviousTransform; + + /// Vertex Buffer Object for the vertices data + static openglframework::VertexBufferObject mVBOVertices; + + /// Vertex Buffer Object for the normals data + static openglframework::VertexBufferObject mVBONormals; + + /// Vertex Buffer Object for the texture coords + static openglframework::VertexBufferObject mVBOTextureCoords; + + /// Vertex Buffer Object for the indices + static openglframework::VertexBufferObject mVBOIndices; + + /// Vertex Array Object for the vertex data + static openglframework::VertexArrayObject mVAO; + + // Total number of capsules created + static int totalNbSpheres; + // -------------------- Methods -------------------- // + // Create the Vertex Buffer Objects used to render with OpenGL. + void createVBOAndVAO(); + public : // -------------------- Methods -------------------- // @@ -63,28 +85,26 @@ class Sphere : public openglframework::Mesh { /// Destructor ~Sphere(); - /// Return a pointer to the collision body of the box - reactphysics3d::CollisionBody* getCollisionBody(); - - /// Return a pointer to the rigid body of the box - reactphysics3d::RigidBody* getRigidBody(); - - /// Update the transform matrix of the sphere - void updateTransform(); - /// Render the sphere at the correct position and with the correct orientation void render(openglframework::Shader& shader, const openglframework::Matrix4& worldToCameraMatrix); + + /// Set the position of the box + void resetTransform(const rp3d::Transform& transform); + + /// Update the transform matrix of the object + virtual void updateTransform(float interpolationFactor); }; -// Return a pointer to the collision body of the box -inline rp3d::CollisionBody* Sphere::getCollisionBody() { - return mRigidBody; -} - +<<<<<<< HEAD:examples/common/Sphere.h // Return a pointer to the rigid body of the box inline rp3d::RigidBody* Sphere::getRigidBody() { return static_cast(mRigidBody); +======= +// Update the transform matrix of the object +inline void Sphere::updateTransform(float interpolationFactor) { + mTransformMatrix = computeTransform(interpolationFactor, mScalingMatrix); +>>>>>>> 1bde11f245806de07a12b1cf6c33cdb83046b882:testbed/common/Sphere.h } #endif diff --git a/examples/common/Viewer.cpp b/testbed/common/Viewer.cpp similarity index 100% rename from examples/common/Viewer.cpp rename to testbed/common/Viewer.cpp diff --git a/examples/common/Viewer.h b/testbed/common/Viewer.h similarity index 100% rename from examples/common/Viewer.h rename to testbed/common/Viewer.h diff --git a/testbed/common/VisualContactPoint.cpp b/testbed/common/VisualContactPoint.cpp new file mode 100644 index 00000000..0014a980 --- /dev/null +++ b/testbed/common/VisualContactPoint.cpp @@ -0,0 +1,183 @@ +/******************************************************************************** +* ReactPhysics3D physics library, http://www.reactphysics3d.com * +* Copyright (c) 2010-2015 Daniel Chappuis * +********************************************************************************* +* * +* This software is provided 'as-is', without any express or implied warranty. * +* In no event will the authors be held liable for any damages arising from the * +* use of this software. * +* * +* Permission is granted to anyone to use this software for any purpose, * +* including commercial applications, and to alter it and redistribute it * +* freely, subject to the following restrictions: * +* * +* 1. The origin of this software must not be misrepresented; you must not claim * +* that you wrote the original software. If you use this software in a * +* product, an acknowledgment in the product documentation would be * +* appreciated but is not required. * +* * +* 2. Altered source versions must be plainly marked as such, and must not be * +* misrepresented as being the original software. * +* * +* 3. This notice may not be removed or altered from any source distribution. * +* * +********************************************************************************/ + +// Libraries +#include "VisualContactPoint.h" + +// Initialization of static variables +openglframework::VertexBufferObject VisualContactPoint::mVBOVertices(GL_ARRAY_BUFFER); +openglframework::VertexBufferObject VisualContactPoint::mVBONormals(GL_ARRAY_BUFFER); +openglframework::VertexBufferObject VisualContactPoint::mVBOIndices(GL_ELEMENT_ARRAY_BUFFER); +openglframework::VertexArrayObject VisualContactPoint::mVAO; +int VisualContactPoint::mNbTotalPoints = 0; +openglframework::Mesh VisualContactPoint::mMesh; +bool VisualContactPoint::mStaticDataCreated = false; + +// Constructor +VisualContactPoint::VisualContactPoint(const openglframework::Vector3& position, + const std::string& meshFolderPath) + : mColor(1.0f, 0.0f, 0.0f, 1.0f) { + + // Initialize the position where the mesh will be rendered + translateWorld(position); +} + +// Destructor +VisualContactPoint::~VisualContactPoint() { + +} + +// Load and initialize the mesh for all the contact points +void VisualContactPoint::createStaticData(const std::string& meshFolderPath) { + + if (mStaticDataCreated) return; + + // Load the mesh from a file + openglframework::MeshReaderWriter::loadMeshFromFile(meshFolderPath + "sphere.obj", mMesh); + + // Calculate the normals of the mesh + mMesh.calculateNormals(); + + mMesh.scaleVertices(VISUAL_CONTACT_POINT_RADIUS); + + createVBOAndVAO(); + + mStaticDataCreated = true; +} + +// Destroy the mesh for the contact points +void VisualContactPoint::destroyStaticData() { + + if (!mStaticDataCreated) return; + + // Destroy the VBOs and VAO + mVBOIndices.destroy(); + mVBOVertices.destroy(); + mVBONormals.destroy(); + mVAO.destroy(); + + mMesh.destroy(); + + mStaticDataCreated = false; +} + +// Render the sphere at the correct position and with the correct orientation +void VisualContactPoint::render(openglframework::Shader& shader, + const openglframework::Matrix4& worldToCameraMatrix) { + + // Bind the VAO + mVAO.bind(); + + // Bind the shader + shader.bind(); + + mVBOVertices.bind(); + + // Set the model to camera matrix + shader.setMatrix4x4Uniform("localToWorldMatrix", mTransformMatrix); + shader.setMatrix4x4Uniform("worldToCameraMatrix", worldToCameraMatrix); + + // Set the normal matrix (inverse transpose of the 3x3 upper-left sub matrix of the + // model-view matrix) + const openglframework::Matrix4 localToCameraMatrix = worldToCameraMatrix * mTransformMatrix; + const openglframework::Matrix3 normalMatrix = + localToCameraMatrix.getUpperLeft3x3Matrix().getInverse().getTranspose(); + shader.setMatrix3x3Uniform("normalMatrix", normalMatrix, false); + + // Set the vertex color + openglframework::Vector4 color(mColor.r, mColor.g, mColor.b, mColor.a); + shader.setVector4Uniform("vertexColor", color, false); + + // Get the location of shader attribute variables + GLint vertexPositionLoc = shader.getAttribLocation("vertexPosition"); + GLint vertexNormalLoc = shader.getAttribLocation("vertexNormal", false); + + glEnableVertexAttribArray(vertexPositionLoc); + glVertexAttribPointer(vertexPositionLoc, 3, GL_FLOAT, GL_FALSE, 0, (char*)NULL); + + mVBONormals.bind(); + + if (vertexNormalLoc != -1) glVertexAttribPointer(vertexNormalLoc, 3, GL_FLOAT, GL_FALSE, 0, (char*)NULL); + if (vertexNormalLoc != -1) glEnableVertexAttribArray(vertexNormalLoc); + + // For each part of the mesh + for (unsigned int i=0; i +#include +#define _USE_MATH_DEFINES +#include +#include "imgui.h" + +#ifdef _MSC_VER +# define snprintf _snprintf +#endif + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +static const unsigned TEXT_POOL_SIZE = 8000; +static char g_textPool[TEXT_POOL_SIZE]; +static unsigned g_textPoolSize = 0; +static const char* allocText(const char* text) +{ + unsigned len = strlen(text)+1; + if (g_textPoolSize + len >= TEXT_POOL_SIZE) + return 0; + char* dst = &g_textPool[g_textPoolSize]; + memcpy(dst, text, len); + g_textPoolSize += len; + return dst; +} + +static const unsigned GFXCMD_QUEUE_SIZE = 5000; +static imguiGfxCmd g_gfxCmdQueue[GFXCMD_QUEUE_SIZE]; +static unsigned g_gfxCmdQueueSize = 0; + +static void resetGfxCmdQueue() +{ + g_gfxCmdQueueSize = 0; + g_textPoolSize = 0; +} + +static void addGfxCmdScissor(int x, int y, int w, int h) +{ + if (g_gfxCmdQueueSize >= GFXCMD_QUEUE_SIZE) + return; + imguiGfxCmd& cmd = g_gfxCmdQueue[g_gfxCmdQueueSize++]; + cmd.type = IMGUI_GFXCMD_SCISSOR; + cmd.flags = x < 0 ? 0 : 1; // on/off flag. + cmd.col = 0; + cmd.rect.x = (short)x; + cmd.rect.y = (short)y; + cmd.rect.w = (short)w; + cmd.rect.h = (short)h; +} + +static void addGfxCmdRect(float x, float y, float w, float h, unsigned int color) +{ + if (g_gfxCmdQueueSize >= GFXCMD_QUEUE_SIZE) + return; + imguiGfxCmd& cmd = g_gfxCmdQueue[g_gfxCmdQueueSize++]; + cmd.type = IMGUI_GFXCMD_RECT; + cmd.flags = 0; + cmd.col = color; + cmd.rect.x = (short)(x*8.0f); + cmd.rect.y = (short)(y*8.0f); + cmd.rect.w = (short)(w*8.0f); + cmd.rect.h = (short)(h*8.0f); + cmd.rect.r = 0; +} + +static void addGfxCmdLine(float x0, float y0, float x1, float y1, float r, unsigned int color) +{ + if (g_gfxCmdQueueSize >= GFXCMD_QUEUE_SIZE) + return; + imguiGfxCmd& cmd = g_gfxCmdQueue[g_gfxCmdQueueSize++]; + cmd.type = IMGUI_GFXCMD_LINE; + cmd.flags = 0; + cmd.col = color; + cmd.line.x0 = (short)(x0*8.0f); + cmd.line.y0 = (short)(y0*8.0f); + cmd.line.x1 = (short)(x1*8.0f); + cmd.line.y1 = (short)(y1*8.0f); + cmd.line.r = (short)(r*8.0f); +} + +static void addGfxCmdRoundedRect(float x, float y, float w, float h, float r, unsigned int color) +{ + if (g_gfxCmdQueueSize >= GFXCMD_QUEUE_SIZE) + return; + imguiGfxCmd& cmd = g_gfxCmdQueue[g_gfxCmdQueueSize++]; + cmd.type = IMGUI_GFXCMD_RECT; + cmd.flags = 0; + cmd.col = color; + cmd.rect.x = (short)(x*8.0f); + cmd.rect.y = (short)(y*8.0f); + cmd.rect.w = (short)(w*8.0f); + cmd.rect.h = (short)(h*8.0f); + cmd.rect.r = (short)(r*8.0f); +} + +static void addGfxCmdTriangle(int x, int y, int w, int h, int flags, unsigned int color) +{ + if (g_gfxCmdQueueSize >= GFXCMD_QUEUE_SIZE) + return; + imguiGfxCmd& cmd = g_gfxCmdQueue[g_gfxCmdQueueSize++]; + cmd.type = IMGUI_GFXCMD_TRIANGLE; + cmd.flags = (char)flags; + cmd.col = color; + cmd.rect.x = (short)(x*8.0f); + cmd.rect.y = (short)(y*8.0f); + cmd.rect.w = (short)(w*8.0f); + cmd.rect.h = (short)(h*8.0f); +} + +static void addGfxCmdText(int x, int y, int align, const char* text, unsigned int color) +{ + if (g_gfxCmdQueueSize >= GFXCMD_QUEUE_SIZE) + return; + imguiGfxCmd& cmd = g_gfxCmdQueue[g_gfxCmdQueueSize++]; + cmd.type = IMGUI_GFXCMD_TEXT; + cmd.flags = 0; + cmd.col = color; + cmd.text.x = (short)x; + cmd.text.y = (short)y; + cmd.text.align = (short)align; + cmd.text.text = allocText(text); +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +struct GuiState +{ + GuiState() : + left(false), leftPressed(false), leftReleased(false), + mx(-1), my(-1), scroll(0), + active(0), hot(0), hotToBe(0), isHot(false), isActive(false), wentActive(false), + dragX(0), dragY(0), dragOrig(0), widgetX(0), widgetY(0), widgetW(100), + insideCurrentScroll(false), nextItemSameLine(false), newLinePreviousX(0), + areaId(0), widgetId(0) + { + } + + bool left; + bool leftPressed, leftReleased; + int mx,my; + int scroll; + unsigned int active; + unsigned int hot; + unsigned int hotToBe; + bool isHot; + bool isActive; + bool wentActive; + int dragX, dragY; + float dragOrig; + int widgetX, widgetY, widgetW; + bool insideCurrentScroll; + bool nextItemSameLine; + int newLinePreviousX; + + unsigned int areaId; + unsigned int widgetId; +}; + +static GuiState g_state; + +inline bool anyActive() +{ + return g_state.active != 0; +} + +inline bool isActive(unsigned int id) +{ + return g_state.active == id; +} + +inline bool isHot(unsigned int id) +{ + return g_state.hot == id; +} + +inline bool inRect(int x, int y, int w, int h, bool checkScroll = true) +{ + return (!checkScroll || g_state.insideCurrentScroll) && g_state.mx >= x && g_state.mx <= x+w && g_state.my >= y && g_state.my <= y+h; +} + +inline void clearInput() +{ + g_state.leftPressed = false; + g_state.leftReleased = false; + g_state.scroll = 0; +} + +inline void clearActive() +{ + g_state.active = 0; + // mark all UI for this frame as processed + clearInput(); +} + +inline void setActive(unsigned int id) +{ + g_state.active = id; + g_state.wentActive = true; +} + +inline void setHot(unsigned int id) +{ + g_state.hotToBe = id; +} + + +static bool buttonLogic(unsigned int id, bool over) +{ + bool res = false; + // process down + if (!anyActive()) + { + if (over) + setHot(id); + if (isHot(id) && g_state.leftPressed) + setActive(id); + } + + // if button is active, then react on left up + if (isActive(id)) + { + g_state.isActive = true; + if (over) + setHot(id); + if (g_state.leftReleased) + { + if (isHot(id)) + res = true; + clearActive(); + } + } + + if (isHot(id)) + g_state.isHot = true; + + return res; +} + +static void updateInput(int mx, int my, unsigned char mbut, int scroll) +{ + bool left = (mbut & IMGUI_MBUT_LEFT) != 0; + + g_state.mx = mx; + g_state.my = my; + g_state.leftPressed = !g_state.left && left; + g_state.leftReleased = g_state.left && !left; + g_state.left = left; + + g_state.scroll = scroll; +} + +void imguiBeginFrame(int mx, int my, unsigned char mbut, int scroll) +{ + updateInput(mx,my,mbut,scroll); + + g_state.hot = g_state.hotToBe; + g_state.hotToBe = 0; + + g_state.wentActive = false; + g_state.isActive = false; + g_state.isHot = false; + + g_state.widgetX = 0; + g_state.widgetY = 0; + g_state.widgetW = 0; + + g_state.areaId = 1; + g_state.widgetId = 1; + + resetGfxCmdQueue(); +} + +void imguiEndFrame() +{ + clearInput(); +} + +const imguiGfxCmd* imguiGetRenderQueue() +{ + return g_gfxCmdQueue; +} + +int imguiGetRenderQueueSize() +{ + return g_gfxCmdQueueSize; +} + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +static int g_scrollTop = 0; +static int g_scrollBottom = 0; +static int g_scrollRight = 0; +static int g_scrollAreaTop = 0; +static int* g_scrollVal = 0; +static int g_focusTop = 0; +static int g_focusBottom = 0; +static unsigned int g_scrollId = 0; +static bool g_insideScrollArea = false; + +bool imguiBeginScrollArea(const char* name, int x, int y, int w, int h, int* scroll) +{ + g_state.areaId++; + g_state.widgetId = 0; + g_scrollId = (g_state.areaId<<16) | g_state.widgetId; + + const int areaHeading = name ? AREA_HEADER : 0; + + g_state.widgetX = x + SCROLL_AREA_PADDING; + g_state.widgetY = y+h-areaHeading + (*scroll) - SCROll_AREA_TOP_PADDING; + g_state.widgetW = w - SCROLL_AREA_PADDING*4; + g_scrollTop = y-areaHeading+h; + g_scrollBottom = y+SCROLL_AREA_PADDING; + g_scrollRight = x+w - SCROLL_AREA_PADDING*3; + g_scrollVal = scroll; + + g_scrollAreaTop = g_state.widgetY; + + g_focusTop = y-areaHeading; + g_focusBottom = y-areaHeading+h; + + g_insideScrollArea = inRect(x, y, w, h, false); + g_state.insideCurrentScroll = g_insideScrollArea; + + addGfxCmdRoundedRect((float)x, (float)y, (float)w, (float)h, 6, imguiRGBA(40,40,40,192)); + + if (name) addGfxCmdText(x+areaHeading/2, y+h-areaHeading/2-TEXT_HEIGHT/2, IMGUI_ALIGN_LEFT, name, imguiRGBA(255,255,255,128)); + + addGfxCmdScissor(x+SCROLL_AREA_PADDING, y+SCROLL_AREA_PADDING, w-SCROLL_AREA_PADDING*4, h-areaHeading-SCROLL_AREA_PADDING); + + return g_insideScrollArea; +} + +void imguiEndScrollArea() +{ + // Disable scissoring. + addGfxCmdScissor(-1,-1,-1,-1); + + // Draw scroll bar + int x = g_scrollRight+SCROLL_AREA_PADDING/2; + int y = g_scrollBottom; + int w = SCROLL_AREA_PADDING*2; + int h = g_scrollTop - g_scrollBottom; + + int stop = g_scrollAreaTop; + int sbot = g_state.widgetY; + int sh = stop - sbot; // The scrollable area height. + + float barHeight = (float)h/(float)sh; + + if (barHeight < 1) + { + float barY = (float)(y - sbot)/(float)sh; + if (barY < 0) barY = 0; + if (barY > 1) barY = 1; + + // Handle scroll bar logic. + unsigned int hid = g_scrollId; + int hx = x; + int hy = y + (int)(barY*h); + int hw = w; + int hh = (int)(barHeight*h); + + const int range = h - (hh-1); + bool over = inRect(hx, hy, hw, hh); + buttonLogic(hid, over); + if (isActive(hid)) + { + float u = (float)(hy-y) / (float)range; + if (g_state.wentActive) + { + g_state.dragY = g_state.my; + g_state.dragOrig = u; + } + if (g_state.dragY != g_state.my) + { + u = g_state.dragOrig + (g_state.my - g_state.dragY) / (float)range; + if (u < 0) u = 0; + if (u > 1) u = 1; + *g_scrollVal = (int)((1-u) * (sh - h)); + } + } + + // BG + addGfxCmdRoundedRect((float)x, (float)y, (float)w, (float)h, (float)w/2-1, imguiRGBA(0,0,0,196)); + // Bar + if (isActive(hid)) + addGfxCmdRoundedRect((float)hx, (float)hy, (float)hw, (float)hh, (float)w/2-1, imguiRGBA(255,196,0,196)); + else + addGfxCmdRoundedRect((float)hx, (float)hy, (float)hw, (float)hh, (float)w/2-1, isHot(hid) ? imguiRGBA(255,196,0,96) : imguiRGBA(255,255,255,64)); + + // Handle mouse scrolling. + if (g_insideScrollArea) // && !anyActive()) + { + if (g_state.scroll) + { + *g_scrollVal += 20*g_state.scroll; + if (*g_scrollVal < 0) *g_scrollVal = 0; + if (*g_scrollVal > (sh - h)) *g_scrollVal = (sh - h); + } + } + } + g_state.insideCurrentScroll = false; +} + +bool imguiButton(const char* text, bool enabled, int width) +{ + g_state.widgetId++; + unsigned int id = (g_state.areaId<<16) | g_state.widgetId; + + int x = g_state.widgetX; + int y = g_state.widgetY - BUTTON_HEIGHT; + int w = width > 0 ? width : g_state.widgetW; + int h = BUTTON_HEIGHT; + if (g_state.nextItemSameLine) { + g_state.widgetX += width; + } + else { + g_state.widgetY -= BUTTON_HEIGHT; + } + + bool over = enabled && inRect(x, y, w, h); + bool res = buttonLogic(id, over); + + addGfxCmdRoundedRect((float)x, (float)y, (float)w, (float)h, (float)BUTTON_HEIGHT/2-1, imguiRGBA(128,128,128, isActive(id)?196:96)); + if (enabled) + addGfxCmdText(x+w/2, y+BUTTON_HEIGHT/2-TEXT_HEIGHT/2, IMGUI_ALIGN_CENTER, text, isHot(id) ? imguiRGBA(255,196,0,255) : imguiRGBA(255,255,255,200)); + else + addGfxCmdText(x+w/2, y+BUTTON_HEIGHT/2-TEXT_HEIGHT/2, IMGUI_ALIGN_CENTER, text, imguiRGBA(128,128,128,200)); + + return res; +} + +bool imguiItem(const char* text, bool enabled) +{ + g_state.widgetId++; + unsigned int id = (g_state.areaId<<16) | g_state.widgetId; + + int x = g_state.widgetX; + int y = g_state.widgetY - BUTTON_HEIGHT; + int w = g_state.widgetW; + int h = BUTTON_HEIGHT; + g_state.widgetY -= BUTTON_HEIGHT + DEFAULT_VERTICAL_SPACING; + + bool over = enabled && inRect(x, y, w, h); + bool res = buttonLogic(id, over); + + if (isHot(id)) + addGfxCmdRoundedRect((float)x, (float)y, (float)w, (float)h, 2.0f, imguiRGBA(255,196,0,isActive(id)?196:96)); + + if (enabled) + addGfxCmdText(x+BUTTON_HEIGHT/2, y+BUTTON_HEIGHT/2-TEXT_HEIGHT/2, IMGUI_ALIGN_LEFT, text, imguiRGBA(255,255,255,200)); + else + addGfxCmdText(x+BUTTON_HEIGHT/2, y+BUTTON_HEIGHT/2-TEXT_HEIGHT/2, IMGUI_ALIGN_LEFT, text, imguiRGBA(128,128,128,200)); + + return res; +} + +bool imguiCheck(const char* text, bool checked, bool enabled) +{ + g_state.widgetId++; + unsigned int id = (g_state.areaId<<16) | g_state.widgetId; + + int x = g_state.widgetX; + int y = g_state.widgetY - BUTTON_HEIGHT; + int w = g_state.widgetW; + int h = BUTTON_HEIGHT; + if (g_state.nextItemSameLine) { + g_state.widgetX += w; + } + else { + g_state.widgetY -= BUTTON_HEIGHT; + } + + bool over = enabled && inRect(x, y, w, h); + bool res = buttonLogic(id, over); + + const int cx = x+BUTTON_HEIGHT/2-CHECK_SIZE/2; + const int cy = y+BUTTON_HEIGHT/2-CHECK_SIZE/2; + addGfxCmdRoundedRect((float)cx-3, (float)cy-3, (float)CHECK_SIZE+6, (float)CHECK_SIZE+6, 4, imguiRGBA(128,128,128, isActive(id)?196:96)); + if (checked) + { + if (enabled) + addGfxCmdRoundedRect((float)cx, (float)cy, (float)CHECK_SIZE, (float)CHECK_SIZE, (float)CHECK_SIZE/2-1, imguiRGBA(255,255,255,isActive(id)?255:200)); + else + addGfxCmdRoundedRect((float)cx, (float)cy, (float)CHECK_SIZE, (float)CHECK_SIZE, (float)CHECK_SIZE/2-1, imguiRGBA(128,128,128,200)); + } + + if (enabled) + addGfxCmdText(x+BUTTON_HEIGHT, y+BUTTON_HEIGHT/2-TEXT_HEIGHT/2, IMGUI_ALIGN_LEFT, text, isHot(id) ? imguiRGBA(255,196,0,255) : imguiRGBA(255,255,255,200)); + else + addGfxCmdText(x+BUTTON_HEIGHT, y+BUTTON_HEIGHT/2-TEXT_HEIGHT/2, IMGUI_ALIGN_LEFT, text, imguiRGBA(128,128,128,200)); + + return res; +} + +bool imguiCollapse(const char* text, const char* subtext, bool checked, bool enabled) +{ + g_state.widgetId++; + unsigned int id = (g_state.areaId<<16) | g_state.widgetId; + + int x = g_state.widgetX; + int y = g_state.widgetY - BUTTON_HEIGHT; + int w = g_state.widgetW; + int h = BUTTON_HEIGHT; + g_state.widgetY -= BUTTON_HEIGHT; // + DEFAULT_SPACING; + + const int cx = x+BUTTON_HEIGHT/2-CHECK_SIZE/2; + const int cy = y+BUTTON_HEIGHT/2-CHECK_SIZE/2; + + bool over = enabled && inRect(x, y, w, h); + bool res = buttonLogic(id, over); + + if (checked) + addGfxCmdTriangle(cx, cy, CHECK_SIZE, CHECK_SIZE, 2, imguiRGBA(255,255,255,isActive(id)?255:200)); + else + addGfxCmdTriangle(cx, cy, CHECK_SIZE, CHECK_SIZE, 1, imguiRGBA(255,255,255,isActive(id)?255:200)); + + if (enabled) + addGfxCmdText(x+BUTTON_HEIGHT, y+BUTTON_HEIGHT/2-TEXT_HEIGHT/2, IMGUI_ALIGN_LEFT, text, isHot(id) ? imguiRGBA(255,196,0,255) : imguiRGBA(255,255,255,200)); + else + addGfxCmdText(x+BUTTON_HEIGHT, y+BUTTON_HEIGHT/2-TEXT_HEIGHT/2, IMGUI_ALIGN_LEFT, text, imguiRGBA(128,128,128,200)); + + if (subtext) + addGfxCmdText(x+w-BUTTON_HEIGHT/2, y+BUTTON_HEIGHT/2-TEXT_HEIGHT/2, IMGUI_ALIGN_RIGHT, subtext, imguiRGBA(255,255,255,128)); + + return res; +} + +void imguiLabel(const char* text) +{ + int x = g_state.widgetX; + int y = g_state.widgetY - BUTTON_HEIGHT; + g_state.widgetY -= BUTTON_HEIGHT; + addGfxCmdText(x, y+BUTTON_HEIGHT/2-TEXT_HEIGHT/2, IMGUI_ALIGN_LEFT, text, imguiRGBA(255,255,255,255)); +} + +void imguiValue(const char* text) +{ + const int x = g_state.widgetX; + const int y = g_state.widgetY - BUTTON_HEIGHT; + const int w = g_state.widgetW; + g_state.widgetY -= BUTTON_HEIGHT; + + addGfxCmdText(x+w-BUTTON_HEIGHT/2, y+BUTTON_HEIGHT/2-TEXT_HEIGHT/2, IMGUI_ALIGN_RIGHT, text, imguiRGBA(255,255,255,200)); +} + +bool imguiSlider(const char* text, float* val, float vmin, float vmax, float vinc, bool enabled) +{ + g_state.widgetId++; + unsigned int id = (g_state.areaId<<16) | g_state.widgetId; + + int x = g_state.widgetX; + int y = g_state.widgetY - BUTTON_HEIGHT; + int w = g_state.widgetW; + int h = SLIDER_HEIGHT; + g_state.widgetY -= SLIDER_HEIGHT + DEFAULT_VERTICAL_SPACING; + + addGfxCmdRoundedRect((float)x, (float)y, (float)w, (float)h, 4.0f, imguiRGBA(0,0,0,128)); + + const int range = w - SLIDER_MARKER_WIDTH; + + float u = (*val - vmin) / (vmax-vmin); + if (u < 0) u = 0; + if (u > 1) u = 1; + int m = (int)(u * range); + + bool over = enabled && inRect(x+m, y, SLIDER_MARKER_WIDTH, SLIDER_HEIGHT); + bool res = buttonLogic(id, over); + bool valChanged = false; + + if (isActive(id)) + { + if (g_state.wentActive) + { + g_state.dragX = g_state.mx; + g_state.dragOrig = u; + } + if (g_state.dragX != g_state.mx) + { + u = g_state.dragOrig + (float)(g_state.mx - g_state.dragX) / (float)range; + if (u < 0) u = 0; + if (u > 1) u = 1; + *val = vmin + u*(vmax-vmin); + *val = floorf(*val/vinc+0.5f)*vinc; // Snap to vinc + m = (int)(u * range); + valChanged = true; + } + } + + if (isActive(id)) + addGfxCmdRoundedRect((float)(x+m), (float)y, (float)SLIDER_MARKER_WIDTH, (float)SLIDER_HEIGHT, 4.0f, imguiRGBA(255,255,255,255)); + else + addGfxCmdRoundedRect((float)(x+m), (float)y, (float)SLIDER_MARKER_WIDTH, (float)SLIDER_HEIGHT, 4.0f, isHot(id) ? imguiRGBA(255,196,0,128) : imguiRGBA(255,255,255,64)); + + // TODO: fix this, take a look at 'nicenum'. + int digits = (int)(ceilf(log10f(vinc))); + char fmt[16]; + snprintf(fmt, 16, "%%.%df", digits >= 0 ? 0 : -digits); + char msg[128]; + snprintf(msg, 128, fmt, *val); + + if (enabled) + { + addGfxCmdText(x+SLIDER_HEIGHT/2, y+SLIDER_HEIGHT/2-TEXT_HEIGHT/2, IMGUI_ALIGN_LEFT, text, isHot(id) ? imguiRGBA(255,196,0,255) : imguiRGBA(255,255,255,200)); + addGfxCmdText(x+w-SLIDER_HEIGHT/2, y+SLIDER_HEIGHT/2-TEXT_HEIGHT/2, IMGUI_ALIGN_RIGHT, msg, isHot(id) ? imguiRGBA(255,196,0,255) : imguiRGBA(255,255,255,200)); + } + else + { + addGfxCmdText(x+SLIDER_HEIGHT/2, y+SLIDER_HEIGHT/2-TEXT_HEIGHT/2, IMGUI_ALIGN_LEFT, text, imguiRGBA(128,128,128,200)); + addGfxCmdText(x+w-SLIDER_HEIGHT/2, y+SLIDER_HEIGHT/2-TEXT_HEIGHT/2, IMGUI_ALIGN_RIGHT, msg, imguiRGBA(128,128,128,200)); + } + + return res || valChanged; +} + +void imguiStartLine() { + g_state.nextItemSameLine = true; + g_state.newLinePreviousX = g_state.widgetX; +} + +void imguiEndLine() { + g_state.nextItemSameLine = false; + g_state.widgetX = g_state.newLinePreviousX; + g_state.newLinePreviousX = 0; +} + +void imguiVerticalSpace(int spaceY) +{ + g_state.widgetY -= spaceY; +} + +void imguiHorizontalSpace(int spaceX) { + g_state.widgetX += spaceX; +} + +void imguiSeparatorLine() +{ + int x = g_state.widgetX; + int y = g_state.widgetY; + int w = g_state.widgetW; + int h = SEPARATOR_LINE_WIDTH; + g_state.widgetY -= SEPARATOR_LINE_WIDTH; + + addGfxCmdRect((float)x, (float)y, (float)w, (float)h, imguiRGBA(255,255,255,70)); +} + +void imguiDrawText(int x, int y, int align, const char* text, unsigned int color) +{ + addGfxCmdText(x, y, align, text, color); +} + +void imguiDrawLine(float x0, float y0, float x1, float y1, float r, unsigned int color) +{ + addGfxCmdLine(x0, y0, x1, y1, r, color); +} + +void imguiDrawRect(float x, float y, float w, float h, unsigned int color) +{ + addGfxCmdRect(x, y, w, h, color); +} + +void imguiDrawRoundedRect(float x, float y, float w, float h, float r, unsigned int color) +{ + addGfxCmdRoundedRect(x, y, w, h, r, color); +} diff --git a/testbed/imgui/imgui.h b/testbed/imgui/imgui.h new file mode 100644 index 00000000..64f28109 --- /dev/null +++ b/testbed/imgui/imgui.h @@ -0,0 +1,128 @@ + +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +// Source altered and distributed from https://github.com/AdrienHerubel/imgui + +#ifndef IMGUI_H +#define IMGUI_H + +static const int BUTTON_HEIGHT = 40; +static const int SLIDER_HEIGHT = 30; +static const int SLIDER_MARKER_WIDTH = 10; +static const int CHECK_SIZE = 18; +static const int DEFAULT_VERTICAL_SPACING = 14; +static const int DEFAULT_HORIZONTAL_SPACING = 14; +static const int TEXT_HEIGHT = 16; +static const int FONT_HEIGHT = 30; +static const int SCROll_AREA_TOP_PADDING = 5; +static const int SCROLL_AREA_PADDING = 5; +static const int INDENT_SIZE = 16; +static const int AREA_HEADER = 20; +static const int SEPARATOR_LINE_WIDTH = 2; + +enum imguiMouseButton +{ + IMGUI_MBUT_LEFT = 0x01, + IMGUI_MBUT_RIGHT = 0x02, +}; + +enum imguiTextAlign +{ + IMGUI_ALIGN_LEFT, + IMGUI_ALIGN_CENTER, + IMGUI_ALIGN_RIGHT, +}; + +inline unsigned int imguiRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a=255) +{ + return (r) | (g << 8) | (b << 16) | (a << 24); +} + +void imguiBeginFrame(int mx, int my, unsigned char mbut, int scroll); +void imguiEndFrame(); + +bool imguiBeginScrollArea(const char* name, int x, int y, int w, int h, int* scroll); +void imguiEndScrollArea(); + +void imguiStartLine(); +void imguiEndLine(); +void imguiIndent(); +void imguiUnindent(); +void imguiVerticalSpace(int spaceY); +void imguiHorizontalSpace(int spaceX); +void imguiSeparatorLine(); + +bool imguiButton(const char* text, bool enabled = true, int width = -1); +bool imguiItem(const char* text, bool enabled = true); +bool imguiCheck(const char* text, bool checked, bool enabled = true); +bool imguiCollapse(const char* text, const char* subtext, bool checked, bool enabled = true); +void imguiLabel(const char* text); +void imguiValue(const char* text); +bool imguiSlider(const char* text, float* val, float vmin, float vmax, float vinc, bool enabled = true); + +void imguiDrawText(int x, int y, int align, const char* text, unsigned int color); +void imguiDrawLine(float x0, float y0, float x1, float y1, float r, unsigned int color); +void imguiDrawRoundedRect(float x, float y, float w, float h, float r, unsigned int color); +void imguiDrawRect(float x, float y, float w, float h, unsigned int color); + +// Pull render interface. +enum imguiGfxCmdType +{ + IMGUI_GFXCMD_RECT, + IMGUI_GFXCMD_TRIANGLE, + IMGUI_GFXCMD_LINE, + IMGUI_GFXCMD_TEXT, + IMGUI_GFXCMD_SCISSOR, +}; + +struct imguiGfxRect +{ + short x,y,w,h,r; +}; + +struct imguiGfxText +{ + short x,y,align; + const char* text; +}; + +struct imguiGfxLine +{ + short x0,y0,x1,y1,r; +}; + +struct imguiGfxCmd +{ + char type; + char flags; + char pad[2]; + unsigned int col; + union + { + imguiGfxLine line; + imguiGfxRect rect; + imguiGfxText text; + }; +}; + +const imguiGfxCmd* imguiGetRenderQueue(); +int imguiGetRenderQueueSize(); + + +#endif // IMGUI_H diff --git a/testbed/imgui/imguiRenderGL2.cpp b/testbed/imgui/imguiRenderGL2.cpp new file mode 100644 index 00000000..21308219 --- /dev/null +++ b/testbed/imgui/imguiRenderGL2.cpp @@ -0,0 +1,489 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +// Source altered and distributed from https://github.com/AdrienHerubel/imguir + +#define _USE_MATH_DEFINES +#include +#include + +#include "imgui.h" + +#include +/*#ifdef __APPLE__ +#include +#else +#include +#endif +*/ + +// Some math headers don't have PI defined. +static const float PI = 3.14159265f; + +void imguifree(void* ptr, void* userptr); +void* imguimalloc(size_t size, void* userptr); + +#define STBTT_malloc(x,y) imguimalloc(x,y) +#define STBTT_free(x,y) imguifree(x,y) +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" + +void imguifree(void* ptr, void* /*userptr*/) +{ + free(ptr); +} + +void* imguimalloc(size_t size, void* /*userptr*/) +{ + return malloc(size); +} + +static const unsigned TEMP_COORD_COUNT = 100; +static float g_tempCoords[TEMP_COORD_COUNT*2]; +static float g_tempNormals[TEMP_COORD_COUNT*2]; + +static const int CIRCLE_VERTS = 8*4; +static float g_circleVerts[CIRCLE_VERTS*2]; + +static stbtt_bakedchar g_cdata[96]; // ASCII 32..126 is 95 glyphs +static GLuint g_ftex = 0; + +inline unsigned int RGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + return (r) | (g << 8) | (b << 16) | (a << 24); +} + +static void drawPolygon(const float* coords, unsigned numCoords, float r, unsigned int col) +{ + if (numCoords > TEMP_COORD_COUNT) numCoords = TEMP_COORD_COUNT; + + for (unsigned i = 0, j = numCoords-1; i < numCoords; j=i++) + { + const float* v0 = &coords[j*2]; + const float* v1 = &coords[i*2]; + float dx = v1[0] - v0[0]; + float dy = v1[1] - v0[1]; + float d = sqrtf(dx*dx+dy*dy); + if (d > 0) + { + d = 1.0f/d; + dx *= d; + dy *= d; + } + g_tempNormals[j*2+0] = dy; + g_tempNormals[j*2+1] = -dx; + } + + for (unsigned i = 0, j = numCoords-1; i < numCoords; j=i++) + { + float dlx0 = g_tempNormals[j*2+0]; + float dly0 = g_tempNormals[j*2+1]; + float dlx1 = g_tempNormals[i*2+0]; + float dly1 = g_tempNormals[i*2+1]; + float dmx = (dlx0 + dlx1) * 0.5f; + float dmy = (dly0 + dly1) * 0.5f; + float dmr2 = dmx*dmx + dmy*dmy; + if (dmr2 > 0.000001f) + { + float scale = 1.0f / dmr2; + if (scale > 10.0f) scale = 10.0f; + dmx *= scale; + dmy *= scale; + } + g_tempCoords[i*2+0] = coords[i*2+0]+dmx*r; + g_tempCoords[i*2+1] = coords[i*2+1]+dmy*r; + } + + unsigned int colTrans = RGBA(col&0xff, (col>>8)&0xff, (col>>16)&0xff, 0); + + glBegin(GL_TRIANGLES); + + glColor4ubv((GLubyte*)&col); + + for (unsigned i = 0, j = numCoords-1; i < numCoords; j=i++) + { + glVertex2fv(&coords[i*2]); + glVertex2fv(&coords[j*2]); + glColor4ubv((GLubyte*)&colTrans); + glVertex2fv(&g_tempCoords[j*2]); + + glVertex2fv(&g_tempCoords[j*2]); + glVertex2fv(&g_tempCoords[i*2]); + + glColor4ubv((GLubyte*)&col); + glVertex2fv(&coords[i*2]); + } + + glColor4ubv((GLubyte*)&col); + for (unsigned i = 2; i < numCoords; ++i) + { + glVertex2fv(&coords[0]); + glVertex2fv(&coords[(i-1)*2]); + glVertex2fv(&coords[i*2]); + } + + glEnd(); +} + +static void drawRect(float x, float y, float w, float h, float fth, unsigned int col) +{ + float verts[4*2] = + { + x+0.5f, y+0.5f, + x+w-0.5f, y+0.5f, + x+w-0.5f, y+h-0.5f, + x+0.5f, y+h-0.5f, + }; + drawPolygon(verts, 4, fth, col); +} + +/* +static void drawEllipse(float x, float y, float w, float h, float fth, unsigned int col) +{ + float verts[CIRCLE_VERTS*2]; + const float* cverts = g_circleVerts; + float* v = verts; + + for (int i = 0; i < CIRCLE_VERTS; ++i) + { + *v++ = x + cverts[i*2]*w; + *v++ = y + cverts[i*2+1]*h; + } + + drawPolygon(verts, CIRCLE_VERTS, fth, col); +} +*/ + +static void drawRoundedRect(float x, float y, float w, float h, float r, float fth, unsigned int col) +{ + const unsigned n = CIRCLE_VERTS/4; + float verts[(n+1)*4*2]; + const float* cverts = g_circleVerts; + float* v = verts; + + for (unsigned i = 0; i <= n; ++i) + { + *v++ = x+w-r + cverts[i*2]*r; + *v++ = y+h-r + cverts[i*2+1]*r; + } + + for (unsigned i = n; i <= n*2; ++i) + { + *v++ = x+r + cverts[i*2]*r; + *v++ = y+h-r + cverts[i*2+1]*r; + } + + for (unsigned i = n*2; i <= n*3; ++i) + { + *v++ = x+r + cverts[i*2]*r; + *v++ = y+r + cverts[i*2+1]*r; + } + + for (unsigned i = n*3; i < n*4; ++i) + { + *v++ = x+w-r + cverts[i*2]*r; + *v++ = y+r + cverts[i*2+1]*r; + } + *v++ = x+w-r + cverts[0]*r; + *v++ = y+r + cverts[1]*r; + + drawPolygon(verts, (n+1)*4, fth, col); +} + + +static void drawLine(float x0, float y0, float x1, float y1, float r, float fth, unsigned int col) +{ + float dx = x1-x0; + float dy = y1-y0; + float d = sqrtf(dx*dx+dy*dy); + if (d > 0.0001f) + { + d = 1.0f/d; + dx *= d; + dy *= d; + } + float nx = dy; + float ny = -dx; + float verts[4*2]; + r -= fth; + r *= 0.5f; + if (r < 0.01f) r = 0.01f; + dx *= r; + dy *= r; + nx *= r; + ny *= r; + + verts[0] = x0-dx-nx; + verts[1] = y0-dy-ny; + + verts[2] = x0-dx+nx; + verts[3] = y0-dy+ny; + + verts[4] = x1+dx+nx; + verts[5] = y1+dy+ny; + + verts[6] = x1+dx-nx; + verts[7] = y1+dy-ny; + + drawPolygon(verts, 4, fth, col); +} + + +bool imguiRenderGLInit(const char* fontpath) +{ + for (int i = 0; i < CIRCLE_VERTS; ++i) + { + float a = (float)i/(float)CIRCLE_VERTS * PI*2; + g_circleVerts[i*2+0] = cosf(a); + g_circleVerts[i*2+1] = sinf(a); + } + + // Load font. + FILE* fp = fopen(fontpath, "rb"); + if (!fp) return false; + fseek(fp, 0, SEEK_END); + int size = ftell(fp); + fseek(fp, 0, SEEK_SET); + + unsigned char* ttfBuffer = (unsigned char*)malloc(size); + if (!ttfBuffer) + { + fclose(fp); + return false; + } + + fread(ttfBuffer, 1, size, fp); + fclose(fp); + fp = 0; + + unsigned char* bmap = (unsigned char*)malloc(512*512); + if (!bmap) + { + free(ttfBuffer); + return false; + } + + stbtt_BakeFontBitmap(ttfBuffer,0, 15.0f, bmap,512,512, 32,96, g_cdata); + + // can free ttf_buffer at this point + glGenTextures(1, &g_ftex); + glBindTexture(GL_TEXTURE_2D, g_ftex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 512,512, 0, GL_ALPHA, GL_UNSIGNED_BYTE, bmap); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + free(ttfBuffer); + free(bmap); + + return true; +} + +void imguiRenderGLDestroy() +{ + if (g_ftex) + { + glDeleteTextures(1, &g_ftex); + g_ftex = 0; + } +} + +static void getBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, int char_index, + float *xpos, float *ypos, stbtt_aligned_quad *q) +{ + stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor(*xpos + b->xoff); + int round_y = STBTT_ifloor(*ypos - b->yoff); + + q->x0 = (float)round_x; + q->y0 = (float)round_y; + q->x1 = (float)round_x + b->x1 - b->x0; + q->y1 = (float)round_y - b->y1 + b->y0; + + q->s0 = b->x0 / (float)pw; + q->t0 = b->y0 / (float)pw; + q->s1 = b->x1 / (float)ph; + q->t1 = b->y1 / (float)ph; + + *xpos += b->xadvance; +} + +static const float g_tabStops[4] = {150, 210, 270, 330}; + +static float getTextLength(stbtt_bakedchar *chardata, const char* text) +{ + float xpos = 0; + float len = 0; + while (*text) + { + int c = (unsigned char)*text; + if (c == '\t') + { + for (int i = 0; i < 4; ++i) + { + if (xpos < g_tabStops[i]) + { + xpos = g_tabStops[i]; + break; + } + } + } + else if (c >= 32 && c < 128) + { + stbtt_bakedchar *b = chardata + c-32; + int round_x = STBTT_ifloor((xpos + b->xoff) + 0.5); + len = round_x + b->x1 - b->x0 + 0.5f; + xpos += b->xadvance; + } + ++text; + } + return len; +} + +static void drawText(float x, float y, const char *text, int align, unsigned int col) +{ + if (!g_ftex) return; + if (!text) return; + + if (align == IMGUI_ALIGN_CENTER) + x -= getTextLength(g_cdata, text)/2; + else if (align == IMGUI_ALIGN_RIGHT) + x -= getTextLength(g_cdata, text); + + glColor4ub(col&0xff, (col>>8)&0xff, (col>>16)&0xff, (col>>24)&0xff); + + glEnable(GL_TEXTURE_2D); + + // assume orthographic projection with units = screen pixels, origin at top left + glBindTexture(GL_TEXTURE_2D, g_ftex); + + glBegin(GL_TRIANGLES); + + const float ox = x; + + while (*text) + { + int c = (unsigned char)*text; + if (c == '\t') + { + for (int i = 0; i < 4; ++i) + { + if (x < g_tabStops[i]+ox) + { + x = g_tabStops[i]+ox; + break; + } + } + } + else if (c >= 32 && c < 128) + { + stbtt_aligned_quad q; + getBakedQuad(g_cdata, 512,512, c-32, &x,&y,&q); + + glTexCoord2f(q.s0, q.t0); + glVertex2f(q.x0, q.y0); + glTexCoord2f(q.s1, q.t1); + glVertex2f(q.x1, q.y1); + glTexCoord2f(q.s1, q.t0); + glVertex2f(q.x1, q.y0); + + glTexCoord2f(q.s0, q.t0); + glVertex2f(q.x0, q.y0); + glTexCoord2f(q.s0, q.t1); + glVertex2f(q.x0, q.y1); + glTexCoord2f(q.s1, q.t1); + glVertex2f(q.x1, q.y1); + } + ++text; + } + + glEnd(); + glDisable(GL_TEXTURE_2D); +} + +void imguiRenderGLDraw(int width, int height) +{ + const imguiGfxCmd* q = imguiGetRenderQueue(); + int nq = imguiGetRenderQueueSize(); + + const float s = 1.0f/8.0f; + + glDisable(GL_SCISSOR_TEST); + for (int i = 0; i < nq; ++i) + { + const imguiGfxCmd& cmd = q[i]; + if (cmd.type == IMGUI_GFXCMD_RECT) + { + if (cmd.rect.r == 0) + { + drawRect((float)cmd.rect.x*s+0.5f, (float)cmd.rect.y*s+0.5f, + (float)cmd.rect.w*s-1, (float)cmd.rect.h*s-1, + 1.0f, cmd.col); + } + else + { + drawRoundedRect((float)cmd.rect.x*s+0.5f, (float)cmd.rect.y*s+0.5f, + (float)cmd.rect.w*s-1, (float)cmd.rect.h*s-1, + (float)cmd.rect.r*s, 1.0f, cmd.col); + } + } + else if (cmd.type == IMGUI_GFXCMD_LINE) + { + drawLine(cmd.line.x0*s, cmd.line.y0*s, cmd.line.x1*s, cmd.line.y1*s, cmd.line.r*s, 1.0f, cmd.col); + } + else if (cmd.type == IMGUI_GFXCMD_TRIANGLE) + { + if (cmd.flags == 1) + { + const float verts[3*2] = + { + (float)cmd.rect.x*s+0.5f, (float)cmd.rect.y*s+0.5f, + (float)cmd.rect.x*s+0.5f+(float)cmd.rect.w*s-1, (float)cmd.rect.y*s+0.5f+(float)cmd.rect.h*s/2-0.5f, + (float)cmd.rect.x*s+0.5f, (float)cmd.rect.y*s+0.5f+(float)cmd.rect.h*s-1, + }; + drawPolygon(verts, 3, 1.0f, cmd.col); + } + if (cmd.flags == 2) + { + const float verts[3*2] = + { + (float)cmd.rect.x*s+0.5f, (float)cmd.rect.y*s+0.5f+(float)cmd.rect.h*s-1, + (float)cmd.rect.x*s+0.5f+(float)cmd.rect.w*s/2-0.5f, (float)cmd.rect.y*s+0.5f, + (float)cmd.rect.x*s+0.5f+(float)cmd.rect.w*s-1, (float)cmd.rect.y*s+0.5f+(float)cmd.rect.h*s-1, + }; + drawPolygon(verts, 3, 1.0f, cmd.col); + } + } + else if (cmd.type == IMGUI_GFXCMD_TEXT) + { + drawText(cmd.text.x, cmd.text.y, cmd.text.text, cmd.text.align, cmd.col); + } + else if (cmd.type == IMGUI_GFXCMD_SCISSOR) + { + if (cmd.flags) + { + glEnable(GL_SCISSOR_TEST); + glScissor(cmd.rect.x, cmd.rect.y, cmd.rect.w, cmd.rect.h); + } + else + { + glDisable(GL_SCISSOR_TEST); + } + } + } + glDisable(GL_SCISSOR_TEST); +} diff --git a/testbed/imgui/imguiRenderGL2.h b/testbed/imgui/imguiRenderGL2.h new file mode 100644 index 00000000..c36b8045 --- /dev/null +++ b/testbed/imgui/imguiRenderGL2.h @@ -0,0 +1,29 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +// Source altered and distributed from https://github.com/AdrienHerubel/imguir + + +#ifndef IMGUI_RENDER_GL_H +#define IMGUI_RENDER_GL_H + +bool imguiRenderGLInit(const char* fontpath); +void imguiRenderGLDestroy(); +void imguiRenderGLDraw(int width, int height); + +#endif // IMGUI_RENDER_GL_H \ No newline at end of file diff --git a/testbed/imgui/imguiRenderGL3.cpp b/testbed/imgui/imguiRenderGL3.cpp new file mode 100644 index 00000000..2a84a6fa --- /dev/null +++ b/testbed/imgui/imguiRenderGL3.cpp @@ -0,0 +1,695 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +// Source altered and distributed from https://github.com/AdrienHerubel/imgui + + +#define _USE_MATH_DEFINES +#include +#include + +#include +/*#ifdef __APPLE__ +#include +#else +#include +#endif +*/ + +#include "imgui.h" + +// Some math headers don't have PI defined. +static const float PI = 3.14159265f; + +void imguifree(void* ptr, void* userptr); +void* imguimalloc(size_t size, void* userptr); + +#define STBTT_malloc(x,y) imguimalloc(x,y) +#define STBTT_free(x,y) imguifree(x,y) +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" + +void imguifree(void* ptr, void* /*userptr*/) +{ + free(ptr); +} + +void* imguimalloc(size_t size, void* /*userptr*/) +{ + return malloc(size); +} + +static const unsigned TEMP_COORD_COUNT = 100; +static float g_tempCoords[TEMP_COORD_COUNT*2]; +static float g_tempNormals[TEMP_COORD_COUNT*2]; +static float g_tempVertices[TEMP_COORD_COUNT * 12 + (TEMP_COORD_COUNT - 2) * 6]; +static float g_tempTextureCoords[TEMP_COORD_COUNT * 12 + (TEMP_COORD_COUNT - 2) * 6]; +static float g_tempColors[TEMP_COORD_COUNT * 24 + (TEMP_COORD_COUNT - 2) * 12]; + +static const int CIRCLE_VERTS = 8*4; +static float g_circleVerts[CIRCLE_VERTS*2]; + +static stbtt_bakedchar g_cdata[96]; // ASCII 32..126 is 95 glyphs +static GLuint g_ftex = 0; +static GLuint g_whitetex = 0; +static GLuint g_vao = 0; +static GLuint g_vbos[3] = {0, 0, 0}; +static GLuint g_program = 0; +static GLuint g_programViewportLocation = 0; +static GLuint g_programTextureLocation = 0; + +inline unsigned int RGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + return (r) | (g << 8) | (b << 16) | (a << 24); +} + +static void drawPolygon(const float* coords, unsigned numCoords, float r, unsigned int col) +{ + if (numCoords > TEMP_COORD_COUNT) numCoords = TEMP_COORD_COUNT; + + for (unsigned i = 0, j = numCoords-1; i < numCoords; j=i++) + { + const float* v0 = &coords[j*2]; + const float* v1 = &coords[i*2]; + float dx = v1[0] - v0[0]; + float dy = v1[1] - v0[1]; + float d = sqrtf(dx*dx+dy*dy); + if (d > 0) + { + d = 1.0f/d; + dx *= d; + dy *= d; + } + g_tempNormals[j*2+0] = dy; + g_tempNormals[j*2+1] = -dx; + } + + float colf[4] = { (float) (col&0xff) / 255.f, (float) ((col>>8)&0xff) / 255.f, (float) ((col>>16)&0xff) / 255.f, (float) ((col>>24)&0xff) / 255.f}; + float colTransf[4] = { (float) (col&0xff) / 255.f, (float) ((col>>8)&0xff) / 255.f, (float) ((col>>16)&0xff) / 255.f, 0}; + + for (unsigned i = 0, j = numCoords-1; i < numCoords; j=i++) + { + float dlx0 = g_tempNormals[j*2+0]; + float dly0 = g_tempNormals[j*2+1]; + float dlx1 = g_tempNormals[i*2+0]; + float dly1 = g_tempNormals[i*2+1]; + float dmx = (dlx0 + dlx1) * 0.5f; + float dmy = (dly0 + dly1) * 0.5f; + float dmr2 = dmx*dmx + dmy*dmy; + if (dmr2 > 0.000001f) + { + float scale = 1.0f / dmr2; + if (scale > 10.0f) scale = 10.0f; + dmx *= scale; + dmy *= scale; + } + g_tempCoords[i*2+0] = coords[i*2+0]+dmx*r; + g_tempCoords[i*2+1] = coords[i*2+1]+dmy*r; + } + + int vSize = numCoords * 12 + (numCoords - 2) * 6; + int uvSize = numCoords * 2 * 6 + (numCoords - 2) * 2 * 3; + int cSize = numCoords * 4 * 6 + (numCoords - 2) * 4 * 3; + float * v = g_tempVertices; + float * uv = g_tempTextureCoords; + memset(uv, 0, uvSize * sizeof(float)); + float * c = g_tempColors; + memset(c, 1, cSize * sizeof(float)); + + float * ptrV = v; + float * ptrC = c; + for (unsigned i = 0, j = numCoords-1; i < numCoords; j=i++) + { + *ptrV = coords[i*2]; + *(ptrV+1) = coords[i*2 + 1]; + ptrV += 2; + *ptrV = coords[j*2]; + *(ptrV+1) = coords[j*2 + 1]; + ptrV += 2; + *ptrV = g_tempCoords[j*2]; + *(ptrV+1) = g_tempCoords[j*2 + 1]; + ptrV += 2; + *ptrV = g_tempCoords[j*2]; + *(ptrV+1) = g_tempCoords[j*2 + 1]; + ptrV += 2; + *ptrV = g_tempCoords[i*2]; + *(ptrV+1) = g_tempCoords[i*2 + 1]; + ptrV += 2; + *ptrV = coords[i*2]; + *(ptrV+1) = coords[i*2 + 1]; + ptrV += 2; + + *ptrC = colf[0]; + *(ptrC+1) = colf[1]; + *(ptrC+2) = colf[2]; + *(ptrC+3) = colf[3]; + ptrC += 4; + *ptrC = colf[0]; + *(ptrC+1) = colf[1]; + *(ptrC+2) = colf[2]; + *(ptrC+3) = colf[3]; + ptrC += 4; + *ptrC = colTransf[0]; + *(ptrC+1) = colTransf[1]; + *(ptrC+2) = colTransf[2]; + *(ptrC+3) = colTransf[3]; + ptrC += 4; + *ptrC = colTransf[0]; + *(ptrC+1) = colTransf[1]; + *(ptrC+2) = colTransf[2]; + *(ptrC+3) = colTransf[3]; + ptrC += 4; + *ptrC = colTransf[0]; + *(ptrC+1) = colTransf[1]; + *(ptrC+2) = colTransf[2]; + *(ptrC+3) = colTransf[3]; + ptrC += 4; + *ptrC = colf[0]; + *(ptrC+1) = colf[1]; + *(ptrC+2) = colf[2]; + *(ptrC+3) = colf[3]; + ptrC += 4; + } + + for (unsigned i = 2; i < numCoords; ++i) + { + *ptrV = coords[0]; + *(ptrV+1) = coords[1]; + ptrV += 2; + *ptrV = coords[(i-1)*2]; + *(ptrV+1) = coords[(i-1)*2+1]; + ptrV += 2; + *ptrV = coords[i*2]; + *(ptrV+1) = coords[i*2 + 1]; + ptrV += 2; + + *ptrC = colf[0]; + *(ptrC+1) = colf[1]; + *(ptrC+2) = colf[2]; + *(ptrC+3) = colf[3]; + ptrC += 4; + *ptrC = colf[0]; + *(ptrC+1) = colf[1]; + *(ptrC+2) = colf[2]; + *(ptrC+3) = colf[3]; + ptrC += 4; + *ptrC = colf[0]; + *(ptrC+1) = colf[1]; + *(ptrC+2) = colf[2]; + *(ptrC+3) = colf[3]; + ptrC += 4; + } + glBindTexture(GL_TEXTURE_2D, g_whitetex); + + glBindVertexArray(g_vao); + glBindBuffer(GL_ARRAY_BUFFER, g_vbos[0]); + glBufferData(GL_ARRAY_BUFFER, vSize*sizeof(float), v, GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, g_vbos[1]); + glBufferData(GL_ARRAY_BUFFER, uvSize*sizeof(float), uv, GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, g_vbos[2]); + glBufferData(GL_ARRAY_BUFFER, cSize*sizeof(float), c, GL_STATIC_DRAW); + glDrawArrays(GL_TRIANGLES, 0, (numCoords * 2 + numCoords - 2)*3); + +} + +static void drawRect(float x, float y, float w, float h, float fth, unsigned int col) +{ + float verts[4*2] = + { + x+0.5f, y+0.5f, + x+w-0.5f, y+0.5f, + x+w-0.5f, y+h-0.5f, + x+0.5f, y+h-0.5f, + }; + drawPolygon(verts, 4, fth, col); +} + +/* +static void drawEllipse(float x, float y, float w, float h, float fth, unsigned int col) +{ + float verts[CIRCLE_VERTS*2]; + const float* cverts = g_circleVerts; + float* v = verts; + + for (int i = 0; i < CIRCLE_VERTS; ++i) + { + *v++ = x + cverts[i*2]*w; + *v++ = y + cverts[i*2+1]*h; + } + + drawPolygon(verts, CIRCLE_VERTS, fth, col); +} +*/ + +static void drawRoundedRect(float x, float y, float w, float h, float r, float fth, unsigned int col) +{ + const unsigned n = CIRCLE_VERTS/4; + float verts[(n+1)*4*2]; + const float* cverts = g_circleVerts; + float* v = verts; + + for (unsigned i = 0; i <= n; ++i) + { + *v++ = x+w-r + cverts[i*2]*r; + *v++ = y+h-r + cverts[i*2+1]*r; + } + + for (unsigned i = n; i <= n*2; ++i) + { + *v++ = x+r + cverts[i*2]*r; + *v++ = y+h-r + cverts[i*2+1]*r; + } + + for (unsigned i = n*2; i <= n*3; ++i) + { + *v++ = x+r + cverts[i*2]*r; + *v++ = y+r + cverts[i*2+1]*r; + } + + for (unsigned i = n*3; i < n*4; ++i) + { + *v++ = x+w-r + cverts[i*2]*r; + *v++ = y+r + cverts[i*2+1]*r; + } + *v++ = x+w-r + cverts[0]*r; + *v++ = y+r + cverts[1]*r; + + drawPolygon(verts, (n+1)*4, fth, col); +} + + +static void drawLine(float x0, float y0, float x1, float y1, float r, float fth, unsigned int col) +{ + float dx = x1-x0; + float dy = y1-y0; + float d = sqrtf(dx*dx+dy*dy); + if (d > 0.0001f) + { + d = 1.0f/d; + dx *= d; + dy *= d; + } + float nx = dy; + float ny = -dx; + float verts[4*2]; + r -= fth; + r *= 0.5f; + if (r < 0.01f) r = 0.01f; + dx *= r; + dy *= r; + nx *= r; + ny *= r; + + verts[0] = x0-dx-nx; + verts[1] = y0-dy-ny; + + verts[2] = x0-dx+nx; + verts[3] = y0-dy+ny; + + verts[4] = x1+dx+nx; + verts[5] = y1+dy+ny; + + verts[6] = x1+dx-nx; + verts[7] = y1+dy-ny; + + drawPolygon(verts, 4, fth, col); +} + + +bool imguiRenderGLInit(const char* fontpath) +{ + for (int i = 0; i < CIRCLE_VERTS; ++i) + { + float a = (float)i/(float)CIRCLE_VERTS * PI*2; + g_circleVerts[i*2+0] = cosf(a); + g_circleVerts[i*2+1] = sinf(a); + } + + // Load font. + FILE* fp = fopen(fontpath, "rb"); + if (!fp) return false; + fseek(fp, 0, SEEK_END); + int size = ftell(fp); + fseek(fp, 0, SEEK_SET); + + unsigned char* ttfBuffer = (unsigned char*)malloc(size); + if (!ttfBuffer) + { + fclose(fp); + return false; + } + + fread(ttfBuffer, 1, size, fp); + fclose(fp); + fp = 0; + + unsigned char* bmap = (unsigned char*)malloc(512*512); + if (!bmap) + { + free(ttfBuffer); + return false; + } + + stbtt_BakeFontBitmap(ttfBuffer,0, FONT_HEIGHT, bmap,512,512, 32,96, g_cdata); + + // can free ttf_buffer at this point + glGenTextures(1, &g_ftex); + glBindTexture(GL_TEXTURE_2D, g_ftex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, 512,512, 0, GL_RED, GL_UNSIGNED_BYTE, bmap); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // can free ttf_buffer at this point + unsigned char white_alpha = 255; + glGenTextures(1, &g_whitetex); + glBindTexture(GL_TEXTURE_2D, g_whitetex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, 1, 1, 0, GL_RED, GL_UNSIGNED_BYTE, &white_alpha); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + glGenVertexArrays(1, &g_vao); + glGenBuffers(3, g_vbos); + + glBindVertexArray(g_vao); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glEnableVertexAttribArray(2); + + glBindBuffer(GL_ARRAY_BUFFER, g_vbos[0]); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(GL_FLOAT)*2, (void*)0); + glBufferData(GL_ARRAY_BUFFER, 0, 0, GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, g_vbos[1]); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(GL_FLOAT)*2, (void*)0); + glBufferData(GL_ARRAY_BUFFER, 0, 0, GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, g_vbos[2]); + glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(GL_FLOAT)*4, (void*)0); + glBufferData(GL_ARRAY_BUFFER, 0, 0, GL_STATIC_DRAW); + g_program = glCreateProgram(); + + const char * vs = + "#version 150\n" + "uniform vec2 Viewport;\n" + "in vec2 VertexPosition;\n" + "in vec2 VertexTexCoord;\n" + "in vec4 VertexColor;\n" + "out vec2 texCoord;\n" + "out vec4 vertexColor;\n" + "void main(void)\n" + "{\n" + " vertexColor = VertexColor;\n" + " texCoord = VertexTexCoord;\n" + " gl_Position = vec4(VertexPosition * 2.0 / Viewport - 1.0, 0.f, 1.0);\n" + "}\n"; + GLuint vso = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vso, 1, (const char **) &vs, NULL); + glCompileShader(vso); + glAttachShader(g_program, vso); + + const char * fs = + "#version 150\n" + "in vec2 texCoord;\n" + "in vec4 vertexColor;\n" + "uniform sampler2D Texture;\n" + "out vec4 Color;\n" + "void main(void)\n" + "{\n" + " float alpha = texture(Texture, texCoord).r;\n" + " Color = vec4(vertexColor.rgb, vertexColor.a * alpha);\n" + "}\n"; + GLuint fso = glCreateShader(GL_FRAGMENT_SHADER); + + glShaderSource(fso, 1, (const char **) &fs, NULL); + glCompileShader(fso); + glAttachShader(g_program, fso); + + glBindAttribLocation(g_program, 0, "VertexPosition"); + glBindAttribLocation(g_program, 1, "VertexTexCoord"); + glBindAttribLocation(g_program, 2, "VertexColor"); + glBindFragDataLocation(g_program, 0, "Color"); + glLinkProgram(g_program); + glDeleteShader(vso); + glDeleteShader(fso); + + glUseProgram(g_program); + g_programViewportLocation = glGetUniformLocation(g_program, "Viewport"); + g_programTextureLocation = glGetUniformLocation(g_program, "Texture"); + + glUseProgram(0); + + + free(ttfBuffer); + free(bmap); + + return true; +} + +void imguiRenderGLDestroy() +{ + if (g_ftex) + { + glDeleteTextures(1, &g_ftex); + g_ftex = 0; + } + + if (g_vao) + { + glDeleteVertexArrays(1, &g_vao); + glDeleteBuffers(3, g_vbos); + g_vao = 0; + } + + if (g_program) + { + glDeleteProgram(g_program); + g_program = 0; + } + +} + +static void getBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, int char_index, + float *xpos, float *ypos, stbtt_aligned_quad *q) +{ + stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor(*xpos + b->xoff); + int round_y = STBTT_ifloor(*ypos - b->yoff); + + q->x0 = (float)round_x; + q->y0 = (float)round_y; + q->x1 = (float)round_x + b->x1 - b->x0; + q->y1 = (float)round_y - b->y1 + b->y0; + + q->s0 = b->x0 / (float)pw; + q->t0 = b->y0 / (float)pw; + q->s1 = b->x1 / (float)ph; + q->t1 = b->y1 / (float)ph; + + *xpos += b->xadvance; +} + +static const float g_tabStops[4] = {150, 210, 270, 330}; + +static float getTextLength(stbtt_bakedchar *chardata, const char* text) +{ + float xpos = 0; + float len = 0; + while (*text) + { + int c = (unsigned char)*text; + if (c == '\t') + { + for (int i = 0; i < 4; ++i) + { + if (xpos < g_tabStops[i]) + { + xpos = g_tabStops[i]; + break; + } + } + } + else if (c >= 32 && c < 128) + { + stbtt_bakedchar *b = chardata + c-32; + int round_x = STBTT_ifloor((xpos + b->xoff) + 0.5); + len = round_x + b->x1 - b->x0 + 0.5f; + xpos += b->xadvance; + } + ++text; + } + return len; +} + +static void drawText(float x, float y, const char *text, int align, unsigned int col) +{ + if (!g_ftex) return; + if (!text) return; + + if (align == IMGUI_ALIGN_CENTER) + x -= getTextLength(g_cdata, text)/2; + else if (align == IMGUI_ALIGN_RIGHT) + x -= getTextLength(g_cdata, text); + + float r = (float) (col&0xff) / 255.f; + float g = (float) ((col>>8)&0xff) / 255.f; + float b = (float) ((col>>16)&0xff) / 255.f; + float a = (float) ((col>>24)&0xff) / 255.f; + + // assume orthographic projection with units = screen pixels, origin at top left + glBindTexture(GL_TEXTURE_2D, g_ftex); + + const float ox = x; + + while (*text) + { + int c = (unsigned char)*text; + if (c == '\t') + { + for (int i = 0; i < 4; ++i) + { + if (x < g_tabStops[i]+ox) + { + x = g_tabStops[i]+ox; + break; + } + } + } + else if (c >= 32 && c < 128) + { + stbtt_aligned_quad q; + getBakedQuad(g_cdata, 512,512, c-32, &x,&y,&q); + + float v[12] = { + q.x0, q.y0, + q.x1, q.y1, + q.x1, q.y0, + q.x0, q.y0, + q.x0, q.y1, + q.x1, q.y1, + }; + float uv[12] = { + q.s0, q.t0, + q.s1, q.t1, + q.s1, q.t0, + q.s0, q.t0, + q.s0, q.t1, + q.s1, q.t1, + }; + float c[24] = { + r, g, b, a, + r, g, b, a, + r, g, b, a, + r, g, b, a, + r, g, b, a, + r, g, b, a, + }; + glBindVertexArray(g_vao); + glBindBuffer(GL_ARRAY_BUFFER, g_vbos[0]); + glBufferData(GL_ARRAY_BUFFER, 12*sizeof(float), v, GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, g_vbos[1]); + glBufferData(GL_ARRAY_BUFFER, 12*sizeof(float), uv, GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, g_vbos[2]); + glBufferData(GL_ARRAY_BUFFER, 24*sizeof(float), c, GL_STATIC_DRAW); + glDrawArrays(GL_TRIANGLES, 0, 6); + + } + ++text; + } + + //glEnd(); + //glDisable(GL_TEXTURE_2D); +} + + +void imguiRenderGLDraw(int width, int height) +{ + const imguiGfxCmd* q = imguiGetRenderQueue(); + int nq = imguiGetRenderQueueSize(); + + const float s = 1.0f/8.0f; + + glViewport(0, 0, width, height); + glUseProgram(g_program); + glActiveTexture(GL_TEXTURE0); + glUniform2f(g_programViewportLocation, (float) width, (float) height); + glUniform1i(g_programTextureLocation, 0); + + + glDisable(GL_SCISSOR_TEST); + for (int i = 0; i < nq; ++i) + { + const imguiGfxCmd& cmd = q[i]; + if (cmd.type == IMGUI_GFXCMD_RECT) + { + if (cmd.rect.r == 0) + { + drawRect((float)cmd.rect.x*s+0.5f, (float)cmd.rect.y*s+0.5f, + (float)cmd.rect.w*s-1, (float)cmd.rect.h*s-1, + 1.0f, cmd.col); + } + else + { + drawRoundedRect((float)cmd.rect.x*s+0.5f, (float)cmd.rect.y*s+0.5f, + (float)cmd.rect.w*s-1, (float)cmd.rect.h*s-1, + (float)cmd.rect.r*s, 1.0f, cmd.col); + } + } + else if (cmd.type == IMGUI_GFXCMD_LINE) + { + drawLine(cmd.line.x0*s, cmd.line.y0*s, cmd.line.x1*s, cmd.line.y1*s, cmd.line.r*s, 1.0f, cmd.col); + } + else if (cmd.type == IMGUI_GFXCMD_TRIANGLE) + { + if (cmd.flags == 1) + { + const float verts[3*2] = + { + (float)cmd.rect.x*s+0.5f, (float)cmd.rect.y*s+0.5f, + (float)cmd.rect.x*s+0.5f+(float)cmd.rect.w*s-1, (float)cmd.rect.y*s+0.5f+(float)cmd.rect.h*s/2-0.5f, + (float)cmd.rect.x*s+0.5f, (float)cmd.rect.y*s+0.5f+(float)cmd.rect.h*s-1, + }; + drawPolygon(verts, 3, 1.0f, cmd.col); + } + if (cmd.flags == 2) + { + const float verts[3*2] = + { + (float)cmd.rect.x*s+0.5f, (float)cmd.rect.y*s+0.5f+(float)cmd.rect.h*s-1, + (float)cmd.rect.x*s+0.5f+(float)cmd.rect.w*s/2-0.5f, (float)cmd.rect.y*s+0.5f, + (float)cmd.rect.x*s+0.5f+(float)cmd.rect.w*s-1, (float)cmd.rect.y*s+0.5f+(float)cmd.rect.h*s-1, + }; + drawPolygon(verts, 3, 1.0f, cmd.col); + } + } + else if (cmd.type == IMGUI_GFXCMD_TEXT) + { + drawText(cmd.text.x, cmd.text.y, cmd.text.text, cmd.text.align, cmd.col); + } + else if (cmd.type == IMGUI_GFXCMD_SCISSOR) + { + if (cmd.flags) + { + glEnable(GL_SCISSOR_TEST); + glScissor(cmd.rect.x, cmd.rect.y, cmd.rect.w, cmd.rect.h); + } + else + { + glDisable(GL_SCISSOR_TEST); + } + } + } + glDisable(GL_SCISSOR_TEST); +} diff --git a/testbed/imgui/imguiRenderGL3.h b/testbed/imgui/imguiRenderGL3.h new file mode 100644 index 00000000..6c84a043 --- /dev/null +++ b/testbed/imgui/imguiRenderGL3.h @@ -0,0 +1,28 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +// Source altered and distributed from https://github.com/AdrienHerubel/imgui + +#ifndef IMGUI_RENDER_GL_H +#define IMGUI_RENDER_GL_H + +bool imguiRenderGLInit(const char* fontpath); +void imguiRenderGLDestroy(); +void imguiRenderGLDraw(int width, int height); + +#endif // IMGUI_RENDER_GL_H \ No newline at end of file diff --git a/testbed/imgui/stb_truetype.h b/testbed/imgui/stb_truetype.h new file mode 100644 index 00000000..9f07124a --- /dev/null +++ b/testbed/imgui/stb_truetype.h @@ -0,0 +1,2065 @@ +// stb_truetype.h - v0.6c - public domain +// authored from 2009-2012 by Sean Barrett / RAD Game Tools +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// +// Bug/warning reports: +// "Zer" on mollyrocket (with fix) +// Cass Everitt +// stoiko (Haemimont Games) +// Brian Hook +// Walter van Niftrik +// +// VERSION HISTORY +// +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (STB) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// +// LICENSE +// +// This software is in the public domain. Where that dedication is not +// recognized, you are granted a perpetual, irrevokable license to copy +// and modify this file as you see fit. +// +// USAGE +// +// Include this file in whatever places neeed to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start, +// and you can cut and paste from it to move to more advanced) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- use for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetCodepointKernAdvance() +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since they different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// ADVANCED USAGE +// +// Quality: +// +// - Use the functions with Subpixel at the end to allow your characters +// to have subpixel positioning. Since the font is anti-aliased, not +// hinted, this is very import for quality. (This is not possible with +// baked fonts.) +// +// - Kerning is now supported, and if you're supporting subpixel rendering +// then kerning is worth using to give your text a polished look. +// +// Performance: +// +// - Convert Unicode codepoints to glyph indexes and operate on the glyphs; +// if you don't do this, stb_truetype is forced to do the conversion on +// every call. +// +// - There are a lot of memory allocations. We should modify it to take +// a temp buffer and allocate from the temp buffer (without freeing), +// should help performance a lot. +// +// NOTES +// +// The system uses the raw data found in the .ttf file without changing it +// and without building auxiliary data structures. This is a bit inefficient +// on little-endian systems (the data is big-endian), but assuming you're +// caching the bitmaps or glyph shapes this shouldn't be a big deal. +// +// It appears to be very hard to programmatically determine what font a +// given file is in a general way. I provide an API for this, but I don't +// recommend it. +// +// +// SOURCE STATISTICS (based on v0.6c, 2050 LOC) +// +// Documentation & header file 520 LOC \___ 660 LOC documentation +// Sample code 140 LOC / +// Truetype parsing 620 LOC ---- 620 LOC TrueType +// Software rasterization 240 LOC \ . +// Curve tesselation 120 LOC \__ 550 LOC Bitmap creation +// Bitmap management 100 LOC / +// Baked bitmap interface 70 LOC / +// Font name matching & access 150 LOC ---- 150 +// C runtime library abstraction 60 LOC ---- 60 + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// SAMPLE PROGRAMS +//// +// +// Incomplete text-in-3d-api example, which draws quads properly aligned to be lossless +// +#if 0 +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<20]; +unsigned char temp_bitmap[512*512]; + +stbtt_bakedchar cdata[96]; // ASCII 32..126 is 95 glyphs +GLstbtt_uint ftex; + +void my_stbtt_initfont(void) +{ + fread(ttf_buffer, 1, 1<<20, fopen("c:/windows/fonts/times.ttf", "rb")); + stbtt_BakeFontBitmap(data,0, 32.0, temp_bitmap,512,512, 32,96, cdata); // no guarantee this fits! + // can free ttf_buffer at this point + glGenTextures(1, &ftex); + glBindTexture(GL_TEXTURE_2D, ftex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 512,512, 0, GL_ALPHA, GL_UNSIGNED_BYTE, temp_bitmap); + // can free temp_bitmap at this point + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); +} + +void my_stbtt_print(float x, float y, char *text) +{ + // assume orthographic projection with units = screen pixels, origin at top left + glBindTexture(GL_TEXTURE_2D, ftex); + glBegin(GL_QUADS); + while (*text) { + if (*text >= 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl,0=old d3d + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=0; + char *text = "Heljo World!"; + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // #define your own STBTT_sort() to override this to avoid qsort + #ifndef STBTT_sort + #include + #define STBTT_sort(data,num_items,item_size,compare_func) qsort(data,num_items,item_size,compare_func) + #endif + + // #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) malloc(x) + #define STBTT_free(x,u) free(x) + #endif + + #ifndef STBTT_assert + #include + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +extern int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +extern void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +extern int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. You can just skip +// this step if you know it's that kind of font. + + +// The following structure is defined publically so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +typedef struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph +} stbtt_fontinfo; + +extern int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +extern float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +extern float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +extern void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +extern void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +extern void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +extern int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 +// @TODO; for now always returns 0! + +extern int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +extern void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +extern int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +extern int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy; + unsigned char type,padding; + } stbtt_vertex; +#endif + +extern int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +extern int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +extern int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates + +extern void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +extern void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +extern unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +extern unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +extern void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +extern void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +extern void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +extern void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +extern unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +extern unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +extern void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +extern void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +extern void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +extern void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +extern void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata); + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +extern int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +extern int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +extern const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +#if defined(STB_TRUETYPE_BIGENDIAN) && !defined(ALLOW_UNALIGNED_TRUETYPE) + + #define ttUSHORT(p) (* (stbtt_uint16 *) (p)) + #define ttSHORT(p) (* (stbtt_int16 *) (p)) + #define ttULONG(p) (* (stbtt_uint32 *) (p)) + #define ttLONG(p) (* (stbtt_int32 *) (p)) + +#else + + stbtt_uint16 ttUSHORT(const stbtt_uint8 *p) { return p[0]*256 + p[1]; } + stbtt_int16 ttSHORT(const stbtt_uint8 *p) { return p[0]*256 + p[1]; } + stbtt_uint32 ttULONG(const stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + stbtt_int32 ttLONG(const stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#endif + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(const stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +int stbtt_GetFontOffsetForIndex(const unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*14); + } + } + return -1; +} + +int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data2, int fontstart) +{ + stbtt_uint8 *data = (stbtt_uint8 *) data2; + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + if (!cmap || !info->loca || !info->head || !info->glyf || !info->hhea || !info->hmtx) + return 0; + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + stbtt_uint16 item, offset, start, end; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 start, end; + searchRange >>= 1; + start = ttUSHORT(data + search + 2 + segcount*2 + 2); + end = ttUSHORT(data + search + 2); + start = ttUSHORT(data + search + searchRange*2 + segcount*2 + 2); + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + item = (stbtt_uint16) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + end = ttUSHORT(data + index_map + 14 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + return 1; +} + +int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours == -1) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0) memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else if (numberOfContours < 0) { + // @TODO other compound variations? + STBTT_assert(0); + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0,y0,x1,y1; + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) + x0=y0=x1=y1=0; // e.g. space character + // now move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor(x0 * scale_x + shift_x); + if (iy0) *iy0 = -STBTT_iceil (y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil (x1 * scale_x + shift_x); + if (iy1) *iy1 = -STBTT_ifloor(y0 * scale_y + shift_y); +} +void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + +typedef struct stbtt__active_edge +{ + int x,dx; + float ey; + struct stbtt__active_edge *next; + int valid; +} stbtt__active_edge; + +#define FIXSHIFT 10 +#define FIX (1 << FIXSHIFT) +#define FIXMASK (FIX-1) + +static stbtt__active_edge *new_active(stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) STBTT_malloc(sizeof(*z), userdata); // @TODO: make a pool of these!!! + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(e->y0 <= start_point); + if (!z) return z; + // round dx down to avoid going too far + if (dxdy < 0) + z->dx = -STBTT_ifloor(FIX * -dxdy); + else + z->dx = STBTT_ifloor(FIX * dxdy); + z->x = STBTT_ifloor(FIX * (e->x0 + dxdy * (start_point - e->y0))); + z->x -= off_x * FIX; + z->ey = e->y1; + z->next = 0; + z->valid = e->invert ? 1 : -1; + return z; +} + +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->valid; + } else { + int x1 = e->x; w += e->valid; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> FIXSHIFT; + int j = x1 >> FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((FIX - (x0 & FIXMASK)) * max_weight) >> FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & FIXMASK) * max_weight) >> FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->valid); + z->valid = 0; + STBTT_free(z, userdata); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = new_active(e, off_x, scan_y, userdata); + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + while (active) { + stbtt__active_edge *z = active; + active = active->next; + STBTT_free(z, userdata); + } + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +static int stbtt__edge_compare(const void *p, const void *q) +{ + stbtt__edge *a = (stbtt__edge *) p; + stbtt__edge *b = (stbtt__edge *) q; + + if (a->y0 < b->y0) return -1; + if (a->y0 > b->y0) return 1; + return 0; +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; + int vsubsample = result->h < 8 ? 15 : 5; + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = p[a].y * y_scale_inv * vsubsample + shift_y; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = p[b].y * y_scale_inv * vsubsample + shift_y; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tesselate until threshhold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +// returns number of contours +stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count, *winding_lengths; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) return NULL; + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBox(info, glyph, scale_x, scale_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +extern int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + stbtt_InitFont(&f, data, offset); + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 2; + if (y+gh+2 > bottom_y) + bottom_y = y+gh+2; + } + return bottom_y; +} + +void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(const stbtt_uint8 *s1, stbtt_int32 len1, const stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((const stbtt_uint8*) s1, len1, (const stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8), off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + stbtt_int32 slen = ttUSHORT(fc+loc+12+8), off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +int stbtt_FindMatchingFont(const unsigned char *font_collection, const char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#endif // STB_TRUETYPE_IMPLEMENTATION \ No newline at end of file diff --git a/examples/common/meshes/capsule.obj b/testbed/meshes/capsule.obj similarity index 100% rename from examples/common/meshes/capsule.obj rename to testbed/meshes/capsule.obj diff --git a/examples/common/meshes/cone.obj b/testbed/meshes/cone.obj similarity index 100% rename from examples/common/meshes/cone.obj rename to testbed/meshes/cone.obj diff --git a/examples/common/meshes/convexmesh.obj b/testbed/meshes/convexmesh.obj similarity index 100% rename from examples/common/meshes/convexmesh.obj rename to testbed/meshes/convexmesh.obj diff --git a/examples/common/meshes/cylinder.obj b/testbed/meshes/cylinder.obj similarity index 100% rename from examples/common/meshes/cylinder.obj rename to testbed/meshes/cylinder.obj diff --git a/examples/common/meshes/dumbbell.obj b/testbed/meshes/dumbbell.obj similarity index 100% rename from examples/common/meshes/dumbbell.obj rename to testbed/meshes/dumbbell.obj diff --git a/examples/common/meshes/sphere.obj b/testbed/meshes/sphere.obj similarity index 100% rename from examples/common/meshes/sphere.obj rename to testbed/meshes/sphere.obj diff --git a/examples/common/opengl-framework/CMakeLists.txt b/testbed/opengl-framework/CMakeLists.txt similarity index 96% rename from examples/common/opengl-framework/CMakeLists.txt rename to testbed/opengl-framework/CMakeLists.txt index e10ae781..56a108a1 100644 --- a/examples/common/opengl-framework/CMakeLists.txt +++ b/testbed/opengl-framework/CMakeLists.txt @@ -71,6 +71,8 @@ SET(OPENGL_FRAMEWORK_SOURCES "src/TextureReaderWriter.cpp" "src/VertexBufferObject.h" "src/VertexBufferObject.cpp" + "src/VertexArrayObject.h" + "src/VertexArrayObject.cpp" ) IF(USE_JPEG_TEXTURES) diff --git a/examples/common/opengl-framework/cmake/FindFREEGLUT.cmake b/testbed/opengl-framework/cmake/FindFREEGLUT.cmake similarity index 100% rename from examples/common/opengl-framework/cmake/FindFREEGLUT.cmake rename to testbed/opengl-framework/cmake/FindFREEGLUT.cmake diff --git a/examples/common/opengl-framework/cmake/FindGLEW.cmake b/testbed/opengl-framework/cmake/FindGLEW.cmake similarity index 100% rename from examples/common/opengl-framework/cmake/FindGLEW.cmake rename to testbed/opengl-framework/cmake/FindGLEW.cmake diff --git a/examples/common/opengl-framework/src/Camera.cpp b/testbed/opengl-framework/src/Camera.cpp similarity index 100% rename from examples/common/opengl-framework/src/Camera.cpp rename to testbed/opengl-framework/src/Camera.cpp diff --git a/examples/common/opengl-framework/src/Camera.h b/testbed/opengl-framework/src/Camera.h similarity index 100% rename from examples/common/opengl-framework/src/Camera.h rename to testbed/opengl-framework/src/Camera.h diff --git a/examples/common/opengl-framework/src/FrameBufferObject.cpp b/testbed/opengl-framework/src/FrameBufferObject.cpp similarity index 99% rename from examples/common/opengl-framework/src/FrameBufferObject.cpp rename to testbed/opengl-framework/src/FrameBufferObject.cpp index 5f67be23..247873c3 100644 --- a/examples/common/opengl-framework/src/FrameBufferObject.cpp +++ b/testbed/opengl-framework/src/FrameBufferObject.cpp @@ -37,7 +37,7 @@ FrameBufferObject::FrameBufferObject() : mFrameBufferID(0), mRenderBufferID (0) // Destructor FrameBufferObject::~FrameBufferObject() { - + destroy(); } // Create the frame buffer object diff --git a/examples/common/opengl-framework/src/FrameBufferObject.h b/testbed/opengl-framework/src/FrameBufferObject.h similarity index 94% rename from examples/common/opengl-framework/src/FrameBufferObject.h rename to testbed/opengl-framework/src/FrameBufferObject.h index a76951c7..6194510a 100644 --- a/examples/common/opengl-framework/src/FrameBufferObject.h +++ b/testbed/opengl-framework/src/FrameBufferObject.h @@ -65,7 +65,7 @@ class FrameBufferObject { void attachTexture(uint position, uint textureID); // Bind the FBO - void bind(uint position) const; + void bind() const; // Unbind the FBO void unbind() const; @@ -78,18 +78,14 @@ class FrameBufferObject { }; // Bind the FBO -inline void FrameBufferObject::bind(uint position) const { +inline void FrameBufferObject::bind() const { assert(mFrameBufferID != 0); glBindFramebuffer(GL_FRAMEBUFFER, mFrameBufferID); - glDrawBuffer(position); - glReadBuffer(position); } // Unbind the FBO inline void FrameBufferObject::unbind() const { assert(mFrameBufferID != 0); - glDrawBuffer(GL_NONE); - glReadBuffer(GL_NONE); glBindFramebuffer(GL_FRAMEBUFFER, 0); } diff --git a/examples/common/opengl-framework/src/shaders/phong.vert b/testbed/opengl-framework/src/Light.cpp similarity index 68% rename from examples/common/opengl-framework/src/shaders/phong.vert rename to testbed/opengl-framework/src/Light.cpp index 620344a9..9bffc0c6 100644 --- a/examples/common/opengl-framework/src/shaders/phong.vert +++ b/testbed/opengl-framework/src/Light.cpp @@ -23,28 +23,40 @@ * * ********************************************************************************/ -// Uniform variables -uniform mat4 localToCameraMatrix; // Local-space to camera-space matrix -uniform mat4 projectionMatrix; // Projection matrix -uniform mat3 normalMatrix; // Normal matrix +// Libraries +#include "Light.h" -// Varying variables -varying vec3 vertexPosCameraSpace; // Camera-space position of the vertex -varying vec3 vertexNormalCameraSpace; // Vertex normal in camera-space -varying vec2 texCoords; // Texture coordinates +// Namespaces +using namespace openglframework; -void main() { +// Constructor +Light::Light(GLuint id) + : mLightID(id), mDiffuseColor(Color::white()), + mSpecularColor(Color::white()), mIsActive(false) { - // Compute the vertex position - vec4 positionCameraSpace = localToCameraMatrix * gl_Vertex; - vertexPosCameraSpace = positionCameraSpace.xyz; - - // Compute the world surface normal - vertexNormalCameraSpace = normalMatrix * gl_Normal; - - // Get the texture coordinates - texCoords = gl_MultiTexCoord0.xy; - - // Compute the clip-space vertex coordinates - gl_Position = projectionMatrix * positionCameraSpace; +} + +// Constructor +Light::Light(GLuint id, Color diffuseColor, Color specularColor) + : mLightID(id), mDiffuseColor(diffuseColor), + mSpecularColor(specularColor), mIsActive(false) { + +} + +// Destructor +Light::~Light() { + +} + +// Initialize the light +void Light::init() { + + // Enable the light + enable(); + + // Set the diffuse and specular color + GLfloat diffuseColor[] = {mDiffuseColor.r, mDiffuseColor.g, mDiffuseColor.b, mDiffuseColor.a}; + GLfloat specularColor[] = {mSpecularColor.r,mSpecularColor.g,mSpecularColor.b,mSpecularColor.a}; + glLightfv(mLightID, GL_DIFFUSE, diffuseColor); + glLightfv(mLightID, GL_SPECULAR, specularColor); } diff --git a/examples/common/opengl-framework/src/Light.h b/testbed/opengl-framework/src/Light.h similarity index 76% rename from examples/common/opengl-framework/src/Light.h rename to testbed/opengl-framework/src/Light.h index 8f8323d9..9ecc63b9 100644 --- a/examples/common/opengl-framework/src/Light.h +++ b/testbed/opengl-framework/src/Light.h @@ -31,7 +31,6 @@ #include "maths/Vector3.h" #include "Object3D.h" #include "Texture2D.h" -#include "FrameBufferObject.h" #include "Shader.h" #include @@ -56,15 +55,6 @@ class Light : public Object3D { // True if the light is active bool mIsActive; - // Shadow map associated with this light - Texture2D mShadowMap; - - // Framebuffer object to render the shadow map - FrameBufferObject mFBOShadowMap; - - // Shader to render the depth of the scene into a texture - Shader mDepthShader; - public: // -------------------- Methods -------------------- // @@ -101,18 +91,6 @@ class Light : public Object3D { // Disable the light void disable(); - - // Create a shadow map associated with this light - bool createShadowMap(uint width, uint height); - - // Call this method before rendering the scene for the shadow map computation - void startRenderingShadowMap(); - - // Call this method after having rendered the scene for the shadow map computation - void stopRenderingShadowMap(); - - // Destroy the shadow map associated with this light - void destroyShadowMap(); }; // Return the diffuse color @@ -159,23 +137,6 @@ inline void Light::disable() { glDisable(GL_LIGHT0 + mLightID); } -// Destroy the shadow map associated with this light -inline void Light::destroyShadowMap() { - mShadowMap.destroy(); - mFBOShadowMap.destroy(); - mDepthShader.destroy(); -} - -// Call this method before rendering the scene for the shadow map computation -inline void Light::startRenderingShadowMap() { - assert(mShadowMap.getID()); -} - -// Call this method after having rendered the scene for the shadow map computation -inline void Light::stopRenderingShadowMap() { - assert(mShadowMap.getID()); -} - } #endif diff --git a/examples/common/opengl-framework/src/Mesh.cpp b/testbed/opengl-framework/src/Mesh.cpp similarity index 100% rename from examples/common/opengl-framework/src/Mesh.cpp rename to testbed/opengl-framework/src/Mesh.cpp diff --git a/examples/common/opengl-framework/src/Mesh.h b/testbed/opengl-framework/src/Mesh.h similarity index 98% rename from examples/common/opengl-framework/src/Mesh.h rename to testbed/opengl-framework/src/Mesh.h index ed15ed04..92e5d144 100644 --- a/examples/common/opengl-framework/src/Mesh.h +++ b/testbed/opengl-framework/src/Mesh.h @@ -140,10 +140,10 @@ class Mesh : public Object3D { void setNormal(uint i, const Vector3& normal); // Return the color of a given vertex - const Color& getColor(uint i) const; + const Color& getVertexColor(uint i) const; // Set the color of a given vertex - void setColor(uint i, const Color& color); + void setVertexColor(uint i, const Color& color); // Set a color to all the vertices void setColorToAllVertices(const Color& color); @@ -282,13 +282,13 @@ inline void Mesh::setNormal(uint i, const Vector3& normal) { } // Return the color of a given vertex -inline const Color& Mesh::getColor(uint i) const { +inline const Color& Mesh::getVertexColor(uint i) const { assert(i < getNbVertices()); return mColors[i]; } // Set the color of a given vertex -inline void Mesh::setColor(uint i, const Color& color) { +inline void Mesh::setVertexColor(uint i, const Color& color) { // If the color array does not have the same size as // the vertices array diff --git a/examples/common/opengl-framework/src/MeshReaderWriter.cpp b/testbed/opengl-framework/src/MeshReaderWriter.cpp similarity index 100% rename from examples/common/opengl-framework/src/MeshReaderWriter.cpp rename to testbed/opengl-framework/src/MeshReaderWriter.cpp diff --git a/examples/common/opengl-framework/src/MeshReaderWriter.h b/testbed/opengl-framework/src/MeshReaderWriter.h similarity index 100% rename from examples/common/opengl-framework/src/MeshReaderWriter.h rename to testbed/opengl-framework/src/MeshReaderWriter.h diff --git a/examples/common/opengl-framework/src/Object3D.cpp b/testbed/opengl-framework/src/Object3D.cpp similarity index 100% rename from examples/common/opengl-framework/src/Object3D.cpp rename to testbed/opengl-framework/src/Object3D.cpp diff --git a/examples/common/opengl-framework/src/Object3D.h b/testbed/opengl-framework/src/Object3D.h similarity index 100% rename from examples/common/opengl-framework/src/Object3D.h rename to testbed/opengl-framework/src/Object3D.h diff --git a/examples/common/opengl-framework/src/Shader.cpp b/testbed/opengl-framework/src/Shader.cpp similarity index 97% rename from examples/common/opengl-framework/src/Shader.cpp rename to testbed/opengl-framework/src/Shader.cpp index 137669f7..aab00154 100644 --- a/examples/common/opengl-framework/src/Shader.cpp +++ b/testbed/opengl-framework/src/Shader.cpp @@ -48,7 +48,7 @@ Shader::Shader(const std::string vertexShaderFilename, const std::string fragmen // Destructor Shader::~Shader() { - + destroy(); } // Create the shader @@ -112,7 +112,7 @@ bool Shader::create(const std::string vertexShaderFilename, glGetShaderInfoLog(vertexShaderID, lengthLog, NULL, str); // Display the log of the compilation - std::cerr << "Vertex Shader Error : " << str << std::endl; + std::cerr << "Vertex Shader Error (in " << vertexShaderFilename << ") : " << str << std::endl; delete[] str; assert(false); return false; @@ -165,7 +165,7 @@ bool Shader::create(const std::string vertexShaderFilename, glGetShaderInfoLog(fragmentShaderID, lengthLog, NULL, str); // Display the log of the compilation - std::cerr << "Fragment Shader Error : " << str << std::endl; + std::cerr << "Fragment Shader Error (in " << fragmentShaderFilename << ") : " << str << std::endl; delete[] str; assert(false); return false; diff --git a/examples/common/opengl-framework/src/Shader.h b/testbed/opengl-framework/src/Shader.h similarity index 71% rename from examples/common/opengl-framework/src/Shader.h rename to testbed/opengl-framework/src/Shader.h index 52a206bc..8019b645 100644 --- a/examples/common/opengl-framework/src/Shader.h +++ b/testbed/opengl-framework/src/Shader.h @@ -34,6 +34,7 @@ #include "maths/Vector4.h" #include #include +#include #include namespace openglframework { @@ -78,54 +79,60 @@ class Shader { void unbind() const; // Return the location of a uniform variable inside a shader program - int getUniformLocation(const std::string& variableName) const; + GLint getUniformLocation(const std::string& variableName, bool errorIfMissing = true) const; + + // Return the location of an attribute variable inside a shader program + GLint getAttribLocation(const std::string& variableName, bool errorIfMissing = true) const; // Set a float uniform value to this shader (be careful if the uniform is not // used in the shader, the compiler will remove it, then when you will try // to set it, an assert will occur) - void setFloatUniform(const std::string& variableName, float value) const; + void setFloatUniform(const std::string& variableName, float value, bool errorIfMissing = true) const; // Set an int uniform value to this shader (be careful if the uniform is not // used in the shader, the compiler will remove it, then when you will try // to set it, an assert will occur) - void setIntUniform(const std::string& variableName, int value) const; + void setIntUniform(const std::string& variableName, int value, bool errorIfMissing = true) const; // Set a vector 2 uniform value to this shader (be careful if the uniform is not // used in the shader, the compiler will remove it, then when you will try // to set it, an assert will occur) - void setVector2Uniform(const std::string& variableName, const Vector2& v) const; + void setVector2Uniform(const std::string& variableName, const Vector2& v, bool errorIfMissing = true) const; // Set a vector 3 uniform value to this shader (be careful if the uniform is not // used in the shader, the compiler will remove it, then when you will try // to set it, an assert will occur) - void setVector3Uniform(const std::string& variableName, const Vector3& v) const; + void setVector3Uniform(const std::string& variableName, const Vector3& v, bool errorIfMissing = true) const; // Set a vector 4 uniform value to this shader (be careful if the uniform is not // used in the shader, the compiler will remove it, then when you will try // to set it, an assert will occur) - void setVector4Uniform(const std::string& variableName, const Vector4 &v) const; + void setVector4Uniform(const std::string& variableName, const Vector4 &v, bool errorIfMissing = true) const; // Set a 3x3 matrix uniform value to this shader (be careful if the uniform is not // used in the shader, the compiler will remove it, then when you will try // to set it, an assert will occur) void setMatrix3x3Uniform(const std::string& variableName, const float* matrix, - bool transpose = false) const; + bool transpose = false, bool errorIfMissing = true) const; // Set a 3x3 matrix uniform value to this shader (be careful if the uniform is not // used in the shader, the compiler will remove it, then when you will try // to set it, an assert will occur) - void setMatrix3x3Uniform(const std::string& variableName, const Matrix3& matrix) const; + void setMatrix3x3Uniform(const std::string& variableName, const Matrix3& matrix, bool errorIfMissing = true) const; // Set a 4x4 matrix uniform value to this shader (be careful if the uniform is not // used in the shader, the compiler will remove it, then when you will try // to set it, an assert will occur) void setMatrix4x4Uniform(const std::string& variableName, const float* matrix, - bool transpose = false) const; + bool transpose = false, bool errorIfMissing = true) const; // Set a 4x4 matrix uniform value to this shader (be careful if the uniform is not // used in the shader, the compiler will remove it, then when you will try // to set it, an assert will occur) - void setMatrix4x4Uniform(const std::string& variableName, const Matrix4& matrix) const; + void setMatrix4x4Uniform(const std::string& variableName, const Matrix4& matrix, bool errorIfMissing = true) const; + + // Return the shader object program ID + GLuint getProgramObjectId() const; // Return true if the needed OpenGL extensions are available static bool checkOpenGLExtensions(); @@ -144,77 +151,112 @@ inline void Shader::unbind() const { } // Return the location of a uniform variable inside a shader program -inline int Shader::getUniformLocation(const std::string& variableName) const { +inline GLint Shader::getUniformLocation(const std::string& variableName, bool errorIfMissing) const { assert(mProgramObjectID != 0); - int location = glGetUniformLocation(mProgramObjectID, variableName.c_str()); - if (location == -1) { + GLint location = glGetUniformLocation(mProgramObjectID, variableName.c_str()); + if (location == -1 && errorIfMissing) { std::cerr << "Error in vertex shader " << mFilenameVertexShader << " or in fragment shader" << mFilenameFragmentShader << " : No Uniform variable : " << variableName << std::endl; + throw std::logic_error("Error in Shader"); } - assert(location != -1); + + return location; +} + +// Return the location of an attribute variable inside a shader program +inline GLint Shader::getAttribLocation(const std::string& variableName, bool errorIfMissing) const { + assert(mProgramObjectID != 0); + GLint location = glGetAttribLocation(mProgramObjectID, variableName.c_str()); + if (location == -1 && errorIfMissing) { + std::cerr << "Error in vertex shader " << mFilenameVertexShader << " or in fragment shader" + << mFilenameFragmentShader << " : No Uniform variable : " << variableName + << std::endl; + throw std::logic_error("Error in Shader"); + } + return location; } // Clear the shader inline void Shader::destroy() { - glDeleteProgram(mProgramObjectID); - mProgramObjectID = 0; + if (mProgramObjectID != 0) { + glDeleteProgram(mProgramObjectID); + mProgramObjectID = 0; + } } // Set a float uniform value to this shader (be careful if the uniform is not // used in the shader, the compiler will remove it, then when you will try // to set it, an assert will occur) -inline void Shader::setFloatUniform(const std::string& variableName, float value) const { +inline void Shader::setFloatUniform(const std::string& variableName, float value, bool errorIfMissing) const { assert(mProgramObjectID != 0); - glUniform1f(getUniformLocation(variableName), value); + GLint location = getUniformLocation(variableName, errorIfMissing); + if (location != -1) { + glUniform1f(location, value); + } } // Set an int uniform value to this shader (be careful if the uniform is not // used in the shader, the compiler will remove it, then when you will try // to set it, an assert will occur) -inline void Shader::setIntUniform(const std::string& variableName, int value) const { +inline void Shader::setIntUniform(const std::string& variableName, int value, bool errorIfMissing) const { assert(mProgramObjectID != 0); - glUniform1i(getUniformLocation(variableName), value); + GLint location = getUniformLocation(variableName, errorIfMissing); + if (location != -1) { + glUniform1i(location, value); + } } // Set a vector 2 uniform value to this shader (be careful if the uniform is not // used in the shader, the compiler will remove it, then when you will try // to set it, an assert will occur) -inline void Shader::setVector2Uniform(const std::string& variableName, const Vector2& v) const { +inline void Shader::setVector2Uniform(const std::string& variableName, const Vector2& v, bool errorIfMissing) const { assert(mProgramObjectID != 0); - glUniform2f(getUniformLocation(variableName), v.x, v.y); + GLint location = getUniformLocation(variableName, errorIfMissing); + if (location != -1) { + glUniform2f(location, v.x, v.y); + } } // Set a vector 3 uniform value to this shader (be careful if the uniform is not // used in the shader, the compiler will remove it, then when you will try // to set it, an assert will occur) -inline void Shader::setVector3Uniform(const std::string& variableName, const Vector3 &v) const { +inline void Shader::setVector3Uniform(const std::string& variableName, const Vector3 &v, bool errorIfMissing) const { assert(mProgramObjectID != 0); - glUniform3f(getUniformLocation(variableName), v.x, v.y, v.z); + GLint location = getUniformLocation(variableName, errorIfMissing); + if (location != -1) { + glUniform3f(location, v.x, v.y, v.z); + } } // Set a vector 4 uniform value to this shader (be careful if the uniform is not // used in the shader, the compiler will remove it, then when you will try // to set it, an assert will occur) -inline void Shader::setVector4Uniform(const std::string& variableName, const Vector4& v) const { +inline void Shader::setVector4Uniform(const std::string& variableName, const Vector4& v, bool errorIfMissing) const { assert(mProgramObjectID != 0); - glUniform4f(getUniformLocation(variableName), v.x, v.y, v.z, v.w); + GLint location = getUniformLocation(variableName, errorIfMissing); + if (location != -1) { + glUniform4f(location, v.x, v.y, v.z, v.w); + } } // Set a 4x4 matrix uniform value to this shader (be careful if the uniform is not // used in the shader, the compiler will remove it, then when you will try // to set it, an assert will occur) inline void Shader::setMatrix3x3Uniform(const std::string& variableName, const float* matrix, - bool transpose) const { + bool transpose, bool errorIfMissing) const { assert(mProgramObjectID != 0); - glUniformMatrix3fv(getUniformLocation(variableName), 1, transpose, matrix); + GLint location = getUniformLocation(variableName, errorIfMissing); + if (location != -1) { + glUniformMatrix3fv(location, 1, transpose, matrix); + } } // Set a 3x3 matrix uniform value to this shader (be careful if the uniform is not // used in the shader, the compiler will remove it, then when you will try // to set it, an assert will occur) -inline void Shader::setMatrix3x3Uniform(const std::string& variableName, const Matrix3& matrix) const { +inline void Shader::setMatrix3x3Uniform(const std::string& variableName, const Matrix3& matrix, bool errorIfMissing) const { assert(mProgramObjectID != 0); GLfloat mat[9]; for (int i=0; i<3; i++) { @@ -222,22 +264,28 @@ inline void Shader::setMatrix3x3Uniform(const std::string& variableName, const M mat[i*3 + j] = matrix.getValue(i, j); } } - glUniformMatrix3fv(getUniformLocation(variableName), 1, true, mat); + GLint location = getUniformLocation(variableName, errorIfMissing); + if (location != -1) { + glUniformMatrix3fv(location, 1, true, mat); + } } // Set a 4x4 matrix uniform value to this shader (be careful if the uniform is not // used in the shader, the compiler will remove it, then when you will try // to set it, an assert will occur) inline void Shader::setMatrix4x4Uniform(const std::string& variableName, const float* matrix, - bool transpose) const { + bool transpose, bool errorIfMissing) const { assert(mProgramObjectID != 0); - glUniformMatrix4fv(getUniformLocation(variableName), 1, transpose, matrix); + GLint location = getUniformLocation(variableName, errorIfMissing); + if (location != -1) { + glUniformMatrix4fv(location, 1, transpose, matrix); + } } // Set a 4x4 matrix uniform value to this shader (be careful if the uniform is not // used in the shader, the compiler will remove it, then when you will try // to set it, an assert will occur) -inline void Shader::setMatrix4x4Uniform(const std::string& variableName, const Matrix4& matrix) const { +inline void Shader::setMatrix4x4Uniform(const std::string& variableName, const Matrix4& matrix, bool errorIfMissing) const { assert(mProgramObjectID != 0); GLfloat mat[16]; for (int i=0; i<4; i++) { @@ -245,7 +293,15 @@ inline void Shader::setMatrix4x4Uniform(const std::string& variableName, const M mat[i*4 + j] = matrix.m[i][j]; } } - glUniformMatrix4fv(getUniformLocation(variableName), 1, true, mat); + GLint location = getUniformLocation(variableName, errorIfMissing); + if (location != -1) { + glUniformMatrix4fv(location, 1, true, mat); + } +} + +// Return the shader object program ID +inline GLuint Shader::getProgramObjectId() const { + return mProgramObjectID; } // Return true if the needed OpenGL extensions are available for shaders diff --git a/examples/common/opengl-framework/src/Texture2D.cpp b/testbed/opengl-framework/src/Texture2D.cpp similarity index 76% rename from examples/common/opengl-framework/src/Texture2D.cpp rename to testbed/opengl-framework/src/Texture2D.cpp index 1b316b73..2bc8f0a5 100644 --- a/examples/common/opengl-framework/src/Texture2D.cpp +++ b/testbed/opengl-framework/src/Texture2D.cpp @@ -33,13 +33,13 @@ using namespace openglframework; // Constructor -Texture2D::Texture2D() : mID(0), mLayer(0), mWidth(0), mHeight(0) { +Texture2D::Texture2D() : mID(0), mUnit(0), mWidth(0), mHeight(0) { } // Constructor Texture2D::Texture2D(uint width, uint height, uint internalFormat, uint format, uint type) - : mID(0), mLayer(0), mWidth(0), mHeight(0){ + : mID(0), mUnit(0), mWidth(0), mHeight(0) { // Create the texture create(width, height, internalFormat, format, type); @@ -47,12 +47,12 @@ Texture2D::Texture2D(uint width, uint height, uint internalFormat, uint format, // Destructor Texture2D::~Texture2D() { - + destroy(); } // Create the texture void Texture2D::create(uint width, uint height, uint internalFormat, uint format, uint type, - void* data) { + void* data) { // Destroy the current texture destroy(); @@ -72,12 +72,34 @@ void Texture2D::create(uint width, uint height, uint internalFormat, uint format glBindTexture(GL_TEXTURE_2D, 0); } +// Create the texture +void Texture2D::create(uint width, uint height, uint internalFormat, uint format, uint type, + uint minFilter, uint maxFilter, uint wrapS, uint wrapT, void* data) { + + // Destroy the current texture + destroy(); + + mWidth = width; + mHeight = height; + + // Create the OpenGL texture + glGenTextures(1, &mID); + assert(mID != 0); + glBindTexture(GL_TEXTURE_2D, mID); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapS); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, maxFilter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter); + glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, mWidth, mHeight, 0, format, type, data); + glBindTexture(GL_TEXTURE_2D, 0); +} + // Destroy the texture void Texture2D::destroy() { if (mID != 0) { glDeleteTextures(1, &mID); mID = 0; - mLayer = 0; + mUnit = 0; mWidth = 0; mHeight = 0; } diff --git a/examples/common/opengl-framework/src/Texture2D.h b/testbed/opengl-framework/src/Texture2D.h similarity index 84% rename from examples/common/opengl-framework/src/Texture2D.h rename to testbed/opengl-framework/src/Texture2D.h index 9c6fa1c0..5f8ce3ae 100644 --- a/examples/common/opengl-framework/src/Texture2D.h +++ b/testbed/opengl-framework/src/Texture2D.h @@ -45,8 +45,8 @@ class Texture2D { // OpenGL texture ID GLuint mID; - // Layer of the texture - GLuint mLayer; + // Current texture unit for this texture + GLuint mUnit; // Width uint mWidth; @@ -71,6 +71,10 @@ class Texture2D { void create(uint width, uint height, uint internalFormat, uint format, uint type, void* data = NULL); + // Create the texture + void create(uint width, uint height, uint internalFormat, uint format, uint type, + uint minFilter, uint maxFilter, uint wrapS, uint wrapT, void* data); + // Destroy the texture void destroy(); @@ -83,11 +87,11 @@ class Texture2D { // Get the OpenGL texture ID uint getID() const; - // Get the layer of the texture - uint getLayer() const; + // Get the unit of the texture + uint getUnit() const; - // Set the layer of the texture - void setLayer(uint layer); + // Set the unit of the texture + void setUnit(uint unit); // Get the width uint getWidth() const; @@ -99,17 +103,15 @@ class Texture2D { // Bind the texture inline void Texture2D::bind() const { assert(mID != 0); - glEnable(GL_TEXTURE_2D); - glActiveTexture(GL_TEXTURE0 + mLayer); + glActiveTexture(GL_TEXTURE0 + mUnit); glBindTexture(GL_TEXTURE_2D, mID); } // Unbind the texture inline void Texture2D::unbind() const { assert(mID != 0); - glActiveTexture(GL_TEXTURE0 + mLayer); + glActiveTexture(GL_TEXTURE0 + mUnit); glBindTexture(GL_TEXTURE_2D, 0); - glDisable(GL_TEXTURE_2D); } // Get the OpenGL texture ID @@ -117,14 +119,14 @@ inline uint Texture2D::getID() const { return mID; } -// Get the layer of the texture -inline uint Texture2D::getLayer() const { - return mLayer; +// Get the unit of the texture +inline uint Texture2D::getUnit() const { + return mUnit; } -// Set the layer of the texture -inline void Texture2D::setLayer(uint layer) { - mLayer = layer; +// Set the unit of the texture +inline void Texture2D::setUnit(uint unit) { + mUnit = unit; } // Get the width diff --git a/examples/common/opengl-framework/src/TextureReaderWriter.cpp b/testbed/opengl-framework/src/TextureReaderWriter.cpp similarity index 100% rename from examples/common/opengl-framework/src/TextureReaderWriter.cpp rename to testbed/opengl-framework/src/TextureReaderWriter.cpp diff --git a/examples/common/opengl-framework/src/TextureReaderWriter.h b/testbed/opengl-framework/src/TextureReaderWriter.h similarity index 100% rename from examples/common/opengl-framework/src/TextureReaderWriter.h rename to testbed/opengl-framework/src/TextureReaderWriter.h diff --git a/testbed/opengl-framework/src/VertexArrayObject.cpp b/testbed/opengl-framework/src/VertexArrayObject.cpp new file mode 100644 index 00000000..b30e2e4b --- /dev/null +++ b/testbed/opengl-framework/src/VertexArrayObject.cpp @@ -0,0 +1,70 @@ +/******************************************************************************** +* OpenGL-Framework * +* Copyright (c) 2015 Daniel Chappuis * +********************************************************************************* +* * +* This software is provided 'as-is', without any express or implied warranty. * +* In no event will the authors be held liable for any damages arising from the * +* use of this software. * +* * +* Permission is granted to anyone to use this software for any purpose, * +* including commercial applications, and to alter it and redistribute it * +* freely, subject to the following restrictions: * +* * +* 1. The origin of this software must not be misrepresented; you must not claim * +* that you wrote the original software. If you use this software in a * +* product, an acknowledgment in the product documentation would be * +* appreciated but is not required. * +* * +* 2. Altered source versions must be plainly marked as such, and must not be * +* misrepresented as being the original software. * +* * +* 3. This notice may not be removed or altered from any source distribution. * +* * +********************************************************************************/ + +// Libraries +#include "VertexArrayObject.h" + +using namespace openglframework; + +// Constructor +VertexArrayObject::VertexArrayObject() : mVertexArrayID(0) { + +} + +// Destructor +VertexArrayObject::~VertexArrayObject() { + destroy(); +} + +// Create the vertex buffer object +bool VertexArrayObject::create() { + + // Destroy the current VAO + destroy(); + + // Check that the needed OpenGL extensions are available + bool isExtensionOK = checkOpenGLExtensions(); + if (!isExtensionOK) { + std::cerr << "Error : Impossible to use Vertex Array Object on this platform" << std::endl; + assert(false); + return false; + } + + // Generate a new VAO + glGenVertexArrays(1, &mVertexArrayID); + assert(mVertexArrayID != 0); + + return true; +} + +// Destroy the VAO +void VertexArrayObject::destroy() { + + // Delete the vertex buffer object + if (mVertexArrayID != 0) { + glDeleteVertexArrays(1, &mVertexArrayID); + mVertexArrayID = 0; + } +} diff --git a/testbed/opengl-framework/src/VertexArrayObject.h b/testbed/opengl-framework/src/VertexArrayObject.h new file mode 100644 index 00000000..2f7ed1b0 --- /dev/null +++ b/testbed/opengl-framework/src/VertexArrayObject.h @@ -0,0 +1,99 @@ +/******************************************************************************** +* OpenGL-Framework * +* Copyright (c) 2013 Daniel Chappuis * +********************************************************************************* +* * +* This software is provided 'as-is', without any express or implied warranty. * +* In no event will the authors be held liable for any damages arising from the * +* use of this software. * +* * +* Permission is granted to anyone to use this software for any purpose, * +* including commercial applications, and to alter it and redistribute it * +* freely, subject to the following restrictions: * +* * +* 1. The origin of this software must not be misrepresented; you must not claim * +* that you wrote the original software. If you use this software in a * +* product, an acknowledgment in the product documentation would be * +* appreciated but is not required. * +* * +* 2. Altered source versions must be plainly marked as such, and must not be * +* misrepresented as being the original software. * +* * +* 3. This notice may not be removed or altered from any source distribution. * +* * +********************************************************************************/ + +#ifndef VERTEX_ARRAY_OBJECT_H +#define VERTEX_ARRAY_OBJECT_H + +// Libraries +#include +#include +#include + +namespace openglframework { + + +// Class VertexArrayObject +class VertexArrayObject { + + private : + + // -------------------- Attributes -------------------- // + + /// ID of the Vertex Array Object + GLuint mVertexArrayID; + + public : + + // -------------------- Methods -------------------- // + + /// Constructor + VertexArrayObject(); + + /// Destructor + ~VertexArrayObject(); + + /// Create the vertex buffer object + bool create(); + + /// Bind the VAO + void bind() const; + + /// Unbind the VAO + void unbind() const; + + /// Return true if the needed OpenGL extensions are available for VAO + static bool checkOpenGLExtensions(); + + /// Destroy the VAO + void destroy(); +}; + +// Bind the VAO +inline void VertexArrayObject::bind() const { + assert(mVertexArrayID != 0); + + // Bind the VAO + glBindVertexArray(mVertexArrayID); +} + +// Unbind the VAO +inline void VertexArrayObject::unbind() const { + assert(mVertexArrayID != 0); + + // Unbind the VAO + glBindVertexArray(0); +} + +// Return true if the needed OpenGL extensions are available for VAO +inline bool VertexArrayObject::checkOpenGLExtensions() { + + // Check that OpenGL version is at least 3.0 or there the vertex array object extension exists + return (GLEW_VERSION_3_0 || GL_ARB_vertex_array_object); +} + +} + +#endif + diff --git a/examples/common/opengl-framework/src/VertexBufferObject.cpp b/testbed/opengl-framework/src/VertexBufferObject.cpp similarity index 97% rename from examples/common/opengl-framework/src/VertexBufferObject.cpp rename to testbed/opengl-framework/src/VertexBufferObject.cpp index 8afd1daf..f5fe3b0c 100644 --- a/examples/common/opengl-framework/src/VertexBufferObject.cpp +++ b/testbed/opengl-framework/src/VertexBufferObject.cpp @@ -36,7 +36,7 @@ VertexBufferObject::VertexBufferObject(GLenum targetData) // Destructor VertexBufferObject::~VertexBufferObject() { - + destroy(); } // Create the vertex buffer object @@ -63,21 +63,15 @@ bool VertexBufferObject::create() { // Copy data into the VBO void VertexBufferObject::copyDataIntoVBO(GLsizei size, const void* data, GLenum usage) { - // Bind the VBO - bind(); - // Copy the data into the VBO glBufferData(mTargetData, size, data, usage); - - // Unbind the VBO - unbind(); } // Destroy the VBO void VertexBufferObject::destroy() { // Delete the vertex buffer object - if (mVertexBufferID) { + if (mVertexBufferID != 0) { glDeleteFramebuffers(1, &mVertexBufferID); mVertexBufferID = 0; } diff --git a/examples/common/opengl-framework/src/VertexBufferObject.h b/testbed/opengl-framework/src/VertexBufferObject.h similarity index 100% rename from examples/common/opengl-framework/src/VertexBufferObject.h rename to testbed/opengl-framework/src/VertexBufferObject.h diff --git a/examples/common/opengl-framework/src/definitions.h b/testbed/opengl-framework/src/definitions.h similarity index 100% rename from examples/common/opengl-framework/src/definitions.h rename to testbed/opengl-framework/src/definitions.h diff --git a/examples/common/opengl-framework/src/maths/Color.h b/testbed/opengl-framework/src/maths/Color.h similarity index 100% rename from examples/common/opengl-framework/src/maths/Color.h rename to testbed/opengl-framework/src/maths/Color.h diff --git a/examples/common/opengl-framework/src/maths/Matrix3.h b/testbed/opengl-framework/src/maths/Matrix3.h similarity index 98% rename from examples/common/opengl-framework/src/maths/Matrix3.h rename to testbed/opengl-framework/src/maths/Matrix3.h index 5460f616..8439388f 100644 --- a/examples/common/opengl-framework/src/maths/Matrix3.h +++ b/testbed/opengl-framework/src/maths/Matrix3.h @@ -272,6 +272,13 @@ class Matrix3 { m[2][0] *= nb; m[2][1] *= nb; m[2][2] *= nb; return *this; } + + // Return the identity matrix + static Matrix3 identity() { + return Matrix3(1, 0, 0, + 0, 1, 0, + 0, 0, 1); + } }; } diff --git a/examples/common/opengl-framework/src/maths/Matrix4.h b/testbed/opengl-framework/src/maths/Matrix4.h similarity index 88% rename from examples/common/opengl-framework/src/maths/Matrix4.h rename to testbed/opengl-framework/src/maths/Matrix4.h index bcfbcebd..1a504c38 100644 --- a/examples/common/opengl-framework/src/maths/Matrix4.h +++ b/testbed/opengl-framework/src/maths/Matrix4.h @@ -27,12 +27,13 @@ #define MATRIX4_H // Libraries -#include +#include #include #include #include "Vector3.h" #include "Vector4.h" #include "Matrix3.h" +#include "definitions.h" namespace openglframework { @@ -376,6 +377,18 @@ class Matrix4 { // Return a 4x4 rotation matrix static Matrix4 rotationMatrix(const Vector3& axis, float angle); + + // Return a 4x4 perspective projection matrix + static Matrix4 perspectiveProjectionMatrix(float near, float far, + int width, int height, + float fieldOfView); + + // Return a 4x4 orthographic projection matrix + static Matrix4 orthoProjectionMatrix(float near, float far, int width, + int height); + + // Return the identity matrix + static Matrix4 identity(); }; // * operator @@ -422,6 +435,61 @@ inline Matrix4 Matrix4::rotationMatrix(const Vector3& axis, float angle) { return rotationMatrix; } +// Return a 4x4 perspective projection matrix +inline Matrix4 Matrix4::perspectiveProjectionMatrix(float near, float far, int width, int height, + float fieldOfView) { + + // Compute the aspect ratio + float aspect = float(width) / float(height); + + float top = near * tan((fieldOfView / 2.0f) * (float(PI) / 180.0f)); + float bottom = -top; + float left = bottom * aspect; + float right = top * aspect; + + float fx = 2.0f * near / (right - left); + float fy = 2.0f * near / (top - bottom); + float fz = -(far + near) / (far - near); + float fw = -2.0f * far * near / (far - near); + + // Compute the projection matrix + return Matrix4(fx, 0, 0, 0, + 0, fy, 0, 0, + 0, 0, fz, fw, + 0, 0, -1, 0); +} + +// Return a 4x4 orthographic projection matrix +inline Matrix4 Matrix4::orthoProjectionMatrix(float near, float far, int width, int height) { + + // Compute the aspect ratio + float aspect = float(width) / float(height); + + float top = height * 0.5f; + float bottom = -top; + float left = bottom * aspect; + float right = top * aspect; + + float fx = 2.0f / (right - left); + float fy = 2.0f / (top - bottom); + float fz = -2.0f / (far - near); + float fw = -(far + near) / (far - near); + + // Compute the projection matrix + return Matrix4(fx, 0, 0, 0, + 0, fy, 0, 0, + 0, 0, fz, fw, + 0, 0, 0, 1); +} + +// Return the identity matrix +inline Matrix4 Matrix4::identity() { + return Matrix4(1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); +} + } #endif //_MATRIX4_H diff --git a/examples/common/opengl-framework/src/maths/Vector2.h b/testbed/opengl-framework/src/maths/Vector2.h similarity index 100% rename from examples/common/opengl-framework/src/maths/Vector2.h rename to testbed/opengl-framework/src/maths/Vector2.h diff --git a/examples/common/opengl-framework/src/maths/Vector3.h b/testbed/opengl-framework/src/maths/Vector3.h similarity index 100% rename from examples/common/opengl-framework/src/maths/Vector3.h rename to testbed/opengl-framework/src/maths/Vector3.h diff --git a/examples/common/opengl-framework/src/maths/Vector4.h b/testbed/opengl-framework/src/maths/Vector4.h similarity index 100% rename from examples/common/opengl-framework/src/maths/Vector4.h rename to testbed/opengl-framework/src/maths/Vector4.h diff --git a/examples/common/opengl-framework/src/openglframework.h b/testbed/opengl-framework/src/openglframework.h similarity index 98% rename from examples/common/opengl-framework/src/openglframework.h rename to testbed/opengl-framework/src/openglframework.h index 4ac735d8..f9d1fd97 100644 --- a/examples/common/opengl-framework/src/openglframework.h +++ b/testbed/opengl-framework/src/openglframework.h @@ -36,6 +36,7 @@ #include "Texture2D.h" #include "FrameBufferObject.h" #include "VertexBufferObject.h" +#include "VertexArrayObject.h" #include "Shader.h" #include "maths/Color.h" #include "maths/Vector2.h" diff --git a/examples/collisionshapes/Scene.cpp b/testbed/scenes/collisionshapes/CollisionShapesScene.cpp similarity index 53% rename from examples/collisionshapes/Scene.cpp rename to testbed/scenes/collisionshapes/CollisionShapesScene.cpp index 740a48e6..b63cb03c 100644 --- a/examples/collisionshapes/Scene.cpp +++ b/testbed/scenes/collisionshapes/CollisionShapesScene.cpp @@ -24,26 +24,23 @@ ********************************************************************************/ // Libraries -#include "Scene.h" +#include "CollisionShapesScene.h" // Namespaces using namespace openglframework; +using namespace collisionshapesscene; // Constructor -Scene::Scene(Viewer* viewer, const std::string& shaderFolderPath, const std::string& meshFolderPath) - : mViewer(viewer), mLight0(0), - mPhongShader(shaderFolderPath + "phong.vert", - shaderFolderPath +"phong.frag"), mIsRunning(false) { +CollisionShapesScene::CollisionShapesScene(const std::string& name) + : SceneDemo(name, SCENE_RADIUS) { - // Move the light 0 - mLight0.translateWorld(Vector3(50, 50, 50)); + std::string meshFolderPath("meshes/"); // Compute the radius and the center of the scene - float radiusScene = 30.0f; openglframework::Vector3 center(0, 5, 0); // Set the center of the scene - mViewer->setScenePosition(center, radiusScene); + setScenePosition(center, SCENE_RADIUS); // Gravity vector in the dynamics world rp3d::Vector3 gravity(0, -9.81, 0); @@ -52,14 +49,11 @@ Scene::Scene(Viewer* viewer, const std::string& shaderFolderPath, const std::str rp3d::decimal timeStep = 1.0f / 60.0f; // Create the dynamics world for the physics simulation - mDynamicsWorld = new rp3d::DynamicsWorld(gravity, timeStep); + mDynamicsWorld = new rp3d::DynamicsWorld(gravity); // Set the number of iterations of the constraint solver mDynamicsWorld->setNbIterationsVelocitySolver(15); - // Create the static data for the visual contact points - VisualContactPoint::createStaticData(meshFolderPath); - float radius = 3.0f; for (int i=0; isetColor(mDemoColors[i % mNbDemoColors]); + dumbbell->setSleepingColor(mRedColorDemo); + // Change the material properties of the rigid body rp3d::Material& material = dumbbell->getRigidBody()->getMaterial(); material.setBounciness(rp3d::decimal(0.2)); @@ -93,6 +91,10 @@ Scene::Scene(Viewer* viewer, const std::string& shaderFolderPath, const std::str // Create a sphere and a corresponding rigid in the dynamics world Box* box = new Box(BOX_SIZE, position , BOX_MASS, mDynamicsWorld); + // Set the box color + box->setColor(mDemoColors[i % mNbDemoColors]); + box->setSleepingColor(mRedColorDemo); + // Change the material properties of the rigid body rp3d::Material& material = box->getRigidBody()->getMaterial(); material.setBounciness(rp3d::decimal(0.2)); @@ -114,6 +116,10 @@ Scene::Scene(Viewer* viewer, const std::string& shaderFolderPath, const std::str Sphere* sphere = new Sphere(SPHERE_RADIUS, position , BOX_MASS, mDynamicsWorld, meshFolderPath); + // Set the box color + sphere->setColor(mDemoColors[i % mNbDemoColors]); + sphere->setSleepingColor(mRedColorDemo); + // Change the material properties of the rigid body rp3d::Material& material = sphere->getRigidBody()->getMaterial(); material.setBounciness(rp3d::decimal(0.2)); @@ -135,6 +141,10 @@ Scene::Scene(Viewer* viewer, const std::string& shaderFolderPath, const std::str Cone* cone = new Cone(CONE_RADIUS, CONE_HEIGHT, position, CONE_MASS, mDynamicsWorld, meshFolderPath); + // Set the box color + cone->setColor(mDemoColors[i % mNbDemoColors]); + cone->setSleepingColor(mRedColorDemo); + // Change the material properties of the rigid body rp3d::Material& material = cone->getRigidBody()->getMaterial(); material.setBounciness(rp3d::decimal(0.2)); @@ -156,6 +166,10 @@ Scene::Scene(Viewer* viewer, const std::string& shaderFolderPath, const std::str Cylinder* cylinder = new Cylinder(CYLINDER_RADIUS, CYLINDER_HEIGHT, position , CYLINDER_MASS, mDynamicsWorld, meshFolderPath); + // Set the box color + cylinder->setColor(mDemoColors[i % mNbDemoColors]); + cylinder->setSleepingColor(mRedColorDemo); + // Change the material properties of the rigid body rp3d::Material& material = cylinder->getRigidBody()->getMaterial(); material.setBounciness(rp3d::decimal(0.2)); @@ -177,6 +191,10 @@ Scene::Scene(Viewer* viewer, const std::string& shaderFolderPath, const std::str Capsule* capsule = new Capsule(CAPSULE_RADIUS, CAPSULE_HEIGHT, position , CAPSULE_MASS, mDynamicsWorld, meshFolderPath); + // Set the box color + capsule->setColor(mDemoColors[i % mNbDemoColors]); + capsule->setSleepingColor(mRedColorDemo); + // Change the material properties of the rigid body rp3d::Material& material = capsule->getRigidBody()->getMaterial(); material.setBounciness(rp3d::decimal(0.2)); @@ -197,6 +215,10 @@ Scene::Scene(Viewer* viewer, const std::string& shaderFolderPath, const std::str // Create a convex mesh and a corresponding rigid in the dynamics world ConvexMesh* mesh = new ConvexMesh(position, MESH_MASS, mDynamicsWorld, meshFolderPath); + // Set the box color + mesh->setColor(mDemoColors[i % mNbDemoColors]); + mesh->setSleepingColor(mRedColorDemo); + // Change the material properties of the rigid body rp3d::Material& material = mesh->getRigidBody()->getMaterial(); material.setBounciness(rp3d::decimal(0.2)); @@ -209,6 +231,10 @@ Scene::Scene(Viewer* viewer, const std::string& shaderFolderPath, const std::str openglframework::Vector3 floorPosition(0, 0, 0); mFloor = new Box(FLOOR_SIZE, floorPosition, FLOOR_MASS, mDynamicsWorld); + // Set the box color + mFloor->setColor(mGreyColorDemo); + mFloor->setSleepingColor(mGreyColorDemo); + // The floor must be a static rigid body mFloor->getRigidBody()->setType(rp3d::STATIC); @@ -216,18 +242,20 @@ Scene::Scene(Viewer* viewer, const std::string& shaderFolderPath, const std::str rp3d::Material& material = mFloor->getRigidBody()->getMaterial(); material.setBounciness(rp3d::decimal(0.2)); - // Start the simulation - startSimulation(); + // Get the physics engine parameters + mEngineSettings.isGravityEnabled = mDynamicsWorld->isGravityEnabled(); + rp3d::Vector3 gravityVector = mDynamicsWorld->getGravity(); + mEngineSettings.gravity = openglframework::Vector3(gravityVector.x, gravityVector.y, gravityVector.z); + mEngineSettings.isSleepingEnabled = mDynamicsWorld->isSleepingEnabled(); + mEngineSettings.sleepLinearVelocity = mDynamicsWorld->getSleepLinearVelocity(); + mEngineSettings.sleepAngularVelocity = mDynamicsWorld->getSleepAngularVelocity(); + mEngineSettings.nbPositionSolverIterations = mDynamicsWorld->getNbIterationsPositionSolver(); + mEngineSettings.nbVelocitySolverIterations = mDynamicsWorld->getNbIterationsVelocitySolver(); + mEngineSettings.timeBeforeSleep = mDynamicsWorld->getTimeBeforeSleep(); } // Destructor -Scene::~Scene() { - - // Stop the physics simulation - stopSimulation(); - - // Destroy the shader - mPhongShader.destroy(); +CollisionShapesScene::~CollisionShapesScene() { // Destroy all the boxes of the scene for (std::vector::iterator it = mBoxes.begin(); it != mBoxes.end(); ++it) { @@ -311,134 +339,262 @@ Scene::~Scene() { delete mDynamicsWorld; } +// Update the physics world (take a simulation step) +void CollisionShapesScene::updatePhysics() { + + // Update the physics engine parameters + mDynamicsWorld->setIsGratityEnabled(mEngineSettings.isGravityEnabled); + rp3d::Vector3 gravity(mEngineSettings.gravity.x, mEngineSettings.gravity.y, + mEngineSettings.gravity.z); + mDynamicsWorld->setGravity(gravity); + mDynamicsWorld->enableSleeping(mEngineSettings.isSleepingEnabled); + mDynamicsWorld->setSleepLinearVelocity(mEngineSettings.sleepLinearVelocity); + mDynamicsWorld->setSleepAngularVelocity(mEngineSettings.sleepAngularVelocity); + mDynamicsWorld->setNbIterationsPositionSolver(mEngineSettings.nbPositionSolverIterations); + mDynamicsWorld->setNbIterationsVelocitySolver(mEngineSettings.nbVelocitySolverIterations); + mDynamicsWorld->setTimeBeforeSleep(mEngineSettings.timeBeforeSleep); + + // Take a simulation step + mDynamicsWorld->update(mEngineSettings.timeStep); +} + // Take a step for the simulation -void Scene::simulate() { +void CollisionShapesScene::update() { - // If the physics simulation is running - if (mIsRunning) { + SceneDemo::update(); - // Take a simulation step - mDynamicsWorld->update(); + // Update the position and orientation of the boxes + for (std::vector::iterator it = mBoxes.begin(); it != mBoxes.end(); ++it) { - // Update the position and orientation of the boxes - for (std::vector::iterator it = mBoxes.begin(); it != mBoxes.end(); ++it) { - - // Update the transform used for the rendering - (*it)->updateTransform(); - } - - // Update the position and orientation of the sphere - for (std::vector::iterator it = mSpheres.begin(); it != mSpheres.end(); ++it) { - - // Update the transform used for the rendering - (*it)->updateTransform(); - } - - // Update the position and orientation of the cones - for (std::vector::iterator it = mCones.begin(); it != mCones.end(); ++it) { - - // Update the transform used for the rendering - (*it)->updateTransform(); - } - - // Update the position and orientation of the cylinders - for (std::vector::iterator it = mCylinders.begin(); it != mCylinders.end(); ++it) { - - // Update the transform used for the rendering - (*it)->updateTransform(); - } - - // Update the position and orientation of the capsules - for (std::vector::iterator it = mCapsules.begin(); it != mCapsules.end(); ++it) { - - // Update the transform used for the rendering - (*it)->updateTransform(); - } - - // Update the position and orientation of the convex meshes - for (std::vector::iterator it = mConvexMeshes.begin(); - it != mConvexMeshes.end(); ++it) { - - // Update the transform used for the rendering - (*it)->updateTransform(); - } - - // Update the position and orientation of the dumbbells - for (std::vector::iterator it = mDumbbells.begin(); - it != mDumbbells.end(); ++it) { - - // Update the transform used for the rendering - (*it)->updateTransform(); - } - - mFloor->updateTransform(); + // Update the transform used for the rendering + (*it)->updateTransform(mInterpolationFactor); } + + // Update the position and orientation of the sphere + for (std::vector::iterator it = mSpheres.begin(); it != mSpheres.end(); ++it) { + + // Update the transform used for the rendering + (*it)->updateTransform(mInterpolationFactor); + } + + // Update the position and orientation of the cones + for (std::vector::iterator it = mCones.begin(); it != mCones.end(); ++it) { + + // Update the transform used for the rendering + (*it)->updateTransform(mInterpolationFactor); + } + + // Update the position and orientation of the cylinders + for (std::vector::iterator it = mCylinders.begin(); it != mCylinders.end(); ++it) { + + // Update the transform used for the rendering + (*it)->updateTransform(mInterpolationFactor); + } + + // Update the position and orientation of the capsules + for (std::vector::iterator it = mCapsules.begin(); it != mCapsules.end(); ++it) { + + // Update the transform used for the rendering + (*it)->updateTransform(mInterpolationFactor); + } + + // Update the position and orientation of the convex meshes + for (std::vector::iterator it = mConvexMeshes.begin(); + it != mConvexMeshes.end(); ++it) { + + // Update the transform used for the rendering + (*it)->updateTransform(mInterpolationFactor); + } + + // Update the position and orientation of the dumbbells + for (std::vector::iterator it = mDumbbells.begin(); + it != mDumbbells.end(); ++it) { + + // Update the transform used for the rendering + (*it)->updateTransform(mInterpolationFactor); + } + + mFloor->updateTransform(mInterpolationFactor); } // Render the scene -void Scene::render() { - - glEnable(GL_DEPTH_TEST); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glEnable(GL_CULL_FACE); - - // Get the world-space to camera-space matrix - const Camera& camera = mViewer->getCamera(); - const openglframework::Matrix4 worldToCameraMatrix = camera.getTransformMatrix().getInverse(); +void CollisionShapesScene::renderSinglePass(openglframework::Shader& shader, + const openglframework::Matrix4& worldToCameraMatrix) { // Bind the shader - mPhongShader.bind(); - - // Set the variables of the shader - mPhongShader.setMatrix4x4Uniform("projectionMatrix", camera.getProjectionMatrix()); - mPhongShader.setVector3Uniform("light0PosCameraSpace", worldToCameraMatrix * mLight0.getOrigin()); - mPhongShader.setVector3Uniform("lightAmbientColor", Vector3(0.3f, 0.3f, 0.3f)); - const Color& diffColLight0 = mLight0.getDiffuseColor(); - const Color& specColLight0 = mLight0.getSpecularColor(); - mPhongShader.setVector3Uniform("light0DiffuseColor", Vector3(diffColLight0.r, diffColLight0.g, diffColLight0.b)); - mPhongShader.setVector3Uniform("light0SpecularColor", Vector3(specColLight0.r, specColLight0.g, specColLight0.b)); - mPhongShader.setFloatUniform("shininess", 200.0f); + shader.bind(); // Render all the boxes of the scene for (std::vector::iterator it = mBoxes.begin(); it != mBoxes.end(); ++it) { - (*it)->render(mPhongShader, worldToCameraMatrix); + (*it)->render(shader, worldToCameraMatrix); } // Render all the sphere of the scene for (std::vector::iterator it = mSpheres.begin(); it != mSpheres.end(); ++it) { - (*it)->render(mPhongShader, worldToCameraMatrix); + (*it)->render(shader, worldToCameraMatrix); } // Render all the cones of the scene for (std::vector::iterator it = mCones.begin(); it != mCones.end(); ++it) { - (*it)->render(mPhongShader, worldToCameraMatrix); + (*it)->render(shader, worldToCameraMatrix); } // Render all the cylinders of the scene for (std::vector::iterator it = mCylinders.begin(); it != mCylinders.end(); ++it) { - (*it)->render(mPhongShader, worldToCameraMatrix); + (*it)->render(shader, worldToCameraMatrix); } // Render all the capsules of the scene for (std::vector::iterator it = mCapsules.begin(); it != mCapsules.end(); ++it) { - (*it)->render(mPhongShader, worldToCameraMatrix); + (*it)->render(shader, worldToCameraMatrix); } // Render all the convex meshes of the scene for (std::vector::iterator it = mConvexMeshes.begin(); it != mConvexMeshes.end(); ++it) { - (*it)->render(mPhongShader, worldToCameraMatrix); + (*it)->render(shader, worldToCameraMatrix); } // Render all the dumbbells of the scene for (std::vector::iterator it = mDumbbells.begin(); it != mDumbbells.end(); ++it) { - (*it)->render(mPhongShader, worldToCameraMatrix); + (*it)->render(shader, worldToCameraMatrix); } // Render the floor - mFloor->render(mPhongShader, worldToCameraMatrix); + mFloor->render(shader, worldToCameraMatrix); // Unbind the shader - mPhongShader.unbind(); + shader.unbind(); +} + +/// Reset the scene +void CollisionShapesScene::reset() { + + float radius = 3.0f; + + for (int i=0; iresetTransform(transform); + } + + // Create all the boxes of the scene + for (int i=0; iresetTransform(transform); + } + + // Create all the spheres of the scene + for (int i=0; iresetTransform(transform); + } + + // Create all the cones of the scene + for (int i=0; iresetTransform(transform); + } + + // Create all the cylinders of the scene + for (int i=0; iresetTransform(transform); + } + + // Create all the capsules of the scene + for (int i=0; iresetTransform(transform); + } + + // Create all the convex meshes of the scene + for (int i=0; iresetTransform(transform); + } } diff --git a/examples/collisionshapes/Scene.h b/testbed/scenes/collisionshapes/CollisionShapesScene.h similarity index 72% rename from examples/collisionshapes/Scene.h rename to testbed/scenes/collisionshapes/CollisionShapesScene.h index 61a2e7a7..d7f41726 100644 --- a/examples/collisionshapes/Scene.h +++ b/testbed/scenes/collisionshapes/CollisionShapesScene.h @@ -23,12 +23,13 @@ * * ********************************************************************************/ -#ifndef SCENE_H -#define SCENE_H +#ifndef COLLISION_SHAPES_SCENE_H +#define COLLISION_SHAPES_SCENE_H // Libraries #include "openglframework.h" #include "reactphysics3d.h" +#include "SceneDemo.h" #include "Sphere.h" #include "Box.h" #include "Cone.h" @@ -39,14 +40,18 @@ #include "VisualContactPoint.h" #include "../common/Viewer.h" +namespace collisionshapesscene { + + // Constants -const int NB_BOXES = 2; -const int NB_CUBES = 1; -const int NB_CONES = 3; -const int NB_CYLINDERS = 2; -const int NB_CAPSULES = 1; -const int NB_MESHES = 2; -const int NB_COMPOUND_SHAPES = 2; +const float SCENE_RADIUS = 30.0f; +const int NB_BOXES = 4; +const int NB_CUBES = 6; +const int NB_CONES = 6; +const int NB_CYLINDERS = 5; +const int NB_CAPSULES = 4; +const int NB_MESHES = 5; +const int NB_COMPOUND_SHAPES = 4; const openglframework::Vector3 BOX_SIZE(2, 2, 2); const float SPHERE_RADIUS = 1.5f; const float CONE_RADIUS = 2.0f; @@ -56,7 +61,7 @@ const float CYLINDER_HEIGHT = 5.0f; const float CAPSULE_RADIUS = 1.0f; const float CAPSULE_HEIGHT = 1.0f; const float DUMBBELL_HEIGHT = 1.0f; -const openglframework::Vector3 FLOOR_SIZE(20, 0.5f, 20); // Floor dimensions in meters +const openglframework::Vector3 FLOOR_SIZE(50, 0.5f, 50); // Floor dimensions in meters const float BOX_MASS = 1.0f; const float CONE_MASS = 1.0f; const float CYLINDER_MASS = 1.0f; @@ -64,22 +69,13 @@ const float CAPSULE_MASS = 1.0f; const float MESH_MASS = 1.0f; const float FLOOR_MASS = 100.0f; // Floor mass in kilograms -// Class Scene -class Scene { +// Class CollisionShapesScene +class CollisionShapesScene : public SceneDemo { private : // -------------------- Attributes -------------------- // - /// Pointer to the viewer - Viewer* mViewer; - - /// Light 0 - openglframework::Light mLight0; - - /// Phong shader - openglframework::Shader mPhongShader; - /// All the spheres of the scene std::vector mBoxes; @@ -103,56 +99,39 @@ class Scene { /// Dynamics world used for the physics simulation rp3d::DynamicsWorld* mDynamicsWorld; - /// True if the physics simulation is running - bool mIsRunning; - public: // -------------------- Methods -------------------- // /// Constructor - Scene(Viewer* viewer, const std::string& shaderFolderPath, - const std::string& meshFolderPath); + CollisionShapesScene(const std::string& name); /// Destructor - ~Scene(); + virtual ~CollisionShapesScene(); + + /// Update the physics world (take a simulation step) + /// Can be called several times per frame + virtual void updatePhysics(); /// Take a step for the simulation - void simulate(); + virtual void update(); - /// Stop the simulation - void stopSimulation(); + /// Render the scene in a single pass + virtual void renderSinglePass(openglframework::Shader& shader, + const openglframework::Matrix4& worldToCameraMatrix); - /// Start the simulation - void startSimulation(); + /// Reset the scene + virtual void reset(); - /// Pause or continue simulation - void pauseContinueSimulation(); - - /// Render the scene - void render(); + /// Return all the contact points of the scene + virtual std::vector getContactPoints() const; }; -// Stop the simulation -inline void Scene::stopSimulation() { - mDynamicsWorld->stop(); - mIsRunning = false; +// Return all the contact points of the scene +inline std::vector CollisionShapesScene::getContactPoints() const { + return computeContactPointsOfWorld(mDynamicsWorld); } -// Start the simulation -inline void Scene::startSimulation() { - mDynamicsWorld->start(); - mIsRunning = true; -} - -// Pause or continue simulation -inline void Scene::pauseContinueSimulation() { - if (mIsRunning) { - stopSimulation(); - } - else { - startSimulation(); - } } #endif diff --git a/examples/cubes/Scene.cpp b/testbed/scenes/cubes/CubesScene.cpp similarity index 55% rename from examples/cubes/Scene.cpp rename to testbed/scenes/cubes/CubesScene.cpp index 073ab02b..c4652972 100644 --- a/examples/cubes/Scene.cpp +++ b/testbed/scenes/cubes/CubesScene.cpp @@ -24,35 +24,30 @@ ********************************************************************************/ // Libraries -#include "Scene.h" +#include "CubesScene.h" // Namespaces using namespace openglframework; +using namespace cubesscene; // Constructor -Scene::Scene(Viewer* viewer, const std::string& shaderFolderPath) - : mViewer(viewer), mLight0(0), - mPhongShader(shaderFolderPath + "phong.vert", shaderFolderPath + "phong.frag"), - mIsRunning(false) { - - // Move the light 0 - mLight0.translateWorld(Vector3(7, 15, 15)); +CubesScene::CubesScene(const std::string& name) + : SceneDemo(name, SCENE_RADIUS) { // Compute the radius and the center of the scene - float radiusScene = 30.0f; openglframework::Vector3 center(0, 5, 0); // Set the center of the scene - mViewer->setScenePosition(center, radiusScene); + setScenePosition(center, SCENE_RADIUS); // Gravity vector in the dynamics world - rp3d::Vector3 gravity(0, rp3d::decimal(-5.81), 0); + rp3d::Vector3 gravity(0, rp3d::decimal(-9.81), 0); // Time step for the physics simulation rp3d::decimal timeStep = 1.0f / 60.0f; // Create the dynamics world for the physics simulation - mDynamicsWorld = new rp3d::DynamicsWorld(gravity, timeStep); + mDynamicsWorld = new rp3d::DynamicsWorld(gravity); // Set the number of iterations of the constraint solver mDynamicsWorld->setNbIterationsVelocitySolver(15); @@ -65,12 +60,16 @@ Scene::Scene(Viewer* viewer, const std::string& shaderFolderPath) // Position of the cubes float angle = i * 30.0f; openglframework::Vector3 position(radius * cos(angle), - 10 + i * (BOX_SIZE.y + 0.3f), + 30 + i * (BOX_SIZE.y + 0.3f), 0); // Create a cube and a corresponding rigid in the dynamics world Box* cube = new Box(BOX_SIZE, position , BOX_MASS, mDynamicsWorld); + // Set the box color + cube->setColor(mDemoColors[i % mNbDemoColors]); + cube->setSleepingColor(mRedColorDemo); + // Change the material properties of the rigid body rp3d::Material& material = cube->getRigidBody()->getMaterial(); material.setBounciness(rp3d::decimal(0.4)); @@ -82,6 +81,8 @@ Scene::Scene(Viewer* viewer, const std::string& shaderFolderPath) // Create the floor openglframework::Vector3 floorPosition(0, 0, 0); mFloor = new Box(FLOOR_SIZE, floorPosition, FLOOR_MASS, mDynamicsWorld); + mFloor->setColor(mGreyColorDemo); + mFloor->setSleepingColor(mGreyColorDemo); // The floor must be a static rigid body mFloor->getRigidBody()->setType(rp3d::STATIC); @@ -90,20 +91,20 @@ Scene::Scene(Viewer* viewer, const std::string& shaderFolderPath) rp3d::Material& material = mFloor->getRigidBody()->getMaterial(); material.setBounciness(rp3d::decimal(0.3)); - // Start the simulation - startSimulation(); - - counter=0; + // Get the physics engine parameters + mEngineSettings.isGravityEnabled = mDynamicsWorld->isGravityEnabled(); + rp3d::Vector3 gravityVector = mDynamicsWorld->getGravity(); + mEngineSettings.gravity = openglframework::Vector3(gravityVector.x, gravityVector.y, gravityVector.z); + mEngineSettings.isSleepingEnabled = mDynamicsWorld->isSleepingEnabled(); + mEngineSettings.sleepLinearVelocity = mDynamicsWorld->getSleepLinearVelocity(); + mEngineSettings.sleepAngularVelocity = mDynamicsWorld->getSleepAngularVelocity(); + mEngineSettings.nbPositionSolverIterations = mDynamicsWorld->getNbIterationsPositionSolver(); + mEngineSettings.nbVelocitySolverIterations = mDynamicsWorld->getNbIterationsVelocitySolver(); + mEngineSettings.timeBeforeSleep = mDynamicsWorld->getTimeBeforeSleep(); } // Destructor -Scene::~Scene() { - - // Stop the physics simulation - stopSimulation(); - - // Destroy the shader - mPhongShader.destroy(); +CubesScene::~CubesScene() { // Destroy all the cubes of the scene for (std::vector::iterator it = mBoxes.begin(); it != mBoxes.end(); ++it) { @@ -125,73 +126,76 @@ Scene::~Scene() { delete mDynamicsWorld; } -// Take a step for the simulation -void Scene::simulate() { +// Update the physics world (take a simulation step) +void CubesScene::updatePhysics() { - // If the physics simulation is running - if (mIsRunning) { + // Update the physics engine parameters + mDynamicsWorld->setIsGratityEnabled(mEngineSettings.isGravityEnabled); + rp3d::Vector3 gravity(mEngineSettings.gravity.x, mEngineSettings.gravity.y, + mEngineSettings.gravity.z); + mDynamicsWorld->setGravity(gravity); + mDynamicsWorld->enableSleeping(mEngineSettings.isSleepingEnabled); + mDynamicsWorld->setSleepLinearVelocity(mEngineSettings.sleepLinearVelocity); + mDynamicsWorld->setSleepAngularVelocity(mEngineSettings.sleepAngularVelocity); + mDynamicsWorld->setNbIterationsPositionSolver(mEngineSettings.nbPositionSolverIterations); + mDynamicsWorld->setNbIterationsVelocitySolver(mEngineSettings.nbVelocitySolverIterations); + mDynamicsWorld->setTimeBeforeSleep(mEngineSettings.timeBeforeSleep); - counter++; - if (counter == 400) { - //mIsRunning = false; - } - - // Take a simulation step - mDynamicsWorld->update(); - - // Update the position and orientation of the boxes - for (std::vector::iterator it = mBoxes.begin(); it != mBoxes.end(); ++it) { - - // Update the transform used for the rendering - (*it)->updateTransform(); - } - - mFloor->updateTransform(); - - // Set the color of the awake/sleeping bodies - for (uint i=0; igetRigidBody()->isSleeping()) { - mBoxes[i]->setColor(Color(1, 0, 0, 1)); - } - else { - mBoxes[i]->setColor(Color(0, 1, 0, 1)); - } - } - } + // Take a simulation step + mDynamicsWorld->update(mEngineSettings.timeStep); } -// Render the scene -void Scene::render() { +// Update the scene +void CubesScene::update() { - glEnable(GL_DEPTH_TEST); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glEnable(GL_CULL_FACE); + SceneDemo::update(); - // Get the world-space to camera-space matrix - const Camera& camera = mViewer->getCamera(); - const openglframework::Matrix4 worldToCameraMatrix = camera.getTransformMatrix().getInverse(); + // Update the position and orientation of the boxes + for (std::vector::iterator it = mBoxes.begin(); it != mBoxes.end(); ++it) { + + // Update the transform used for the rendering + (*it)->updateTransform(mInterpolationFactor); + } + + mFloor->updateTransform(mInterpolationFactor); +} + +// Render the scene in a single pass +void CubesScene::renderSinglePass(Shader& shader, const openglframework::Matrix4& worldToCameraMatrix) { // Bind the shader - mPhongShader.bind(); - - // Set the variables of the shader - mPhongShader.setMatrix4x4Uniform("projectionMatrix", camera.getProjectionMatrix()); - mPhongShader.setVector3Uniform("light0PosCameraSpace",worldToCameraMatrix * mLight0.getOrigin()); - mPhongShader.setVector3Uniform("lightAmbientColor", Vector3(0.3f, 0.3f, 0.3f)); - const Color& diffCol = mLight0.getDiffuseColor(); - const Color& specCol = mLight0.getSpecularColor(); - mPhongShader.setVector3Uniform("light0DiffuseColor", Vector3(diffCol.r, diffCol.g, diffCol.b)); - mPhongShader.setVector3Uniform("light0SpecularColor", Vector3(specCol.r, specCol.g, specCol.b)); - mPhongShader.setFloatUniform("shininess", 60.0f); + shader.bind(); // Render all the cubes of the scene for (std::vector::iterator it = mBoxes.begin(); it != mBoxes.end(); ++it) { - (*it)->render(mPhongShader, worldToCameraMatrix); + (*it)->render(shader, worldToCameraMatrix); } // Render the floor - mFloor->render(mPhongShader, worldToCameraMatrix); + mFloor->render(shader, worldToCameraMatrix); // Unbind the shader - mPhongShader.unbind(); + shader.unbind(); +} + +// Reset the scene +void CubesScene::reset() { + + float radius = 2.0f; + + for (int i=0; iresetTransform(transform); + } } diff --git a/examples/cubes/Scene.h b/testbed/scenes/cubes/CubesScene.h similarity index 68% rename from examples/cubes/Scene.h rename to testbed/scenes/cubes/CubesScene.h index 2f79daed..57dfb99f 100644 --- a/examples/cubes/Scene.h +++ b/testbed/scenes/cubes/CubesScene.h @@ -23,40 +23,32 @@ * * ********************************************************************************/ -#ifndef SCENE_H -#define SCENE_H +#ifndef CUBES_SCENE_H +#define CUBES_SCENE_H // Libraries #include "openglframework.h" #include "reactphysics3d.h" #include "Box.h" -#include "Viewer.h" +#include "SceneDemo.h" + +namespace cubesscene { // Constants -const int NB_CUBES = 200; // Number of boxes in the scene +const float SCENE_RADIUS = 30.0f; // Radius of the scene in meters +const int NB_CUBES = 30; // Number of boxes in the scene const openglframework::Vector3 BOX_SIZE(2, 2, 2); // Box dimensions in meters const openglframework::Vector3 FLOOR_SIZE(50, 0.5f, 50); // Floor dimensions in meters const float BOX_MASS = 1.0f; // Box mass in kilograms const float FLOOR_MASS = 100.0f; // Floor mass in kilograms -// Class Scene -class Scene { +// Class CubesScene +class CubesScene : public SceneDemo { - private : + protected : // -------------------- Attributes -------------------- // - int counter; - - /// Pointer to the viewer - Viewer* mViewer; - - /// Light 0 - openglframework::Light mLight0; - - /// Phong shader - openglframework::Shader mPhongShader; - /// All the boxes of the scene std::vector mBoxes; @@ -66,55 +58,39 @@ class Scene { /// Dynamics world used for the physics simulation rp3d::DynamicsWorld* mDynamicsWorld; - /// True if the physics simulation is running - bool mIsRunning; - public: // -------------------- Methods -------------------- // /// Constructor - Scene(Viewer* viewer, const std::string& shaderFolderPath); + CubesScene(const std::string& name); /// Destructor - ~Scene(); + virtual ~CubesScene(); - /// Take a step for the simulation - void simulate(); + /// Update the physics world (take a simulation step) + /// Can be called several times per frame + virtual void updatePhysics(); - /// Stop the simulation - void stopSimulation(); + /// Update the scene (take a simulation step) + virtual void update(); - /// Start the simulation - void startSimulation(); + /// Render the scene in a single pass + virtual void renderSinglePass(openglframework::Shader& shader, + const openglframework::Matrix4& worldToCameraMatrix); - /// Pause or continue simulation - void pauseContinueSimulation(); + /// Reset the scene + virtual void reset(); - /// Render the scene - void render(); + /// Return all the contact points of the scene + virtual std::vector getContactPoints() const; }; -// Stop the simulation -inline void Scene::stopSimulation() { - mDynamicsWorld->stop(); - mIsRunning = false; +// Return all the contact points of the scene +inline std::vector CubesScene::getContactPoints() const { + return computeContactPointsOfWorld(mDynamicsWorld); } -// Start the simulation -inline void Scene::startSimulation() { - mDynamicsWorld->start(); - mIsRunning = true; -} - -// Pause or continue simulation -inline void Scene::pauseContinueSimulation() { - if (mIsRunning) { - stopSimulation(); - } - else { - startSimulation(); - } } #endif diff --git a/examples/joints/Scene.cpp b/testbed/scenes/joints/JointsScene.cpp similarity index 62% rename from examples/joints/Scene.cpp rename to testbed/scenes/joints/JointsScene.cpp index def294fa..dd3255d4 100644 --- a/examples/joints/Scene.cpp +++ b/testbed/scenes/joints/JointsScene.cpp @@ -24,27 +24,22 @@ ********************************************************************************/ // Libraries -#include "Scene.h" +#include "JointsScene.h" #include // Namespaces using namespace openglframework; +using namespace jointsscene; // Constructor -Scene::Scene(Viewer *viewer, const std::string& shaderFolderPath) - : mViewer(viewer), mLight0(0), mPhongShader(shaderFolderPath + "phong.vert", - shaderFolderPath + "phong.frag"), - mIsRunning(false) { - - // Move the light 0 - mLight0.translateWorld(Vector3(7, 15, 15)); +JointsScene::JointsScene(const std::string& name) + : SceneDemo(name, SCENE_RADIUS) { // Compute the radius and the center of the scene - float radiusScene = 30.0f; openglframework::Vector3 center(0, 5, 0); // Set the center of the scene - mViewer->setScenePosition(center, radiusScene); + setScenePosition(center, SCENE_RADIUS); // Gravity vector in the dynamics world rp3d::Vector3 gravity(0, rp3d::decimal(-9.81), 0); @@ -53,7 +48,7 @@ Scene::Scene(Viewer *viewer, const std::string& shaderFolderPath) rp3d::decimal timeStep = 1.0f / 60.0f; // Create the dynamics world for the physics simulation - mDynamicsWorld = new rp3d::DynamicsWorld(gravity, timeStep); + mDynamicsWorld = new rp3d::DynamicsWorld(gravity); // Set the number of iterations of the constraint solver mDynamicsWorld->setNbIterationsVelocitySolver(15); @@ -73,18 +68,20 @@ Scene::Scene(Viewer *viewer, const std::string& shaderFolderPath) // Create the floor createFloor(); - // Start the simulation - startSimulation(); + // Get the physics engine parameters + mEngineSettings.isGravityEnabled = mDynamicsWorld->isGravityEnabled(); + rp3d::Vector3 gravityVector = mDynamicsWorld->getGravity(); + mEngineSettings.gravity = openglframework::Vector3(gravityVector.x, gravityVector.y, gravityVector.z); + mEngineSettings.isSleepingEnabled = mDynamicsWorld->isSleepingEnabled(); + mEngineSettings.sleepLinearVelocity = mDynamicsWorld->getSleepLinearVelocity(); + mEngineSettings.sleepAngularVelocity = mDynamicsWorld->getSleepAngularVelocity(); + mEngineSettings.nbPositionSolverIterations = mDynamicsWorld->getNbIterationsPositionSolver(); + mEngineSettings.nbVelocitySolverIterations = mDynamicsWorld->getNbIterationsVelocitySolver(); + mEngineSettings.timeBeforeSleep = mDynamicsWorld->getTimeBeforeSleep(); } // Destructor -Scene::~Scene() { - - // Stop the physics simulation - stopSimulation(); - - // Destroy the shader - mPhongShader.destroy(); +JointsScene::~JointsScene() { // Destroy the joints mDynamicsWorld->destroyJoint(mSliderJoint); @@ -122,77 +119,145 @@ Scene::~Scene() { delete mDynamicsWorld; } +// Update the physics world (take a simulation step) +void JointsScene::updatePhysics() { + + // Update the physics engine parameters + mDynamicsWorld->setIsGratityEnabled(mEngineSettings.isGravityEnabled); + rp3d::Vector3 gravity(mEngineSettings.gravity.x, mEngineSettings.gravity.y, + mEngineSettings.gravity.z); + mDynamicsWorld->setGravity(gravity); + mDynamicsWorld->enableSleeping(mEngineSettings.isSleepingEnabled); + mDynamicsWorld->setSleepLinearVelocity(mEngineSettings.sleepLinearVelocity); + mDynamicsWorld->setSleepAngularVelocity(mEngineSettings.sleepAngularVelocity); + mDynamicsWorld->setNbIterationsPositionSolver(mEngineSettings.nbPositionSolverIterations); + mDynamicsWorld->setNbIterationsVelocitySolver(mEngineSettings.nbVelocitySolverIterations); + mDynamicsWorld->setTimeBeforeSleep(mEngineSettings.timeBeforeSleep); + + // Update the motor speed of the Slider Joint (to move up and down) + long double motorSpeed = 2 * cos(mEngineSettings.elapsedTime * 1.5); + mSliderJoint->setMotorSpeed(rp3d::decimal(motorSpeed)); + + // Take a simulation step + mDynamicsWorld->update(mEngineSettings.timeStep); +} + // Take a step for the simulation -void Scene::simulate() { +void JointsScene::update() { - // If the physics simulation is running - if (mIsRunning) { + SceneDemo::update(); - // Update the motor speed of the Slider Joint (to move up and down) - long double motorSpeed = 2 * cos(mDynamicsWorld->getPhysicsTime() * 1.5); - mSliderJoint->setMotorSpeed(rp3d::decimal(motorSpeed)); - - // Take a simulation step - mDynamicsWorld->update(); - - // Update the position and orientation of the boxes - mSliderJointBottomBox->updateTransform(); - mSliderJointTopBox->updateTransform(); - mPropellerBox->updateTransform(); - mFixedJointBox1->updateTransform(); - mFixedJointBox2->updateTransform(); - for (int i=0; iupdateTransform(); - } - - // Update the position and orientation of the floor - mFloor->updateTransform(); + // Update the position and orientation of the boxes + mSliderJointBottomBox->updateTransform(mInterpolationFactor); + mSliderJointTopBox->updateTransform(mInterpolationFactor); + mPropellerBox->updateTransform(mInterpolationFactor); + mFixedJointBox1->updateTransform(mInterpolationFactor); + mFixedJointBox2->updateTransform(mInterpolationFactor); + for (int i=0; iupdateTransform(mInterpolationFactor); } + + // Update the position and orientation of the floor + mFloor->updateTransform(mInterpolationFactor); } // Render the scene -void Scene::render() { - - glEnable(GL_DEPTH_TEST); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glEnable(GL_CULL_FACE); - - // Get the world-space to camera-space matrix - const Camera& camera = mViewer->getCamera(); - const openglframework::Matrix4 worldToCameraMatrix = camera.getTransformMatrix().getInverse(); +void JointsScene::renderSinglePass(openglframework::Shader& shader, + const openglframework::Matrix4& worldToCameraMatrix) { // Bind the shader - mPhongShader.bind(); - - // Set the variables of the shader - mPhongShader.setVector3Uniform("light0PosCameraSpace",worldToCameraMatrix * mLight0.getOrigin()); - mPhongShader.setMatrix4x4Uniform("projectionMatrix", camera.getProjectionMatrix()); - mPhongShader.setVector3Uniform("lightAmbientColor", Vector3(0.3f, 0.3f, 0.3f)); - const Color& diffCol = mLight0.getDiffuseColor(); - const Color& specCol = mLight0.getSpecularColor(); - mPhongShader.setVector3Uniform("light0DiffuseColor", Vector3(diffCol.r, diffCol.g, diffCol.b)); - mPhongShader.setVector3Uniform("light0SpecularColor", Vector3(specCol.r, specCol.g, specCol.b)); - mPhongShader.setFloatUniform("shininess", 60.0f); + shader.bind(); // Render all the boxes - mSliderJointBottomBox->render(mPhongShader, worldToCameraMatrix); - mSliderJointTopBox->render(mPhongShader, worldToCameraMatrix); - mPropellerBox->render(mPhongShader, worldToCameraMatrix); - mFixedJointBox1->render(mPhongShader, worldToCameraMatrix); - mFixedJointBox2->render(mPhongShader, worldToCameraMatrix); + mSliderJointBottomBox->render(shader, worldToCameraMatrix); + mSliderJointTopBox->render(shader, worldToCameraMatrix); + mPropellerBox->render(shader, worldToCameraMatrix); + mFixedJointBox1->render(shader, worldToCameraMatrix); + mFixedJointBox2->render(shader, worldToCameraMatrix); for (int i=0; irender(mPhongShader, worldToCameraMatrix); + mBallAndSocketJointChainBoxes[i]->render(shader, worldToCameraMatrix); } // Render the floor - mFloor->render(mPhongShader, worldToCameraMatrix); + mFloor->render(shader, worldToCameraMatrix); // Unbind the shader - mPhongShader.unbind(); + shader.unbind(); +} + +// Reset the scene +void JointsScene::reset() { + + openglframework::Vector3 positionBox(0, 15, 5); + openglframework::Vector3 boxDimension(1, 1, 1); + + for (int i=0; iresetTransform(transform); + + positionBox.y -= boxDimension.y + 0.5f; + } + + // --------------- Slider Joint --------------- // + + // Position of the box + openglframework::Vector3 positionBox1(0, 2.1f, 0); + rp3d::Vector3 initPosition(positionBox1.x, positionBox1.y, positionBox1.z); + rp3d::Quaternion initOrientation = rp3d::Quaternion::identity(); + rp3d::Transform transformBottomBox(initPosition, initOrientation); + + // Create a box and a corresponding rigid in the dynamics world + mSliderJointBottomBox->resetTransform(transformBottomBox); + + // Position of the box + openglframework::Vector3 positionBox2(0, 4.2f, 0); + initPosition = rp3d::Vector3(positionBox2.x, positionBox2.y, positionBox2.z); + initOrientation = rp3d::Quaternion::identity(); + rp3d::Transform transformTopBox(initPosition, initOrientation); + + // Create a box and a corresponding rigid in the dynamics world + mSliderJointTopBox->resetTransform(transformTopBox); + + // --------------- Propeller Hinge joint --------------- // + + // Position of the box + positionBox1 = openglframework::Vector3(0, 7, 0); + initPosition = rp3d::Vector3(positionBox1.x, positionBox1.y, positionBox1.z); + initOrientation = rp3d::Quaternion::identity(); + rp3d::Transform transformHingeBox(initPosition, initOrientation); + + // Create a box and a corresponding rigid in the dynamics world + mPropellerBox->resetTransform(transformHingeBox); + + // --------------- Fixed joint --------------- // + + // Position of the box + positionBox1 = openglframework::Vector3(5, 7, 0); + initPosition = rp3d::Vector3(positionBox1.x, positionBox1.y, positionBox1.z); + initOrientation = rp3d::Quaternion::identity(); + rp3d::Transform transformFixedBox1(initPosition, initOrientation); + + // Create a box and a corresponding rigid in the dynamics world + mFixedJointBox1->resetTransform(transformFixedBox1); + + // Position of the box + positionBox2 = openglframework::Vector3(-5, 7, 0); + initPosition = rp3d::Vector3(positionBox2.x, positionBox2.y, positionBox2.z); + initOrientation = rp3d::Quaternion::identity(); + rp3d::Transform transformFixedBox2(initPosition, initOrientation); + + // Create a box and a corresponding rigid in the dynamics world + mFixedJointBox2->resetTransform(transformFixedBox2); } // Create the boxes and joints for the Ball-and-Socket joint example -void Scene::createBallAndSocketJoints() { +void JointsScene::createBallAndSocketJoints() { // --------------- Create the boxes --------------- // @@ -206,6 +271,10 @@ void Scene::createBallAndSocketJoints() { mBallAndSocketJointChainBoxes[i] = new Box(boxDimension, positionBox , boxMass, mDynamicsWorld); + // Set the box color + mBallAndSocketJointChainBoxes[i]->setColor(mDemoColors[i % mNbDemoColors]); + mBallAndSocketJointChainBoxes[i]->setSleepingColor(mRedColorDemo); + // The fist box cannot move (static body) if (i == 0) { mBallAndSocketJointChainBoxes[i]->getRigidBody()->setType(rp3d::STATIC); @@ -240,7 +309,7 @@ void Scene::createBallAndSocketJoints() { } /// Create the boxes and joint for the Slider joint example -void Scene::createSliderJoint() { +void JointsScene::createSliderJoint() { // --------------- Create the first box --------------- // @@ -251,6 +320,10 @@ void Scene::createSliderJoint() { openglframework::Vector3 box1Dimension(2, 4, 2); mSliderJointBottomBox = new Box(box1Dimension, positionBox1 , BOX_MASS, mDynamicsWorld); + // Set the box color + mSliderJointBottomBox->setColor(mBlueColorDemo); + mSliderJointBottomBox->setSleepingColor(mRedColorDemo); + // The fist box cannot move mSliderJointBottomBox->getRigidBody()->setType(rp3d::STATIC); @@ -265,7 +338,11 @@ void Scene::createSliderJoint() { // Create a box and a corresponding rigid in the dynamics world openglframework::Vector3 box2Dimension(1.5f, 4, 1.5f); - mSliderJointTopBox = new Box(box2Dimension, positionBox2 , BOX_MASS, mDynamicsWorld); + mSliderJointTopBox = new Box(box2Dimension, positionBox2, BOX_MASS, mDynamicsWorld); + + // Set the box color + mSliderJointTopBox->setColor(mOrangeColorDemo); + mSliderJointTopBox->setSleepingColor(mRedColorDemo); // Change the material properties of the rigid body rp3d::Material& material2 = mSliderJointTopBox->getRigidBody()->getMaterial(); @@ -292,7 +369,7 @@ void Scene::createSliderJoint() { } /// Create the boxes and joint for the Hinge joint example -void Scene::createPropellerHingeJoint() { +void JointsScene::createPropellerHingeJoint() { // --------------- Create the propeller box --------------- // @@ -303,6 +380,10 @@ void Scene::createPropellerHingeJoint() { openglframework::Vector3 boxDimension(10, 1, 1); mPropellerBox = new Box(boxDimension, positionBox1 , BOX_MASS, mDynamicsWorld); + // Set the box color + mPropellerBox->setColor(mYellowColorDemo); + mPropellerBox->setSleepingColor(mRedColorDemo); + // Change the material properties of the rigid body rp3d::Material& material = mPropellerBox->getRigidBody()->getMaterial(); material.setBounciness(rp3d::decimal(0.4)); @@ -327,7 +408,7 @@ void Scene::createPropellerHingeJoint() { } /// Create the boxes and joints for the fixed joints -void Scene::createFixedJoints() { +void JointsScene::createFixedJoints() { // --------------- Create the first box --------------- // @@ -338,6 +419,10 @@ void Scene::createFixedJoints() { openglframework::Vector3 boxDimension(1.5, 1.5, 1.5); mFixedJointBox1 = new Box(boxDimension, positionBox1 , BOX_MASS, mDynamicsWorld); + // Set the box color + mFixedJointBox1->setColor(mPinkColorDemo); + mFixedJointBox1->setSleepingColor(mRedColorDemo); + // Change the material properties of the rigid body rp3d::Material& material1 = mFixedJointBox1->getRigidBody()->getMaterial(); material1.setBounciness(rp3d::decimal(0.4)); @@ -350,6 +435,10 @@ void Scene::createFixedJoints() { // Create a box and a corresponding rigid in the dynamics world mFixedJointBox2 = new Box(boxDimension, positionBox2 , BOX_MASS, mDynamicsWorld); + // Set the box color + mFixedJointBox2->setColor(mBlueColorDemo); + mFixedJointBox2->setSleepingColor(mRedColorDemo); + // Change the material properties of the rigid body rp3d::Material& material2 = mFixedJointBox2->getRigidBody()->getMaterial(); material2.setBounciness(rp3d::decimal(0.4)); @@ -379,12 +468,16 @@ void Scene::createFixedJoints() { } // Create the floor -void Scene::createFloor() { +void JointsScene::createFloor() { // Create the floor openglframework::Vector3 floorPosition(0, 0, 0); mFloor = new Box(FLOOR_SIZE, floorPosition, FLOOR_MASS, mDynamicsWorld); + // Set the box color + mFloor->setColor(mGreyColorDemo); + mFloor->setSleepingColor(mGreyColorDemo); + // The floor must be a static rigid body mFloor->getRigidBody()->setType(rp3d::STATIC); diff --git a/examples/joints/Scene.h b/testbed/scenes/joints/JointsScene.h similarity index 79% rename from examples/joints/Scene.h rename to testbed/scenes/joints/JointsScene.h index 4467f3af..39ceecb2 100644 --- a/examples/joints/Scene.h +++ b/testbed/scenes/joints/JointsScene.h @@ -23,39 +23,33 @@ * * ********************************************************************************/ -#ifndef SCENE_H -#define SCENE_H +#ifndef JOINTS_SCENE_H +#define JOINTS_SCENE_H // Libraries #include "openglframework.h" #include "reactphysics3d.h" #include "Box.h" -#include "../common/Viewer.h" +#include "SceneDemo.h" + +namespace jointsscene { // Constants +const float SCENE_RADIUS = 30.0f; const openglframework::Vector3 BOX_SIZE(2, 2, 2); // Box dimensions in meters -const openglframework::Vector3 FLOOR_SIZE(20, 0.5f, 20); // Floor dimensions in meters +const openglframework::Vector3 FLOOR_SIZE(50, 0.5f, 50); // Floor dimensions in meters const float BOX_MASS = 1.0f; // Box mass in kilograms const float FLOOR_MASS = 100.0f; // Floor mass in kilograms const int NB_BALLSOCKETJOINT_BOXES = 7; // Number of Ball-And-Socket chain boxes const int NB_HINGE_BOXES = 7; // Number of Hinge chain boxes -// Class Scene -class Scene { +// Class JointsScene +class JointsScene : public SceneDemo { - private : + protected : // -------------------- Attributes -------------------- // - /// Pointer to the viewer - Viewer* mViewer; - - /// Light 0 - openglframework::Light mLight0; - - /// Phong shader - openglframework::Shader mPhongShader; - /// Boxes of Ball-And-Socket joint chain Box* mBallAndSocketJointChainBoxes[NB_BALLSOCKETJOINT_BOXES]; @@ -101,9 +95,6 @@ class Scene { /// Dynamics world used for the physics simulation rp3d::DynamicsWorld* mDynamicsWorld; - /// True if the physics simulation is running - bool mIsRunning; - // -------------------- Methods -------------------- // /// Create the boxes and joints for the Ball-and-Socket joint example @@ -126,47 +117,34 @@ class Scene { // -------------------- Methods -------------------- // /// Constructor - Scene(Viewer* viewer, const std::string& shaderFolderPath); + JointsScene(const std::string& name); /// Destructor - ~Scene(); + virtual ~JointsScene(); + + /// Update the physics world (take a simulation step) + /// Can be called several times per frame + virtual void updatePhysics(); /// Take a step for the simulation - void simulate(); + virtual void update(); - /// Stop the simulation - void stopSimulation(); + /// Render the scene in a single pass + virtual void renderSinglePass(openglframework::Shader& shader, + const openglframework::Matrix4& worldToCameraMatrix); - /// Start the simulation - void startSimulation(); + /// Reset the scene + virtual void reset(); - /// Pause or continue simulation - void pauseContinueSimulation(); - - /// Render the scene - void render(); + /// Return all the contact points of the scene + virtual std::vector getContactPoints() const; }; -// Stop the simulation -inline void Scene::stopSimulation() { - mDynamicsWorld->stop(); - mIsRunning = false; +// Return all the contact points of the scene +inline std::vector JointsScene::getContactPoints() const { + return computeContactPointsOfWorld(mDynamicsWorld); } -// Start the simulation -inline void Scene::startSimulation() { - mDynamicsWorld->start(); - mIsRunning = true; -} - -// Pause or continue simulation -inline void Scene::pauseContinueSimulation() { - if (mIsRunning) { - stopSimulation(); - } - else { - startSimulation(); - } } #endif diff --git a/examples/raycast/Scene.cpp b/testbed/scenes/raycast/RaycastScene.cpp similarity index 63% rename from examples/raycast/Scene.cpp rename to testbed/scenes/raycast/RaycastScene.cpp index da6423a3..b2760012 100644 --- a/examples/raycast/Scene.cpp +++ b/testbed/scenes/raycast/RaycastScene.cpp @@ -24,38 +24,38 @@ ********************************************************************************/ // Libraries -#include "Scene.h" +#include "RaycastScene.h" // Namespaces using namespace openglframework; +using namespace raycastscene; // Constructor -Scene::Scene(Viewer* viewer, const std::string& shaderFolderPath, const std::string& meshFolderPath) - : mViewer(viewer), mLight0(0), mCurrentBodyIndex(-1), mAreNormalsDisplayed(false), - mPhongShader(shaderFolderPath + "phong.vert", - shaderFolderPath +"phong.frag") { +RaycastScene::RaycastScene(const std::string& name) + : SceneDemo(name, SCENE_RADIUS, false), mCurrentBodyIndex(-1), mAreNormalsDisplayed(false), + mMeshFolderPath("meshes/"), mRaycastManager(mPhongShader, mMeshFolderPath), + mVBOVertices(GL_ARRAY_BUFFER) { - // Move the light 0 - mLight0.translateWorld(Vector3(50, 50, 50)); + mIsContactPointsDisplayed = true; // Compute the radius and the center of the scene - float radiusScene = 30.0f; openglframework::Vector3 center(0, 0, 0); // Set the center of the scene - mViewer->setScenePosition(center, radiusScene); + setScenePosition(center, SCENE_RADIUS); // Create the dynamics world for the physics simulation mCollisionWorld = new rp3d::CollisionWorld(); - // Create the static data for the visual contact points - VisualContactPoint::createStaticData(meshFolderPath); - // ---------- Dumbbell ---------- // openglframework::Vector3 position1(0, 0, 0); // Create a convex mesh and a corresponding collision body in the dynamics world - mDumbbell = new Dumbbell(position1, mCollisionWorld, meshFolderPath); + mDumbbell = new Dumbbell(position1, mCollisionWorld, mMeshFolderPath); + + // Set the box color + mDumbbell->setColor(mGreyColorDemo); + mDumbbell->setSleepingColor(mRedColorDemo); // ---------- Box ---------- // openglframework::Vector3 position2(0, 0, 0); @@ -64,56 +64,83 @@ Scene::Scene(Viewer* viewer, const std::string& shaderFolderPath, const std::str mBox = new Box(BOX_SIZE, position2, mCollisionWorld); mBox->getCollisionBody()->setIsActive(false); + // Set the box color + mBox->setColor(mGreyColorDemo); + mBox->setSleepingColor(mRedColorDemo); + // ---------- Sphere ---------- // openglframework::Vector3 position3(0, 0, 0); // Create a sphere and a corresponding collision body in the dynamics world mSphere = new Sphere(SPHERE_RADIUS, position3, mCollisionWorld, - meshFolderPath); + mMeshFolderPath); + + // Set the color + mSphere->setColor(mGreyColorDemo); + mSphere->setSleepingColor(mRedColorDemo); // ---------- Cone ---------- // openglframework::Vector3 position4(0, 0, 0); // Create a cone and a corresponding collision body in the dynamics world mCone = new Cone(CONE_RADIUS, CONE_HEIGHT, position4, mCollisionWorld, - meshFolderPath); + mMeshFolderPath); + + // Set the color + mCone->setColor(mGreyColorDemo); + mCone->setSleepingColor(mRedColorDemo); // ---------- Cylinder ---------- // openglframework::Vector3 position5(0, 0, 0); // Create a cylinder and a corresponding collision body in the dynamics world mCylinder = new Cylinder(CYLINDER_RADIUS, CYLINDER_HEIGHT, position5, - mCollisionWorld, meshFolderPath); + mCollisionWorld, mMeshFolderPath); + + // Set the color + mCylinder->setColor(mGreyColorDemo); + mCylinder->setSleepingColor(mRedColorDemo); // ---------- Capsule ---------- // openglframework::Vector3 position6(0, 0, 0); // Create a cylinder and a corresponding collision body in the dynamics world mCapsule = new Capsule(CAPSULE_RADIUS, CAPSULE_HEIGHT, position6 , - mCollisionWorld, meshFolderPath); + mCollisionWorld, mMeshFolderPath); + + // Set the color + mCapsule->setColor(mGreyColorDemo); + mCapsule->setSleepingColor(mRedColorDemo); // ---------- Convex Mesh ---------- // openglframework::Vector3 position7(0, 0, 0); // Create a convex mesh and a corresponding collision body in the dynamics world - mConvexMesh = new ConvexMesh(position7, mCollisionWorld, meshFolderPath); + mConvexMesh = new ConvexMesh(position7, mCollisionWorld, mMeshFolderPath); + + // Set the color + mConvexMesh->setColor(mGreyColorDemo); + mConvexMesh->setSleepingColor(mRedColorDemo); // Create the lines that will be used for raycasting createLines(); + // Create the VBO and VAO to render the lines + createVBOAndVAO(mPhongShader); + changeBody(); } // Create the raycast lines -void Scene::createLines() { +void RaycastScene::createLines() { int nbRaysOneDimension = std::sqrt(float(NB_RAYS)); for (int i=0; i= NB_BODIES) mCurrentBodyIndex = 0; @@ -163,8 +193,13 @@ void Scene::changeBody() { } } +// Reset the scene +void RaycastScene::reset() { + +} + // Destructor -Scene::~Scene() { +RaycastScene::~RaycastScene() { // Destroy the shader mPhongShader.destroy(); @@ -218,10 +253,20 @@ Scene::~Scene() { ++it) { delete (*it); } + + // Destroy the VBOs and VAO + mVBOVertices.destroy(); + mVAO.destroy(); +} + +// Update the physics world (take a simulation step) +void RaycastScene::updatePhysics() { + + } // Take a step for the simulation -void Scene::simulate() { +void RaycastScene::update() { mRaycastManager.resetPoints(); @@ -243,65 +288,101 @@ void Scene::simulate() { // callback class in argument. mCollisionWorld->raycast(ray, &mRaycastManager); } + + SceneDemo::update(); } // Render the scene -void Scene::render() { +void RaycastScene::renderSinglePass(openglframework::Shader& shader, + const openglframework::Matrix4& worldToCameraMatrix) { - glEnable(GL_DEPTH_TEST); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glEnable(GL_CULL_FACE); - - // Get the world-space to camera-space matrix - const Camera& camera = mViewer->getCamera(); - const openglframework::Matrix4 worldToCameraMatrix = camera.getTransformMatrix().getInverse(); + // Bind the VAO + mVAO.bind(); // Bind the shader - mPhongShader.bind(); + shader.bind(); - openglframework::Vector4 grey(0.7, 0.7, 0.7, 1); - mPhongShader.setVector4Uniform("vertexColor", grey); + mVBOVertices.bind(); - // Set the variables of the shader - mPhongShader.setMatrix4x4Uniform("projectionMatrix", camera.getProjectionMatrix()); - mPhongShader.setVector3Uniform("light0PosCameraSpace", worldToCameraMatrix * mLight0.getOrigin()); - mPhongShader.setVector3Uniform("lightAmbientColor", Vector3(0.3f, 0.3f, 0.3f)); - const Color& diffColLight0 = mLight0.getDiffuseColor(); - const Color& specColLight0 = mLight0.getSpecularColor(); - mPhongShader.setVector3Uniform("light0DiffuseColor", Vector3(diffColLight0.r, diffColLight0.g, diffColLight0.b)); - mPhongShader.setVector3Uniform("light0SpecularColor", Vector3(specColLight0.r, specColLight0.g, specColLight0.b)); - mPhongShader.setFloatUniform("shininess", 200.0f); + // Set the model to camera matrix + const Matrix4 localToCameraMatrix = Matrix4::identity(); + shader.setMatrix4x4Uniform("localToWorldMatrix", localToCameraMatrix); + shader.setMatrix4x4Uniform("worldToCameraMatrix", worldToCameraMatrix); - if (mBox->getCollisionBody()->isActive()) mBox->render(mPhongShader, worldToCameraMatrix); - if (mSphere->getCollisionBody()->isActive()) mSphere->render(mPhongShader, worldToCameraMatrix); - if (mCone->getCollisionBody()->isActive()) mCone->render(mPhongShader, worldToCameraMatrix); - if (mCylinder->getCollisionBody()->isActive()) mCylinder->render(mPhongShader, worldToCameraMatrix); - if (mCapsule->getCollisionBody()->isActive()) mCapsule->render(mPhongShader, worldToCameraMatrix); - if (mConvexMesh->getCollisionBody()->isActive()) mConvexMesh->render(mPhongShader, worldToCameraMatrix); - if (mDumbbell->getCollisionBody()->isActive()) mDumbbell->render(mPhongShader, worldToCameraMatrix); + // Set the normal matrix (inverse transpose of the 3x3 upper-left sub matrix of the + // model-view matrix) + const openglframework::Matrix3 normalMatrix = + localToCameraMatrix.getUpperLeft3x3Matrix().getInverse().getTranspose(); + shader.setMatrix3x3Uniform("normalMatrix", normalMatrix, false); - mPhongShader.unbind(); - mPhongShader.bind(); + // Set the vertex color + openglframework::Vector4 color(1, 0, 0, 1); + shader.setVector4Uniform("vertexColor", color, false); - mPhongShader.setVector3Uniform("light0SpecularColor", Vector3(0, 0, 0)); - openglframework::Vector4 redColor(1, 0, 0, 1); - mPhongShader.setVector4Uniform("vertexColor", redColor); + // Get the location of shader attribute variables + GLint vertexPositionLoc = shader.getAttribLocation("vertexPosition"); - // Render all the raycast hit points - mRaycastManager.render(mPhongShader, worldToCameraMatrix, mAreNormalsDisplayed); + glEnableVertexAttribArray(vertexPositionLoc); + glVertexAttribPointer(vertexPositionLoc, 3, GL_FLOAT, GL_FALSE, 0, (char*)NULL); - mPhongShader.unbind(); - mPhongShader.bind(); + // Draw the lines + glDrawArrays(GL_LINES, 0, NB_RAYS); - openglframework::Vector4 blueColor(0, 0.62, 0.92, 1); - mPhongShader.setVector4Uniform("vertexColor", blueColor); + glDisableVertexAttribArray(vertexPositionLoc); - // Render the lines - for (std::vector::iterator it = mLines.begin(); it != mLines.end(); - ++it) { - (*it)->render(mPhongShader, worldToCameraMatrix); - } + mVBOVertices.unbind(); + + // Unbind the VAO + mVAO.unbind(); + + shader.unbind(); + + // Render the shapes + if (mBox->getCollisionBody()->isActive()) mBox->render(shader, worldToCameraMatrix); + if (mSphere->getCollisionBody()->isActive()) mSphere->render(shader, worldToCameraMatrix); + if (mCone->getCollisionBody()->isActive()) mCone->render(shader, worldToCameraMatrix); + if (mCylinder->getCollisionBody()->isActive()) mCylinder->render(shader, worldToCameraMatrix); + if (mCapsule->getCollisionBody()->isActive()) mCapsule->render(shader, worldToCameraMatrix); + if (mConvexMesh->getCollisionBody()->isActive()) mConvexMesh->render(shader, worldToCameraMatrix); + if (mDumbbell->getCollisionBody()->isActive()) mDumbbell->render(shader, worldToCameraMatrix); + + //mPhongShader.unbind(); + shader.unbind(); +} + +// Create the Vertex Buffer Objects used to render with OpenGL. +/// We create two VBOs (one for vertices and one for indices) +void RaycastScene::createVBOAndVAO(openglframework::Shader& shader) { + + // Bind the shader + shader.bind(); + + // Create the VBO for the vertices data + mVBOVertices.create(); + mVBOVertices.bind(); + size_t sizeVertices = mLinePoints.size() * sizeof(openglframework::Vector3); + mVBOVertices.copyDataIntoVBO(sizeVertices, &mLinePoints[0], GL_STATIC_DRAW); + mVBOVertices.unbind(); + + // Create the VAO for both VBOs + mVAO.create(); + mVAO.bind(); + + // Bind the VBO of vertices + mVBOVertices.bind(); + + // Unbind the VAO + mVAO.unbind(); // Unbind the shader - mPhongShader.unbind(); + shader.unbind(); +} + +// Called when a keyboard event occurs +void RaycastScene::keyboardEvent(int key, int scancode, int action, int mods) { + + // If the space key has been pressed + if (key == GLFW_KEY_SPACE && action == GLFW_PRESS) { + changeBody(); + } } diff --git a/examples/raycast/Scene.h b/testbed/scenes/raycast/RaycastScene.h similarity index 64% rename from examples/raycast/Scene.h rename to testbed/scenes/raycast/RaycastScene.h index 9b044e06..84cb0702 100644 --- a/examples/raycast/Scene.h +++ b/testbed/scenes/raycast/RaycastScene.h @@ -23,14 +23,15 @@ * * ********************************************************************************/ -#ifndef SCENE_H -#define SCENE_H +#ifndef RAYCAST_SCENE_H +#define RAYCAST_SCENE_H // Libraries #define _USE_MATH_DEFINES #include #include "openglframework.h" #include "reactphysics3d.h" +#include "SceneDemo.h" #include "Sphere.h" #include "Box.h" #include "Cone.h" @@ -42,7 +43,10 @@ #include "VisualContactPoint.h" #include "../common/Viewer.h" +namespace raycastscene { + // Constants +const float SCENE_RADIUS = 30.0f; const openglframework::Vector3 BOX_SIZE(4, 2, 1); const float SPHERE_RADIUS = 3.0f; const float CONE_RADIUS = 3.0f; @@ -62,18 +66,29 @@ class RaycastManager : public rp3d::RaycastCallback { private: /// All the visual contact points - std::vector mHitPoints; + std::vector mHitPoints; /// All the normals at hit points std::vector mNormals; + /// Shader + openglframework::Shader& mShader; + + /// Contact point mesh folder path + std::string mMeshFolderPath; + public: + RaycastManager(openglframework::Shader& shader, + const std::string& meshFolderPath) + : mShader(shader), mMeshFolderPath(meshFolderPath) { + + } + virtual rp3d::decimal notifyRaycastHit(const rp3d::RaycastInfo& raycastInfo) { rp3d::Vector3 hitPos = raycastInfo.worldPoint; openglframework::Vector3 position(hitPos.x, hitPos.y, hitPos.z); - VisualContactPoint* point = new VisualContactPoint(position); - mHitPoints.push_back(point); + mHitPoints.push_back(ContactPoint(position)); // Create a line to display the normal at hit point rp3d::Vector3 n = raycastInfo.worldNormal; @@ -84,33 +99,8 @@ class RaycastManager : public rp3d::RaycastCallback { return raycastInfo.hitFraction; } - void render(openglframework::Shader& shader, - const openglframework::Matrix4& worldToCameraMatrix, - bool showNormals) { - - // Render all the raycast hit points - for (std::vector::iterator it = mHitPoints.begin(); - it != mHitPoints.end(); ++it) { - (*it)->render(shader, worldToCameraMatrix); - } - - if (showNormals) { - - // Render all the normals at hit points - for (std::vector::iterator it = mNormals.begin(); - it != mNormals.end(); ++it) { - (*it)->render(shader, worldToCameraMatrix); - } - } - } - void resetPoints() { - // Destroy all the visual contact points - for (std::vector::iterator it = mHitPoints.begin(); - it != mHitPoints.end(); ++it) { - delete (*it); - } mHitPoints.clear(); // Destroy all the normals @@ -120,27 +110,25 @@ class RaycastManager : public rp3d::RaycastCallback { } mNormals.clear(); } + + std::vector getHitPoints() const { + return mHitPoints; + } }; -// Class Scene -class Scene { +// Class RaycastScene +class RaycastScene : public SceneDemo { private : // -------------------- Attributes -------------------- // - /// Pointer to the viewer - Viewer* mViewer; + /// Contact point mesh folder path + std::string mMeshFolderPath; /// Raycast manager RaycastManager mRaycastManager; - /// Light 0 - openglframework::Light mLight0; - - /// Phong shader - openglframework::Shader mPhongShader; - /// All the raycast lines std::vector mLines; @@ -164,37 +152,85 @@ class Scene { /// Collision world used for the physics simulation rp3d::CollisionWorld* mCollisionWorld; + /// All the points to render the lines + std::vector mLinePoints; + + /// Vertex Buffer Object for the vertices data + openglframework::VertexBufferObject mVBOVertices; + + /// Vertex Array Object for the vertex data + openglframework::VertexArrayObject mVAO; + /// Create the raycast lines void createLines(); + // Create the Vertex Buffer Objects used to render with OpenGL. + void createVBOAndVAO(openglframework::Shader& shader); + + public: // -------------------- Methods -------------------- // /// Constructor - Scene(Viewer* viewer, const std::string& shaderFolderPath, - const std::string& meshFolderPath); + RaycastScene(const std::string& name); /// Destructor - ~Scene(); + virtual ~RaycastScene(); + + /// Update the physics world (take a simulation step) + /// Can be called several times per frame + virtual void updatePhysics(); /// Take a step for the simulation - void simulate(); + virtual void update(); - /// Render the scene - void render(); + /// Render the scene in a single pass + virtual void renderSinglePass(openglframework::Shader& shader, + const openglframework::Matrix4& worldToCameraMatrix); + + /// Reset the scene + virtual void reset(); /// Change the body to raycast void changeBody(); /// Display or not the surface normals at hit points void showHideNormals(); + + /// Called when a keyboard event occurs + virtual void keyboardEvent(int key, int scancode, int action, int mods); + + /// Enabled/Disable the shadow mapping + void virtual setIsShadowMappingEnabled(bool isShadowMappingEnabled); + + /// Display/Hide the contact points + void virtual setIsContactPointsDisplayed(bool display); + + /// Return all the contact points of the scene + virtual std::vector getContactPoints() const; }; // Display or not the surface normals at hit points -inline void Scene::showHideNormals() { +inline void RaycastScene::showHideNormals() { mAreNormalsDisplayed = !mAreNormalsDisplayed; } +// Enabled/Disable the shadow mapping +inline void RaycastScene::setIsShadowMappingEnabled(bool isShadowMappingEnabled) { + SceneDemo::setIsShadowMappingEnabled(false); +} + +// Display/Hide the contact points +inline void RaycastScene::setIsContactPointsDisplayed(bool display) { + SceneDemo::setIsContactPointsDisplayed(true); +} + +// Return all the contact points of the scene +inline std::vector RaycastScene::getContactPoints() const { + return mRaycastManager.getHitPoints(); +} + +} #endif diff --git a/examples/common/opengl-framework/src/shaders/depth.frag b/testbed/shaders/depth.frag similarity index 88% rename from examples/common/opengl-framework/src/shaders/depth.frag rename to testbed/shaders/depth.frag index 4ef97b32..75c12abe 100644 --- a/examples/common/opengl-framework/src/shaders/depth.frag +++ b/testbed/shaders/depth.frag @@ -1,12 +1,14 @@ +#version 330 + /******************************************************************************** * OpenGL-Framework * -* Copyright (c) 2013 Daniel Chappuis * +* Copyright (c) 2015 Daniel Chappuis * ********************************************************************************* * * * This software is provided 'as-is', without any express or implied warranty. * * In no event will the authors be held liable for any damages arising from the * * use of this software. * -* * +* * * Permission is granted to anyone to use this software for any purpose, * * including commercial applications, and to alter it and redistribute it * * freely, subject to the following restrictions: * @@ -23,10 +25,11 @@ * * ********************************************************************************/ -void main(void) { +// Out variable +out vec4 color; // Output color - // Compute the depth of the pixel - float depth = +void main() { - gl_FragColor = vec4(depth, depth, depth, 1); + // Compute the final color + color = vec4(1, 1, 1, 1); } diff --git a/examples/common/opengl-framework/src/shaders/depth.vert b/testbed/shaders/depth.vert similarity index 84% rename from examples/common/opengl-framework/src/shaders/depth.vert rename to testbed/shaders/depth.vert index f320cc72..c4af178a 100644 --- a/examples/common/opengl-framework/src/shaders/depth.vert +++ b/testbed/shaders/depth.vert @@ -1,6 +1,8 @@ +#version 330 + /******************************************************************************** * OpenGL-Framework * -* Copyright (c) 2013 Daniel Chappuis * +* Copyright (c) 2015 Daniel Chappuis * ********************************************************************************* * * * This software is provided 'as-is', without any express or implied warranty. * @@ -24,13 +26,15 @@ ********************************************************************************/ // Uniform variables -uniform mat4 modelToWorldMatrix; // Model too world coordinates matrix -uniform mat4 worldToCameraMatrix; // World to camera coordinates matrix +uniform mat4 localToWorldMatrix; // Local-space to world-space matrix +uniform mat4 worldToCameraMatrix; // World-space to camera-space matrix uniform mat4 projectionMatrix; // Projection matrix -void main(void) { +// In variables +in vec4 vertexPosition; + +void main() { // Compute the clip-space vertex coordinates - gl_Position = projectionMatrix * worldToCameraMatrix * - modelToWorldMatrix * gl_Vertex; + gl_Position = projectionMatrix * worldToCameraMatrix * localToWorldMatrix * vertexPosition; } diff --git a/examples/common/opengl-framework/src/shaders/phong.frag b/testbed/shaders/phong.frag similarity index 57% rename from examples/common/opengl-framework/src/shaders/phong.frag rename to testbed/shaders/phong.frag index f09777da..73c76ba6 100644 --- a/examples/common/opengl-framework/src/shaders/phong.frag +++ b/testbed/shaders/phong.frag @@ -1,6 +1,8 @@ +#version 330 + /******************************************************************************** * OpenGL-Framework * -* Copyright (c) 2013 Daniel Chappuis * +* Copyright (c) 2015 Daniel Chappuis * ********************************************************************************* * * * This software is provided 'as-is', without any express or implied warranty. * @@ -27,16 +29,28 @@ uniform vec3 lightAmbientColor; // Lights ambient color uniform vec3 light0PosCameraSpace; // Camera-space position of the light uniform vec3 light0DiffuseColor; // Light 0 diffuse color -uniform vec3 light0SpecularColor; // Light 0 specular color -uniform float shininess; // Shininess -uniform sampler2D texture; // Texture +uniform sampler2D textureSampler; // Texture +uniform sampler2D shadowMapSampler; // Shadow map texture sampler uniform bool isTexture; // True if we need to use the texture uniform vec4 vertexColor; // Vertex color +uniform bool isShadowEnabled; // True if shadow mapping is enabled +uniform vec2 shadowMapDimension; // Shadow map dimension -// Varying variables -varying vec3 vertexPosCameraSpace; // Camera-space position of the vertex -varying vec3 vertexNormalCameraSpace; // Vertex normal in camera-space -varying vec2 texCoords; // Texture coordinates +// In variables +in vec3 vertexPosCameraSpace; // Camera-space position of the vertex +in vec3 vertexNormalCameraSpace; // Vertex normal in camera-space +in vec2 texCoords; // Texture coordinates +in vec4 shadowMapCoords; // Shadow map texture coords + +// Out variable +out vec4 color; // Output color + +// Texture for PCF Shadow mapping +float textureLookupPCF(sampler2D map, vec2 texCoords, vec2 offset) +{ + vec2 shadowMapScale = vec2(1.0, 1.0) / shadowMapDimension; + return texture(map, texCoords.xy + offset * shadowMapScale).r; +} void main() { @@ -45,7 +59,7 @@ void main() { // Get the texture color vec3 textureColor = vertexColor.rgb; - if (isTexture) textureColor = texture2D(texture, texCoords).rgb; + if (isTexture) textureColor = texture(textureSampler, texCoords).rgb; // Compute the surface normal vector vec3 N = normalize(vertexNormalCameraSpace); @@ -55,13 +69,35 @@ void main() { float diffuseFactor = max(dot(N, L0), 0.0); vec3 diffuse = light0DiffuseColor * diffuseFactor * textureColor; - // Compute the specular term of light 0 - vec3 V = normalize(-vertexPosCameraSpace); - vec3 H0 = normalize(V + L0); - float specularFactor = pow(max(dot(N, H0), 0.0), shininess); - if (diffuseFactor < 0.0) specularFactor = 0.0; - vec3 specular = light0SpecularColor * specularFactor; + // Compute shadow factor + float shadow = 1.0; + if (isShadowEnabled) { + shadow = 0.0; + float bias = 0.0003; + float shadowBias = -0.000; + vec4 shadowMapUV = shadowMapCoords; + shadowMapUV.z -= shadowBias; + vec4 shadowMapCoordsOverW = shadowMapUV / shadowMapUV.w; + + // PCF Shadow Mapping + for (float i=-1; i<=1; i++) { + for (float j=-1; j<=1; j++) { + float distInShadowMap = textureLookupPCF(shadowMapSampler, shadowMapCoordsOverW.xy, vec2(i, j)) + bias; + if (shadowMapCoords.w > 0) { + shadow += distInShadowMap < shadowMapCoordsOverW.z ? 0.5 : 1.0; + } + } + } + shadow /= 9.0; + + /* + float distanceInShadowMap = texture(shadowMapSampler, shadowMapCoordsOverW.xy).r + bias; + if (shadowMapCoords.w > 0) { + shadow = distanceInShadowMap < shadowMapCoordsOverW.z ? 0.5 : 1.0; + } + */ + } // Compute the final color - gl_FragColor = vec4(ambient + diffuse + specular, 1.0); + color = vec4(ambient + shadow * diffuse, 1.0); } diff --git a/testbed/shaders/phong.vert b/testbed/shaders/phong.vert new file mode 100644 index 00000000..491558fb --- /dev/null +++ b/testbed/shaders/phong.vert @@ -0,0 +1,64 @@ +#version 330 + +/******************************************************************************** +* OpenGL-Framework * +* Copyright (c) 2015 Daniel Chappuis * +********************************************************************************* +* * +* This software is provided 'as-is', without any express or implied warranty. * +* In no event will the authors be held liable for any damages arising from the * +* use of this software. * +* * +* Permission is granted to anyone to use this software for any purpose, * +* including commercial applications, and to alter it and redistribute it * +* freely, subject to the following restrictions: * +* * +* 1. The origin of this software must not be misrepresented; you must not claim * +* that you wrote the original software. If you use this software in a * +* product, an acknowledgment in the product documentation would be * +* appreciated but is not required. * +* * +* 2. Altered source versions must be plainly marked as such, and must not be * +* misrepresented as being the original software. * +* * +* 3. This notice may not be removed or altered from any source distribution. * +* * +********************************************************************************/ + +// Uniform variables +uniform mat4 localToWorldMatrix; // Local-space to world-space matrix +uniform mat4 worldToCameraMatrix; // World-space to camera-space matrix +uniform mat4 worldToLight0CameraMatrix; // World-space to light0 camera-space matrix (for shadow mapping) +uniform mat4 projectionMatrix; // Projection matrix +uniform mat3 normalMatrix; // Normal matrix +uniform mat4 shadowMapProjectionMatrix; // Shadow map projection matrix + +// In variables +in vec4 vertexPosition; +in vec3 vertexNormal; +in vec2 textureCoords; + +// Out variables +out vec3 vertexPosCameraSpace; // Camera-space position of the vertex +out vec3 vertexNormalCameraSpace; // Vertex normal in camera-space +out vec2 texCoords; // Texture coordinates +out vec4 shadowMapCoords; // Shadow map texture coords + +void main() { + + // Compute the vertex position + vec4 positionCameraSpace = worldToCameraMatrix * localToWorldMatrix * vertexPosition; + vertexPosCameraSpace = positionCameraSpace.xyz; + + // Compute the world surface normal + vertexNormalCameraSpace = normalMatrix * vertexNormal; + + // Get the texture coordinates + texCoords = textureCoords; + + // Compute the texture coords of the vertex in the shadow map + shadowMapCoords = shadowMapProjectionMatrix * worldToLight0CameraMatrix * localToWorldMatrix * vertexPosition; + + // Compute the clip-space vertex coordinates + gl_Position = projectionMatrix * positionCameraSpace; +} diff --git a/testbed/shaders/quad.frag b/testbed/shaders/quad.frag new file mode 100644 index 00000000..a6a842ba --- /dev/null +++ b/testbed/shaders/quad.frag @@ -0,0 +1,42 @@ +#version 330 + +/******************************************************************************** +* OpenGL-Framework * +* Copyright (c) 2015 Daniel Chappuis * +********************************************************************************* +* * +* This software is provided 'as-is', without any express or implied warranty. * +* In no event will the authors be held liable for any damages arising from the * +* use of this software. * +* * +* Permission is granted to anyone to use this software for any purpose, * +* including commercial applications, and to alter it and redistribute it * +* freely, subject to the following restrictions: * +* * +* 1. The origin of this software must not be misrepresented; you must not claim * +* that you wrote the original software. If you use this software in a * +* product, an acknowledgment in the product documentation would be * +* appreciated but is not required. * +* * +* 2. Altered source versions must be plainly marked as such, and must not be * +* misrepresented as being the original software. * +* * +* 3. This notice may not be removed or altered from any source distribution. * +* * +********************************************************************************/ + +// Uniform variables +uniform sampler2D textureSampler; // Texture + +// In variables +in vec2 texCoords; // Texture coordinates + +// Out variable +out vec4 color; // Output color + +void main() { + + // Get the texture color + float depthColor = texture(textureSampler, texCoords).r; + color = vec4(depthColor, depthColor, depthColor, 1); +} diff --git a/testbed/shaders/quad.vert b/testbed/shaders/quad.vert new file mode 100644 index 00000000..81c6ee9e --- /dev/null +++ b/testbed/shaders/quad.vert @@ -0,0 +1,41 @@ +#version 330 + +/******************************************************************************** +* OpenGL-Framework * +* Copyright (c) 2015 Daniel Chappuis * +********************************************************************************* +* * +* This software is provided 'as-is', without any express or implied warranty. * +* In no event will the authors be held liable for any damages arising from the * +* use of this software. * +* * +* Permission is granted to anyone to use this software for any purpose, * +* including commercial applications, and to alter it and redistribute it * +* freely, subject to the following restrictions: * +* * +* 1. The origin of this software must not be misrepresented; you must not claim * +* that you wrote the original software. If you use this software in a * +* product, an acknowledgment in the product documentation would be * +* appreciated but is not required. * +* * +* 2. Altered source versions must be plainly marked as such, and must not be * +* misrepresented as being the original software. * +* * +* 3. This notice may not be removed or altered from any source distribution. * +* * +********************************************************************************/ + +// In variables +in vec4 vertexPosition; + +// Out variables +out vec2 texCoords; // Texture coordinates + +void main() { + + // Compute the texture coordinates + texCoords = (vertexPosition.xy + vec2(1, 1)) / 2.0; + + // Compute the clip-space vertex coordinates + gl_Position = vec4(vertexPosition.xyz, 1); +} diff --git a/testbed/src/Gui.cpp b/testbed/src/Gui.cpp new file mode 100644 index 00000000..a80ed525 --- /dev/null +++ b/testbed/src/Gui.cpp @@ -0,0 +1,406 @@ +/******************************************************************************** +* ReactPhysics3D physics library, http://www.reactphysics3d.com * +* Copyright (c) 2010-2015 Daniel Chappuis * +********************************************************************************* +* * +* This software is provided 'as-is', without any express or implied warranty. * +* In no event will the authors be held liable for any damages arising from the * +* use of this software. * +* * +* Permission is granted to anyone to use this software for any purpose, * +* including commercial applications, and to alter it and redistribute it * +* freely, subject to the following restrictions: * +* * +* 1. The origin of this software must not be misrepresented; you must not claim * +* that you wrote the original software. If you use this software in a * +* product, an acknowledgment in the product documentation would be * +* appreciated but is not required. * +* * +* 2. Altered source versions must be plainly marked as such, and must not be * +* misrepresented as being the original software. * +* * +* 3. This notice may not be removed or altered from any source distribution. * +* * +********************************************************************************/ + +// Libraries +#include "Gui.h" +#include +#include +#include +#include "TestbedApplication.h" + +GLFWwindow* Gui::mWindow = NULL; +double Gui::g_Time = 0.0f; +bool Gui::g_MousePressed[3] = {false, false, false}; +float Gui::g_MouseWheel = 0.0f; +GLuint Gui::g_FontTexture = 0; +size_t Gui::g_VboSize = 0; +int Gui::g_AttribLocationTex = 0, Gui::g_AttribLocationProjMtx = 0; +int Gui::g_AttribLocationPosition = 0, Gui::g_AttribLocationUV = 0, Gui::g_AttribLocationColor = 0; +Shader Gui::mShader; +openglframework::VertexBufferObject Gui::mVBO(GL_ARRAY_BUFFER); +openglframework::VertexArrayObject Gui::mVAO; +Gui::LeftPane Gui::mLeftPane = SCENES; +double Gui::mScrollX = 0.0; +double Gui::mScrollY = 0.0; +double Gui::mTimeSinceLastProfilingDisplay = 0; +double Gui::mCachedFPS = 0; +double Gui::mCachedUpdateTime = 0; +double Gui::mCachedPhysicsUpdateTime = 0; + +// Constructor +Gui::Gui() { + + g_Time = 0.0f; + g_MousePressed[0] = false; + g_MousePressed[1] = false; + g_MousePressed[2] = false; + g_MouseWheel = 0.0f; + g_FontTexture = 0; + g_VboSize = 0; +} + +// Destructor +Gui::~Gui() { + + mVAO.destroy(); + mVBO.destroy(); + + mShader.destroy(); + + imguiRenderGLDestroy(); +} + +// Create and return the singleton instance of this class +Gui& Gui::getInstance() { + static Gui instance; + return instance; +} + +/// Initialize the GUI +void Gui::init() { + + // Init UI + if (!imguiRenderGLInit("DroidSans.ttf")) { + fprintf(stderr, "Could not init GUI renderer.\n"); + exit(EXIT_FAILURE); + } + + mTimeSinceLastProfilingDisplay = glfwGetTime(); +} + +void Gui::displayLeftPane() { + + TestbedApplication& app = TestbedApplication::getInstance(); + + int windowWidth, windowHeight; + glfwGetWindowSize(mWindow, &windowWidth, &windowHeight); + + int scrollarea = 0; + imguiBeginScrollArea(NULL, 0, app.mWindowToFramebufferRatio.y * (windowHeight - LEFT_PANE_HEADER_HEIGHT), + app.mWindowToFramebufferRatio.x * LEFT_PANE_WIDTH, + app.mWindowToFramebufferRatio.y * LEFT_PANE_HEADER_HEIGHT, &scrollarea); + + // ------ Header ----- // + + imguiHorizontalSpace(10); + imguiVerticalSpace(20); + imguiStartLine(); + + const int button_width = 150; + + // Play/Pause + if (imguiButton(app.mTimer.isRunning() ? "Pause" : "Play", true, button_width)) { + app.togglePlayPauseSimulation(); + } + imguiHorizontalSpace(5); + + // Step + if (imguiButton("Step", !app.mTimer.isRunning(), button_width)) { + app.toggleTakeSinglePhysicsStep(); + } + imguiHorizontalSpace(5); + + // Restart + if (imguiButton("Restart", true, button_width)) { + app.restartSimulation(); + } + + imguiEndLine(); + imguiVerticalSpace(70); + + imguiSeparatorLine(); + imguiVerticalSpace(5); + imguiStartLine(); + + // ----- Left Pane Tabs ----- // + + int widthButton = app.mWindowToFramebufferRatio.x * LEFT_PANE_WIDTH / 4.3; + if (imguiButton("Scenes", true, widthButton)) { + mLeftPane = SCENES; + } + imguiHorizontalSpace(5); + + if (imguiButton("Physics", true, widthButton)) { + mLeftPane = PHYSICS; + } + imguiHorizontalSpace(5); + + if (imguiButton("Rendering", true, widthButton)) { + mLeftPane = RENDERING; + } + imguiHorizontalSpace(5); + + if (imguiButton("Profiling", true, widthButton)) { + mLeftPane = PROFILING; + } + + imguiEndLine(); + imguiVerticalSpace(BUTTON_HEIGHT + 8); + imguiSeparatorLine(); + imguiEndScrollArea(); + + // Display the left pane content + switch(mLeftPane) { + case SCENES: displayScenesPane(); break; + case PHYSICS: displayPhysicsPane(); break; + case RENDERING: displayRenderingPane(); break; + case PROFILING: displayProfilingPane(); break; + } +} + +// Display the list of scenes +void Gui::displayScenesPane() { + + TestbedApplication& app = TestbedApplication::getInstance(); + + static int choice = 0; + int startChoice = choice; + int scrollarea = 1; + int windowWidth, windowHeight; + glfwGetWindowSize(mWindow, &windowWidth, &windowHeight); + + std::vector scenes = app.getScenes(); + + imguiBeginScrollArea("Scenes", 0, 0, + app.mWindowToFramebufferRatio.x * LEFT_PANE_WIDTH, + app.mWindowToFramebufferRatio.y * (windowHeight - LEFT_PANE_HEADER_HEIGHT), + &scrollarea); + + imguiVerticalSpace(15); + + // For each scene + for (int i=0; igetName().c_str(), choice == i)) { + choice = i; + } + } + if (choice != startChoice) { + app.switchScene(scenes[choice]); + } + + imguiEndScrollArea(); +} + +void Gui::displayPhysicsPane() { + + TestbedApplication& app = TestbedApplication::getInstance(); + + int windowWidth, windowHeight; + glfwGetWindowSize(mWindow, &windowWidth, &windowHeight); + + int scrollarea = 2; + imguiBeginScrollArea("Physics Engine Parameters", 0, 0, + app.mWindowToFramebufferRatio.x * LEFT_PANE_WIDTH, + app.mWindowToFramebufferRatio.y * (windowHeight - LEFT_PANE_HEADER_HEIGHT), + &scrollarea); + + imguiVerticalSpace(15); + + // Enabled/Disable Sleeping + bool toggle = imguiCheck("Sleeping enabled", app.mEngineSettings.isSleepingEnabled); + if (toggle) { + app.mEngineSettings.isSleepingEnabled = !app.mEngineSettings.isSleepingEnabled; + } + + // Enabled/Disable Gravity + toggle = imguiCheck("Gravity enabled", app.mEngineSettings.isGravityEnabled); + if (toggle) { + app.mEngineSettings.isGravityEnabled = !app.mEngineSettings.isGravityEnabled; + } + + // Timestep + float timeStep = app.mEngineSettings.timeStep; + if (imguiSlider("Timestep", &timeStep, 0.001f, 1.0f, 0.001f)) { + app.mEngineSettings.timeStep = timeStep; + } + + // Nb velocity solver iterations + float nbVelocityIterations = static_cast(app.mEngineSettings.nbVelocitySolverIterations); + if (imguiSlider("Velocity Solver Iterations", &nbVelocityIterations, 1.0f, 100.0f, 1.0f)) { + app.mEngineSettings.nbVelocitySolverIterations = static_cast(nbVelocityIterations); + } + + // Nb position solver iterations + float nbPositionIterations = static_cast(app.mEngineSettings.nbPositionSolverIterations); + if (imguiSlider("Position Solver Iterations", &nbPositionIterations, 1.0f, 100.0f, 1.0f)) { + app.mEngineSettings.nbPositionSolverIterations = static_cast(nbPositionIterations); + } + + // Time before sleep + float timeBeforeSleep = app.mEngineSettings.timeBeforeSleep; + if (imguiSlider("Time before sleep", &timeBeforeSleep, 0.0f, 60.0f, 0.5f)) { + app.mEngineSettings.timeBeforeSleep = timeBeforeSleep; + } + + // Sleep linear velocity + float sleepLinearVelocity = app.mEngineSettings.sleepLinearVelocity; + if (imguiSlider("Sleep linear velocity", &sleepLinearVelocity, 0.0f, 30.0f, 0.5f)) { + app.mEngineSettings.sleepLinearVelocity = sleepLinearVelocity; + } + + // Sleep angular velocity + float sleepAngularVelocity = app.mEngineSettings.sleepAngularVelocity; + if (imguiSlider("Sleep angular velocity", &sleepAngularVelocity, 0.0f, 30.0f, 0.5f)) { + app.mEngineSettings.sleepAngularVelocity = sleepAngularVelocity; + } + + // Gravity vector + openglframework::Vector3 gravity = app.mEngineSettings.gravity; + float gravityX = gravity.x, gravityY = gravity.y, gravityZ = gravity.z; + if (imguiSlider("Gravity X", &gravityX, -50.0f, 50.0f, 0.5f)) { + app.mEngineSettings.gravity.x = gravityX; + } + if (imguiSlider("Gravity Y", &gravityY, -50.0f, 50.0f, 0.5f)) { + app.mEngineSettings.gravity.y = gravityY; + } + if (imguiSlider("Gravity Z", &gravityZ, -50.0f, 50.0f, 0.5f)) { + app.mEngineSettings.gravity.z = gravityZ; + } + + imguiEndScrollArea(); +} + +void Gui::displayRenderingPane() { + + TestbedApplication& app = TestbedApplication::getInstance(); + + int windowWidth, windowHeight; + glfwGetWindowSize(mWindow, &windowWidth, &windowHeight); + + int scrollarea = 2; + imguiBeginScrollArea("Rendering Parameters", 0, 0, + app.mWindowToFramebufferRatio.x * LEFT_PANE_WIDTH, + app.mWindowToFramebufferRatio.y * (windowHeight - LEFT_PANE_HEADER_HEIGHT), + &scrollarea); + + imguiVerticalSpace(15); + + + // Display/Hide contact points + bool toggleContactPoints = imguiCheck("Contacts", app.mIsContactPointsDisplayed); + if (toggleContactPoints) { + app.displayContactPoints(!app.mIsContactPointsDisplayed); + } + + // Enabled/Disable VSync + bool toggleVSync = imguiCheck("V Sync", app.mIsVSyncEnabled); + if (toggleVSync) { + app.enableVSync(!app.mIsVSyncEnabled); + } + + // Enabled/Disable Shadows + bool toggleShadows = imguiCheck("Shadows", app.mIsShadowMappingEnabled); + if (toggleShadows) { + app.enableShadows(!app.mIsShadowMappingEnabled); + } + + imguiEndScrollArea(); +} + +void Gui::displayProfilingPane() { + + TestbedApplication& app = TestbedApplication::getInstance(); + + double currentTime = glfwGetTime(); + if ((currentTime - mTimeSinceLastProfilingDisplay) > TIME_INTERVAL_DISPLAY_PROFILING_INFO) { + mTimeSinceLastProfilingDisplay = currentTime; + mCachedFPS = app.mFPS; + mCachedUpdateTime = app.mUpdateTime; + mCachedPhysicsUpdateTime = app.mPhysicsUpdateTime; + } + + int windowWidth, windowHeight; + glfwGetWindowSize(mWindow, &windowWidth, &windowHeight); + + int scrollarea = 2; + imguiBeginScrollArea("Profiling", 0, 0, + app.mWindowToFramebufferRatio.x * LEFT_PANE_WIDTH, + app.mWindowToFramebufferRatio.y * (windowHeight - LEFT_PANE_HEADER_HEIGHT), + &scrollarea); + + imguiVerticalSpace(15); + + // Framerate (FPS) + std::stringstream ss; + ss << std::setprecision(4) << mCachedFPS; + std::string fps = std::string("FPS : ") + ss.str(); + imguiItem(fps.c_str()); + + // Update time + std::stringstream ss1; + double updateTime = mCachedUpdateTime * 1000.0; + ss1 << std::setprecision(4) << updateTime; + std::string updateTimeStr = std::string("Update time (ms) : ") + ss1.str(); + imguiItem(updateTimeStr.c_str()); + + // Update time (physics) + std::stringstream ss2; + ss2 << std::setprecision(4) << (mCachedPhysicsUpdateTime * 1000.0); + std::string updatePhysicsTimeStr = std::string("Update physics time (ms) : ") + ss2.str(); + imguiItem(updatePhysicsTimeStr.c_str()); + + imguiEndScrollArea(); +} + +// Display the GUI +void Gui::render() { + + TestbedApplication& app = TestbedApplication::getInstance(); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_DEPTH_TEST); + + int display_w, display_h; + glfwGetFramebufferSize(mWindow, &display_w, &display_h); + + int windowWidth, windowHeight; + glfwGetWindowSize(mWindow, &windowWidth, &windowHeight); + + // Mouse position + double mouseX, mouseY; + glfwGetCursorPos(mWindow, &mouseX, &mouseY); + + // Mouse buttons + int leftButton = glfwGetMouseButton(mWindow, GLFW_MOUSE_BUTTON_LEFT); + unsigned char mousebutton = 0; + if(leftButton == GLFW_PRESS ) { + mousebutton |= IMGUI_MBUT_LEFT; + } + + imguiBeginFrame(app.mWindowToFramebufferRatio.x * mouseX, + app.mWindowToFramebufferRatio.y * (windowHeight - mouseY), mousebutton, + - app.mWindowToFramebufferRatio.y * mScrollY); + resetScroll(); + + //displayHeader(); + displayLeftPane(); + + imguiEndFrame(); + + imguiRenderGLDraw(display_w, display_h); +} diff --git a/testbed/src/Gui.h b/testbed/src/Gui.h new file mode 100644 index 00000000..19d5dc07 --- /dev/null +++ b/testbed/src/Gui.h @@ -0,0 +1,140 @@ +/******************************************************************************** +* ReactPhysics3D physics library, http://www.reactphysics3d.com * +* Copyright (c) 2010-2015 Daniel Chappuis * +********************************************************************************* +* * +* This software is provided 'as-is', without any express or implied warranty. * +* In no event will the authors be held liable for any damages arising from the * +* use of this software. * +* * +* Permission is granted to anyone to use this software for any purpose, * +* including commercial applications, and to alter it and redistribute it * +* freely, subject to the following restrictions: * +* * +* 1. The origin of this software must not be misrepresented; you must not claim * +* that you wrote the original software. If you use this software in a * +* product, an acknowledgment in the product documentation would be * +* appreciated but is not required. * +* * +* 2. Altered source versions must be plainly marked as such, and must not be * +* misrepresented as being the original software. * +* * +* 3. This notice may not be removed or altered from any source distribution. * +* * +********************************************************************************/ + +#ifndef GUI_H +#define GUI_H + +// Libraries +#include "imgui.h" +#include "imguiRenderGL3.h" +#include +#include +#include "openglframework.h" + +// Constants +const float GUI_SCALING = 2.0f; +const int LEFT_PANE_WIDTH = 300; +const int LEFT_PANE_HEADER_HEIGHT = 90; +const double TIME_INTERVAL_DISPLAY_PROFILING_INFO = 0.3; + +using namespace openglframework; + +// Declarations +class TestbedApplication; + +// Class Gui +class Gui { + + protected : + + enum LeftPane {SCENES, PHYSICS, RENDERING, PROFILING}; + + // -------------------- Constants -------------------- // + + + // -------------------- Attributes -------------------- // + + // TODO : Delete this + static GLFWwindow* mWindow; + + static Shader mShader; + static double g_Time; + static bool g_MousePressed[3]; + static float g_MouseWheel; + static GLuint g_FontTexture; + static int g_AttribLocationTex, g_AttribLocationProjMtx; + static int g_AttribLocationPosition, g_AttribLocationUV, g_AttribLocationColor; + static size_t g_VboSize; + static openglframework::VertexBufferObject mVBO; + static openglframework::VertexArrayObject mVAO; + static LeftPane mLeftPane; + + static double mScrollX, mScrollY; + + // -------------------- Methods -------------------- // + + static void displayLeftPane(); + + /// Display the list of scenes + static void displayScenesPane(); + + static void displayPhysicsPane(); + static void displayRenderingPane(); + static void displayProfilingPane(); + + static void resetScroll(); + + /// Current time (in seconds) from last profiling time display + static double mTimeSinceLastProfilingDisplay; + + /// Cached Framerate + static double mCachedFPS; + + /// Cached update time + static double mCachedUpdateTime; + + // Cached update physics time + static double mCachedPhysicsUpdateTime; + + + public : + + // -------------------- Methods -------------------- // + + /// Constructor + Gui(); + + /// Destructor + ~Gui(); + + /// Create and return the singleton instance of this class + static Gui& getInstance(); + + /// Initialize the GUI + void init(); + + /// Display the GUI + void render(); + + static void setWindow(GLFWwindow* window); + + static void setScroll(double scrollX, double scrollY); +}; + +inline void Gui::setWindow(GLFWwindow* window) { + mWindow = window; +} + +inline void Gui::resetScroll() { + mScrollX = 0.0; + mScrollY = 0.0; +} + +inline void Gui::setScroll(double scrollX, double scrollY) { + mScrollX = scrollX; + mScrollY = scrollY; +} + +#endif diff --git a/testbed/src/Main.cpp b/testbed/src/Main.cpp new file mode 100644 index 00000000..39fd038f --- /dev/null +++ b/testbed/src/Main.cpp @@ -0,0 +1,38 @@ +/******************************************************************************** +* ReactPhysics3D physics library, http://www.reactphysics3d.com * +* Copyright (c) 2010-2015 Daniel Chappuis * +********************************************************************************* +* * +* This software is provided 'as-is', without any express or implied warranty. * +* In no event will the authors be held liable for any damages arising from the * +* use of this software. * +* * +* Permission is granted to anyone to use this software for any purpose, * +* including commercial applications, and to alter it and redistribute it * +* freely, subject to the following restrictions: * +* * +* 1. The origin of this software must not be misrepresented; you must not claim * +* that you wrote the original software. If you use this software in a * +* product, an acknowledgment in the product documentation would be * +* appreciated but is not required. * +* * +* 2. Altered source versions must be plainly marked as such, and must not be * +* misrepresented as being the original software. * +* * +* 3. This notice may not be removed or altered from any source distribution. * +* * +********************************************************************************/ + +// Libraries +#include "TestbedApplication.h" + +// Main function +int main(int argc, char** argv) { + + // Create and start the testbed application + TestbedApplication& application = TestbedApplication::getInstance(); + application.init(); + application.startMainLoop(); + + return 0; +} diff --git a/testbed/src/Scene.cpp b/testbed/src/Scene.cpp new file mode 100644 index 00000000..7e71968b --- /dev/null +++ b/testbed/src/Scene.cpp @@ -0,0 +1,181 @@ +/******************************************************************************** +* ReactPhysics3D physics library, http://www.reactphysics3d.com * +* Copyright (c) 2010-2015 Daniel Chappuis * +********************************************************************************* +* * +* This software is provided 'as-is', without any express or implied warranty. * +* In no event will the authors be held liable for any damages arising from the * +* use of this software. * +* * +* Permission is granted to anyone to use this software for any purpose, * +* including commercial applications, and to alter it and redistribute it * +* freely, subject to the following restrictions: * +* * +* 1. The origin of this software must not be misrepresented; you must not claim * +* that you wrote the original software. If you use this software in a * +* product, an acknowledgment in the product documentation would be * +* appreciated but is not required. * +* * +* 2. Altered source versions must be plainly marked as such, and must not be * +* misrepresented as being the original software. * +* * +* 3. This notice may not be removed or altered from any source distribution. * +* * +********************************************************************************/ + +// Libraries +#include "Scene.h" +#include + +using namespace openglframework; + +// Constructor +Scene::Scene(const std::string& name, bool isShadowMappingEnabled) + : mName(name), mInterpolationFactor(0.0f), mViewportX(0), mViewportY(0), + mViewportWidth(0), mViewportHeight(0), mIsShadowMappingEnabled(isShadowMappingEnabled), + mIsContactPointsDisplayed(true) { + +} + +// Destructor +Scene::~Scene() { + +} + +// Set the scene position (where the camera needs to look at) +void Scene::setScenePosition(const openglframework::Vector3& position, float sceneRadius) { + + // Set the position and radius of the scene + mCenterScene = position; + mCamera.setSceneRadius(sceneRadius); + + // Reset the camera position and zoom in order to view all the scene + resetCameraToViewAll(); +} + +// Set the camera so that we can view the whole scene +void Scene::resetCameraToViewAll() { + + // Move the camera to the origin of the scene + mCamera.translateWorld(-mCamera.getOrigin()); + + // Move the camera to the center of the scene + mCamera.translateWorld(mCenterScene); + + // Set the zoom of the camera so that the scene center is + // in negative view direction of the camera + mCamera.setZoom(1.0); +} + +// Map the mouse x,y coordinates to a point on a sphere +bool Scene::mapMouseCoordinatesToSphere(double xMouse, double yMouse, + Vector3& spherePoint) const { + + if ((xMouse >= 0) && (xMouse <= mWindowWidth) && (yMouse >= 0) && (yMouse <= mWindowHeight)) { + float x = float(xMouse - 0.5f * mWindowWidth) / float(mWindowWidth); + float y = float(0.5f * mWindowHeight - yMouse) / float(mWindowHeight); + float sinx = sin(PI * x * 0.5f); + float siny = sin(PI * y * 0.5f); + float sinx2siny2 = sinx * sinx + siny * siny; + + // Compute the point on the sphere + spherePoint.x = sinx; + spherePoint.y = siny; + spherePoint.z = (sinx2siny2 < 1.0) ? sqrt(1.0f - sinx2siny2) : 0.0f; + + return true; + } + + return false; +} + +// Called when a mouse button event occurs +void Scene::mouseButtonEvent(int button, int action, int mods, + double mousePosX, double mousePosY) { + + // If the mouse button is pressed + if (action == GLFW_PRESS) { + mLastMouseX = mousePosX; + mLastMouseY = mousePosY; + mIsLastPointOnSphereValid = mapMouseCoordinatesToSphere(mousePosX, mousePosY, mLastPointOnSphere); + } + else { // If the mouse button is released + mIsLastPointOnSphereValid = false; + } +} + +// Called when a mouse motion event occurs +void Scene::mouseMotionEvent(double xMouse, double yMouse, int leftButtonState, + int rightButtonState, int middleButtonState, + int altKeyState) { + + // Zoom + if (leftButtonState == GLFW_PRESS && altKeyState == GLFW_PRESS) { + + float dy = static_cast(yMouse - mLastMouseY); + float h = static_cast(mWindowHeight); + + // Zoom the camera + zoom(-dy / h); + } + // Translation + else if (middleButtonState == GLFW_PRESS || rightButtonState == GLFW_PRESS || + (leftButtonState == GLFW_PRESS && altKeyState == GLFW_PRESS)) { + translate(xMouse, yMouse); + } + // Rotation + else if (leftButtonState == GLFW_PRESS) { + rotate(xMouse, yMouse); + } + + // Remember the mouse position + mLastMouseX = xMouse; + mLastMouseY = yMouse; + mIsLastPointOnSphereValid = mapMouseCoordinatesToSphere(xMouse, yMouse, mLastPointOnSphere); +} + +// Called when a scrolling event occurs +void Scene::scrollingEvent(float xAxis, float yAxis, float scrollSensitivy) { + zoom(yAxis * scrollSensitivy); +} + +// Zoom the camera +void Scene::zoom(float zoomDiff) { + + // Zoom the camera + mCamera.setZoom(zoomDiff); +} + +// Translate the camera +void Scene::translate(int xMouse, int yMouse) { + float dx = static_cast(xMouse - mLastMouseX); + float dy = static_cast(yMouse - mLastMouseY); + + // Translate the camera + mCamera.translateCamera(-dx / float(mCamera.getWidth()), + -dy / float(mCamera.getHeight()), mCenterScene); +} + +// Rotate the camera +void Scene::rotate(int xMouse, int yMouse) { + + if (mIsLastPointOnSphereValid) { + + Vector3 newPoint3D; + bool isNewPointOK = mapMouseCoordinatesToSphere(xMouse, yMouse, newPoint3D); + + if (isNewPointOK) { + Vector3 axis = mLastPointOnSphere.cross(newPoint3D); + float cosAngle = mLastPointOnSphere.dot(newPoint3D); + + float epsilon = std::numeric_limits::epsilon(); + if (fabs(cosAngle) < 1.0f && axis.length() > epsilon) { + axis.normalize(); + float angle = 2.0f * acos(cosAngle); + + // Rotate the camera around the center of the scene + mCamera.rotateAroundLocalPoint(axis, -angle, mCenterScene); + } + } + } +} diff --git a/testbed/src/Scene.h b/testbed/src/Scene.h new file mode 100644 index 00000000..2091caac --- /dev/null +++ b/testbed/src/Scene.h @@ -0,0 +1,277 @@ +/******************************************************************************** +* ReactPhysics3D physics library, http://www.reactphysics3d.com * +* Copyright (c) 2010-2015 Daniel Chappuis * +********************************************************************************* +* * +* This software is provided 'as-is', without any express or implied warranty. * +* In no event will the authors be held liable for any damages arising from the * +* use of this software. * +* * +* Permission is granted to anyone to use this software for any purpose, * +* including commercial applications, and to alter it and redistribute it * +* freely, subject to the following restrictions: * +* * +* 1. The origin of this software must not be misrepresented; you must not claim * +* that you wrote the original software. If you use this software in a * +* product, an acknowledgment in the product documentation would be * +* appreciated but is not required. * +* * +* 2. Altered source versions must be plainly marked as such, and must not be * +* misrepresented as being the original software. * +* * +* 3. This notice may not be removed or altered from any source distribution. * +* * +********************************************************************************/ + +#ifndef SCENE_H +#define SCENE_H + +// Libraries +#include "openglframework.h" + +// Structure ContactPoint +struct ContactPoint { + + public: + openglframework::Vector3 point; + + /// Constructor + ContactPoint(const openglframework::Vector3& pointWorld) : point(pointWorld) { + + } +}; + +/// Structure EngineSettings +/// This structure contains several physics engine parameters +struct EngineSettings { + + public: + + float elapsedTime; // Elapsed time (in seconds) + float timeStep; // Current time step (in seconds) + int nbVelocitySolverIterations; // Nb of velocity solver iterations + int nbPositionSolverIterations; // Nb of position solver iterations + bool isSleepingEnabled; // True if sleeping technique is enabled + float timeBeforeSleep; // Time of inactivity before a body sleep + float sleepLinearVelocity; // Sleep linear velocity + float sleepAngularVelocity; // Sleep angular velocity + bool isGravityEnabled; // True if gravity is enabled + openglframework::Vector3 gravity; // Gravity vector + + /// Constructor + EngineSettings() : elapsedTime(0.0f), timeStep(0.0f) { + + } +}; + +// Class Scene +// Abstract class that represents a 3D scene. +class Scene { + + protected: + + // -------------------- Attributes -------------------- // + + /// Scene name + std::string mName; + + /// Physics engine settings + EngineSettings mEngineSettings; + + /// Camera + openglframework::Camera mCamera; + + /// Center of the scene + openglframework::Vector3 mCenterScene; + + /// Last mouse coordinates on the windows + double mLastMouseX, mLastMouseY; + + /// Window dimension + int mWindowWidth, mWindowHeight; + + /// Last point computed on a sphere (for camera rotation) + openglframework::Vector3 mLastPointOnSphere; + + /// True if the last point computed on a sphere (for camera rotation) is valid + bool mIsLastPointOnSphereValid; + + /// Interpolation factor for the bodies in the current frame + float mInterpolationFactor; + + /// Viewport x,y, width and height values + int mViewportX, mViewportY, mViewportWidth, mViewportHeight; + + /// True if the shadow mapping is enabled + bool mIsShadowMappingEnabled; + + /// True if contact points are displayed + bool mIsContactPointsDisplayed; + + // -------------------- Methods -------------------- // + + /// Set the scene position (where the camera needs to look at) + void setScenePosition(const openglframework::Vector3& position, float sceneRadius); + + /// Set the camera so that we can view the whole scene + void resetCameraToViewAll(); + + /// Map mouse coordinates to coordinates on the sphere + bool mapMouseCoordinatesToSphere(double xMouse, double yMouse, + openglframework::Vector3& spherePoint) const; + + /// Zoom the camera + void zoom(float zoomDiff); + + /// Translate the camera + void translate(int xMouse, int yMouse); + + /// Rotate the camera + void rotate(int xMouse, int yMouse); + + public: + + // -------------------- Methods -------------------- // + + /// Constructor + Scene(const std::string& name, bool isShadowMappingEnabled = false); + + /// Destructor + virtual ~Scene(); + + /// Reshape the view + virtual void reshape(int width, int height); + + /// Update the physics world (take a simulation step) + /// Can be called several times per frame + virtual void updatePhysics()=0; + + /// Update the scene + virtual void update()=0; + + /// Render the scene + virtual void render()=0; + + /// Reset the scene + virtual void reset()=0; + + /// Called when a keyboard event occurs + virtual void keyboardEvent(int key, int scancode, int action, int mods); + + /// Called when a mouse button event occurs + virtual void mouseButtonEvent(int button, int action, int mods, + double mousePosX, double mousePosY); + + /// Called when a mouse motion event occurs + virtual void mouseMotionEvent(double xMouse, double yMouse, int leftButtonState, + int rightButtonState, int middleButtonState, int altKeyState); + + /// Called when a scrolling event occurs + virtual void scrollingEvent(float xAxis, float yAxis, float scrollSensitivy); + + /// Set the window dimension + void setWindowDimension(int width, int height); + + /// Set the viewport to render the scene + void setViewport(int x, int y, int width, int height); + + /// Return a reference to the camera + const openglframework::Camera& getCamera() const; + + /// Get the engine settings + EngineSettings getEngineSettings() const; + + /// Set the engine settings + void setEngineSettings(const EngineSettings& settings); + + /// Set the interpolation factor + void setInterpolationFactor(float interpolationFactor); + + /// Return the name of the scene + std::string getName() const; + + /// Return true if the shadow mapping is enabled + bool getIsShadowMappingEnabled() const; + + /// Enabled/Disable the shadow mapping + void virtual setIsShadowMappingEnabled(bool isShadowMappingEnabled); + + /// Display/Hide the contact points + void virtual setIsContactPointsDisplayed(bool display); + + /// Return all the contact points of the scene + std::vector virtual getContactPoints() const; +}; + +// Called when a keyboard event occurs +inline void Scene::keyboardEvent(int key, int scancode, int action, int mods) { + +} + +/// Reshape the view +inline void Scene::reshape(int width, int height) { + mCamera.setDimensions(width, height); +} + +// Return a reference to the camera +inline const openglframework::Camera& Scene::getCamera() const { + return mCamera; +} + +// Set the window dimension +inline void Scene::setWindowDimension(int width, int height) { + mWindowWidth = width; + mWindowHeight = height; +} + +// Set the viewport to render the scene +inline void Scene::setViewport(int x, int y, int width, int height) { + mViewportX = x; + mViewportY = y; + mViewportWidth = width; + mViewportHeight = height; +} + +// Get the engine settings +inline EngineSettings Scene::getEngineSettings() const { + return mEngineSettings; +} + +// Set the engine settings +inline void Scene::setEngineSettings(const EngineSettings& settings) { + mEngineSettings = settings; +} + +// Set the interpolation factor +inline void Scene::setInterpolationFactor(float interpolationFactor) { + mInterpolationFactor = interpolationFactor; +} + +// Return the name of the scene +inline std::string Scene::getName() const { + return mName; +} + +// Return true if the shadow mapping is enabled +inline bool Scene::getIsShadowMappingEnabled() const { + return mIsShadowMappingEnabled; +} + +// Enabled/Disable the shadow mapping +inline void Scene::setIsShadowMappingEnabled(bool isShadowMappingEnabled) { + mIsShadowMappingEnabled = isShadowMappingEnabled; +} + +// Display/Hide the contact points +inline void Scene::setIsContactPointsDisplayed(bool display) { + mIsContactPointsDisplayed = display; +} + +// Return all the contact points of the scene +inline std::vector Scene::getContactPoints() const { + + // Return an empty list of contact points + return std::vector(); +} + +#endif diff --git a/testbed/src/SceneDemo.cpp b/testbed/src/SceneDemo.cpp new file mode 100644 index 00000000..7fe881b9 --- /dev/null +++ b/testbed/src/SceneDemo.cpp @@ -0,0 +1,346 @@ +/******************************************************************************** +* ReactPhysics3D physics library, http://www.reactphysics3d.com * +* Copyright (c) 2010-2015 Daniel Chappuis * +********************************************************************************* +* * +* This software is provided 'as-is', without any express or implied warranty. * +* In no event will the authors be held liable for any damages arising from the * +* use of this software. * +* * +* Permission is granted to anyone to use this software for any purpose, * +* including commercial applications, and to alter it and redistribute it * +* freely, subject to the following restrictions: * +* * +* 1. The origin of this software must not be misrepresented; you must not claim * +* that you wrote the original software. If you use this software in a * +* product, an acknowledgment in the product documentation would be * +* appreciated but is not required. * +* * +* 2. Altered source versions must be plainly marked as such, and must not be * +* misrepresented as being the original software. * +* * +* 3. This notice may not be removed or altered from any source distribution. * +* * +********************************************************************************/ + +// Libraries +#include "SceneDemo.h" +#include + +using namespace openglframework; + +int SceneDemo::shadowMapTextureLevel = 0; +openglframework::Color SceneDemo::mGreyColorDemo = Color(0.70f, 0.70f, 0.7f, 1.0); +openglframework::Color SceneDemo::mYellowColorDemo = Color(0.9, 0.88, 0.145, 1.0); +openglframework::Color SceneDemo::mBlueColorDemo = Color(0, 0.66, 0.95, 1.0); +openglframework::Color SceneDemo::mOrangeColorDemo = Color(0.9, 0.35, 0, 1.0); +openglframework::Color SceneDemo::mPinkColorDemo = Color(0.83, 0.48, 0.64, 1.0); +openglframework::Color SceneDemo::mRedColorDemo = Color(0.95, 0, 0, 1.0); +int SceneDemo::mNbDemoColors = 4; +openglframework::Color SceneDemo::mDemoColors[] = {SceneDemo::mYellowColorDemo, SceneDemo::mBlueColorDemo, + SceneDemo::mOrangeColorDemo, SceneDemo::mPinkColorDemo}; + +// Constructor +SceneDemo::SceneDemo(const std::string& name, float sceneRadius, bool isShadowMappingEnabled) + : Scene(name, isShadowMappingEnabled), + mLight0(0), mIsShadowMappingInitialized(false), + mDepthShader("shaders/depth.vert", "shaders/depth.frag"), + mPhongShader("shaders/phong.vert", "shaders/phong.frag"), + mQuadShader("shaders/quad.vert", "shaders/quad.frag"), + mVBOQuad(GL_ARRAY_BUFFER), mMeshFolderPath("meshes/") { + + shadowMapTextureLevel++; + + // Move the light0 + mLight0.translateWorld(Vector3(-2, 35, 40)); + + // Camera at light0 postion for the shadow map + mShadowMapLightCamera.translateWorld(mLight0.getOrigin()); + mShadowMapLightCamera.rotateLocal(Vector3(1, 0, 0), -PI / 4.0f); + mShadowMapLightCamera.rotateWorld(Vector3(0, 1, 0), PI / 8.0f); + + mShadowMapLightCamera.setDimensions(SHADOWMAP_WIDTH, SHADOWMAP_HEIGHT); + mShadowMapLightCamera.setFieldOfView(80.0f); + mShadowMapLightCamera.setSceneRadius(100); + + mShadowMapBiasMatrix.setAllValues(0.5, 0.0, 0.0, 0.5, + 0.0, 0.5, 0.0, 0.5, + 0.0, 0.0, 0.5, 0.5, + 0.0, 0.0, 0.0, 1.0); + + // Create the Shadow map FBO and texture + if (mIsShadowMappingEnabled) { + createShadowMapFBOAndTexture(); + } + + createQuadVBO(); + + VisualContactPoint::createStaticData(mMeshFolderPath); +} + +// Destructor +SceneDemo::~SceneDemo() { + + mShadowMapTexture.destroy(); + mFBOShadowMap.destroy(); + mVBOQuad.destroy(); + + // Destroy the contact points + removeAllContactPoints(); + + VisualContactPoint::destroyStaticData(); +} + +// Update the scene +void SceneDemo::update() { + + // Update the contact points + updateContactPoints(); +} + +// Render the scene (in multiple passes for shadow mapping) +void SceneDemo::render() { + + const Color& diffCol = mLight0.getDiffuseColor(); + + glEnable(GL_DEPTH_TEST); + glEnable(GL_CULL_FACE); + + // ---------- Render the scene to generate the shadow map (first pass) ----------- // + + const Matrix4 shadowMapProjMatrix = mShadowMapLightCamera.getProjectionMatrix(); + const openglframework::Matrix4 worldToLightCameraMatrix = mShadowMapLightCamera.getTransformMatrix().getInverse(); + + // If Shadow Mapping is enabled + if (mIsShadowMappingEnabled) { + + // Culling switching, rendering only backface, this is done to avoid self-shadowing + glCullFace(GL_BACK); + + mFBOShadowMap.bind(); + + // Bind the shader + mDepthShader.bind(); + + // Set the variables of the shader + mDepthShader.setMatrix4x4Uniform("projectionMatrix", shadowMapProjMatrix); + + // Set the viewport to render into the shadow map texture + glViewport(0, 0, SHADOWMAP_WIDTH, SHADOWMAP_HEIGHT); + + // Clear previous frame values + glClear(GL_DEPTH_BUFFER_BIT); + + // Disable color rendering, we only want to write to the Z-Buffer + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + + // Render the objects of the scene + renderSinglePass(mDepthShader, worldToLightCameraMatrix); + + // Unbind the shader + mDepthShader.unbind(); + + mFBOShadowMap.unbind(); + + glDisable(GL_POLYGON_OFFSET_FILL); + } + + // ---------- Render the scene for final rendering (second pass) ----------- // + + glCullFace(GL_BACK); + + // Get the world-space to camera-space matrix + const openglframework::Matrix4 worldToCameraMatrix = mCamera.getTransformMatrix().getInverse(); + + mPhongShader.bind(); + + if (mIsShadowMappingEnabled) mShadowMapTexture.bind(); + const GLuint textureUnit = 0; + + // Set the variables of the shader + mPhongShader.setMatrix4x4Uniform("projectionMatrix", mCamera.getProjectionMatrix()); + mPhongShader.setMatrix4x4Uniform("shadowMapProjectionMatrix", mShadowMapBiasMatrix * shadowMapProjMatrix); + mPhongShader.setMatrix4x4Uniform("worldToLight0CameraMatrix", worldToLightCameraMatrix); + mPhongShader.setVector3Uniform("light0PosCameraSpace", worldToCameraMatrix * mLight0.getOrigin()); + mPhongShader.setVector3Uniform("lightAmbientColor", Vector3(0.4f, 0.4f, 0.4f)); + mPhongShader.setVector3Uniform("light0DiffuseColor", Vector3(diffCol.r, diffCol.g, diffCol.b)); + mPhongShader.setIntUniform("shadowMapSampler", textureUnit); + mPhongShader.setIntUniform("isShadowEnabled", mIsShadowMappingEnabled); + mPhongShader.setVector2Uniform("shadowMapDimension", Vector2(SHADOWMAP_WIDTH, SHADOWMAP_HEIGHT)); + + // Set the viewport to render the scene + glViewport(mViewportX, mViewportY, mViewportWidth, mViewportHeight); + + //Enabling color write (previously disabled for light POV z-buffer rendering) + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + + // Clear previous frame values + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // Render the objects of the scene + renderSinglePass(mPhongShader, worldToCameraMatrix); + + // Render the contact points + if (mIsContactPointsDisplayed) { + renderContactPoints(mPhongShader, worldToCameraMatrix); + } + + if (mIsShadowMappingEnabled) mShadowMapTexture.unbind(); + mPhongShader.unbind(); + + //drawTextureQuad(); +} + +// Create the Shadow map FBO and texture +void SceneDemo::createShadowMapFBOAndTexture() { + + // Create the texture for the depth values + mShadowMapTexture.create(SHADOWMAP_WIDTH, SHADOWMAP_HEIGHT, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, + GL_UNSIGNED_BYTE, GL_NEAREST, GL_NEAREST, GL_CLAMP, GL_CLAMP, NULL); + + // Create the FBO for the shadow map + mFBOShadowMap.create(0, 0, false); + mFBOShadowMap.bind(); + + // Tell OpenGL that we won't bind a color texture with the currently binded FBO + glDrawBuffer(GL_NONE); + glReadBuffer(GL_NONE); + + mFBOShadowMap.attachTexture(GL_DEPTH_ATTACHMENT_EXT, mShadowMapTexture.getID()); + mFBOShadowMap.unbind(); + + mIsShadowMappingInitialized = true; +} + +// Used for debugging shadow maps +void SceneDemo::createQuadVBO() { + + mVAOQuad.create(); + mVAOQuad.bind(); + + static const GLfloat quadVertexData[] = { + -1.0f, -1.0f, 0.0f, + 1.0f, -1.0f, 0.0f, + -1.0f, 1.0f, 0.0f, + -1.0f, 1.0f, 0.0f, + 1.0f, -1.0f, 0.0f, + 1.0f, 1.0f, 0.0f, + }; + + mVBOQuad.create(); + mVBOQuad.bind(); + mVBOQuad.copyDataIntoVBO(sizeof(quadVertexData), quadVertexData, GL_STATIC_DRAW); + mVBOQuad.unbind(); + + mVAOQuad.unbind(); +} + +void SceneDemo::drawTextureQuad() { + + glViewport(mViewportX, mViewportY, mViewportWidth, mViewportHeight); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + + // Clear previous frame values + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + const GLuint textureUnit = 0; + + mVAOQuad.bind(); + mQuadShader.bind(); + mShadowMapTexture.bind(); + mQuadShader.setIntUniform("textureSampler", textureUnit); + mVBOQuad.bind(); + + GLint vertexPositionLoc = mQuadShader.getAttribLocation("vertexPosition"); + glEnableVertexAttribArray(vertexPositionLoc); + + glVertexAttribPointer( + vertexPositionLoc, // attribute 0. No particular reason for 0, but must match the layout in the shader. + 3, // size + GL_FLOAT, // type + GL_FALSE, // normalized? + 0, // stride + (void*)0 // array buffer offset + ); + + // Draw the triangles ! + glDrawArrays(GL_TRIANGLES, 0, 6); // 2*3 indices starting at 0 -> 2 triangles + + glDisableVertexAttribArray(vertexPositionLoc); + + mVBOQuad.unbind(); + mShadowMapTexture.unbind(); + mQuadShader.unbind(); + mVAOQuad.unbind(); +} + +// Gather and create contact points +void SceneDemo::updateContactPoints() { + + // Remove the previous contact points + removeAllContactPoints(); + + if (mIsContactPointsDisplayed) { + + // Get the current contact points of the scene + std::vector contactPoints = getContactPoints(); + + // For each contact point + std::vector::const_iterator it; + for (it = contactPoints.begin(); it != contactPoints.end(); ++it) { + + // Create a visual contact point for rendering + VisualContactPoint* point = new VisualContactPoint(it->point, mMeshFolderPath); + mContactPoints.push_back(point); + } + } +} + +// Render the contact points +void SceneDemo::renderContactPoints(openglframework::Shader& shader, + const openglframework::Matrix4& worldToCameraMatrix) { + + // Render all the raycast hit points + for (std::vector::iterator it = mContactPoints.begin(); + it != mContactPoints.end(); ++it) { + (*it)->render(shader, worldToCameraMatrix); + } +} + +void SceneDemo::removeAllContactPoints() { + + // Destroy all the visual contact points + for (std::vector::iterator it = mContactPoints.begin(); + it != mContactPoints.end(); ++it) { + delete (*it); + } + mContactPoints.clear(); +} + +// Return all the contact points of the scene +std::vector SceneDemo::computeContactPointsOfWorld(const rp3d::DynamicsWorld* world) const { + + std::vector contactPoints; + + // Get the list of contact manifolds from the world + std::vector manifolds = world->getContactsList(); + + // For each contact manifold + std::vector::const_iterator it; + for (it = manifolds.begin(); it != manifolds.end(); ++it) { + + const rp3d::ContactManifold* manifold = *it; + + // For each contact point of the manifold + for (uint i=0; igetNbContactPoints(); i++) { + + rp3d::ContactPoint* contactPoint = manifold->getContactPoint(i); + rp3d::Vector3 point = contactPoint->getWorldPointOnBody1(); + ContactPoint contact(openglframework::Vector3(point.x, point.y, point.z)); + contactPoints.push_back(contact); + } + + } + + return contactPoints; +} diff --git a/testbed/src/SceneDemo.h b/testbed/src/SceneDemo.h new file mode 100644 index 00000000..5ec37b45 --- /dev/null +++ b/testbed/src/SceneDemo.h @@ -0,0 +1,154 @@ +/******************************************************************************** +* ReactPhysics3D physics library, http://www.reactphysics3d.com * +* Copyright (c) 2010-2015 Daniel Chappuis * +********************************************************************************* +* * +* This software is provided 'as-is', without any express or implied warranty. * +* In no event will the authors be held liable for any damages arising from the * +* use of this software. * +* * +* Permission is granted to anyone to use this software for any purpose, * +* including commercial applications, and to alter it and redistribute it * +* freely, subject to the following restrictions: * +* * +* 1. The origin of this software must not be misrepresented; you must not claim * +* that you wrote the original software. If you use this software in a * +* product, an acknowledgment in the product documentation would be * +* appreciated but is not required. * +* * +* 2. Altered source versions must be plainly marked as such, and must not be * +* misrepresented as being the original software. * +* * +* 3. This notice may not be removed or altered from any source distribution. * +* * +********************************************************************************/ + +#ifndef SCENEDEMO_H +#define SCENEDEMO_H + +// Libraries +#include "Scene.h" +#include "VisualContactPoint.h" +#include "reactphysics3d.h" + +// Constants +const int SHADOWMAP_WIDTH = 2048; +const int SHADOWMAP_HEIGHT = 2048; + +// Class SceneDemo +// Abstract class that represents a 3D scene for the ReactPhysics3D examples. +// This scene has a single light source with shadow mapping. +class SceneDemo : public Scene { + + protected: + + // -------------------- Attributes -------------------- // + + /// Light 0 + openglframework::Light mLight0; + + /// True if the shadows FBO, textures have been created + bool mIsShadowMappingInitialized; + + /// FBO for the shadow map + openglframework::FrameBufferObject mFBOShadowMap; + + /// Shadow map texture + openglframework::Texture2D mShadowMapTexture; + + static int shadowMapTextureLevel; + + /// All the visual contact points + std::vector mContactPoints; + + /// Shadow map bias matrix + openglframework::Matrix4 mShadowMapBiasMatrix; + + /// Camera at light0 position for the shadow map + openglframework::Camera mShadowMapLightCamera; + + /// Depth shader to render the shadow map + openglframework::Shader mDepthShader; + + /// Phong shader + openglframework::Shader mPhongShader; + + // TODO : Delete this + openglframework::Shader mQuadShader; + + // TODO : Delete this + openglframework::VertexArrayObject mVAOQuad; + + openglframework::VertexBufferObject mVBOQuad; + + static openglframework::Color mGreyColorDemo; + static openglframework::Color mYellowColorDemo; + static openglframework::Color mBlueColorDemo; + static openglframework::Color mOrangeColorDemo; + static openglframework::Color mPinkColorDemo; + static openglframework::Color mRedColorDemo; + static openglframework::Color mDemoColors[]; + static int mNbDemoColors; + + std::string mMeshFolderPath; + + // -------------------- Methods -------------------- // + + // Create the Shadow map FBO and texture + void createShadowMapFBOAndTexture(); + + // Used for debugging shadow maps + void createQuadVBO(); + + // TODO : Delete this + void drawTextureQuad(); + + // Update the contact points + void updateContactPoints(); + + // Render the contact points + void renderContactPoints(openglframework::Shader& shader, + const openglframework::Matrix4& worldToCameraMatrix); + + void removeAllContactPoints(); + + public: + + // -------------------- Methods -------------------- // + + /// Constructor + SceneDemo(const std::string& name, float sceneRadius, bool isShadowMappingEnabled = true); + + /// Destructor + virtual ~SceneDemo(); + + /// Update the scene + virtual void update(); + + /// Render the scene (possibly in multiple passes for shadow mapping) + virtual void render(); + + /// Render the scene in a single pass + virtual void renderSinglePass(openglframework::Shader& shader, + const openglframework::Matrix4& worldToCameraMatrix)=0; + + /// Enabled/Disable the shadow mapping + void virtual setIsShadowMappingEnabled(bool isShadowMappingEnabled); + + /// Return all the contact points of the scene + std::vector computeContactPointsOfWorld(const rp3d::DynamicsWorld* world) const; +}; + +// Enabled/Disable the shadow mapping +inline void SceneDemo::setIsShadowMappingEnabled(bool isShadowMappingEnabled) { + + Scene::setIsShadowMappingEnabled(isShadowMappingEnabled); + + if (mIsShadowMappingEnabled && !mIsShadowMappingInitialized) { + createShadowMapFBOAndTexture(); + } +} + +#endif + + diff --git a/testbed/src/TestbedApplication.cpp b/testbed/src/TestbedApplication.cpp new file mode 100644 index 00000000..fc958671 --- /dev/null +++ b/testbed/src/TestbedApplication.cpp @@ -0,0 +1,453 @@ +/******************************************************************************** +* ReactPhysics3D physics library, http://www.reactphysics3d.com * +* Copyright (c) 2010-2015 Daniel Chappuis * +********************************************************************************* +* * +* This software is provided 'as-is', without any express or implied warranty. * +* In no event will the authors be held liable for any damages arising from the * +* use of this software. * +* * +* Permission is granted to anyone to use this software for any purpose, * +* including commercial applications, and to alter it and redistribute it * +* freely, subject to the following restrictions: * +* * +* 1. The origin of this software must not be misrepresented; you must not claim * +* that you wrote the original software. If you use this software in a * +* product, an acknowledgment in the product documentation would be * +* appreciated but is not required. * +* * +* 2. Altered source versions must be plainly marked as such, and must not be * +* misrepresented as being the original software. * +* * +* 3. This notice may not be removed or altered from any source distribution. * +* * +********************************************************************************/ + +// Libraries +#include "TestbedApplication.h" +#include "openglframework.h" +#include +#include +#include +#include "cubes/CubesScene.h" +#include "joints/JointsScene.h" +#include "collisionshapes/CollisionShapesScene.h" +#include "raycast/RaycastScene.h" + +using namespace openglframework; +using namespace jointsscene; +using namespace cubesscene; +using namespace raycastscene; +using namespace collisionshapesscene; + +// Initialization of static variables +const float TestbedApplication::SCROLL_SENSITIVITY = 0.02f; + +// Create and return the singleton instance of this class +TestbedApplication& TestbedApplication::getInstance() { + static TestbedApplication instance; + return instance; +} + +// Constructor +TestbedApplication::TestbedApplication() + : mFPS(0), mNbFrames(0), mPreviousTime(0), + mUpdateTime(0), mPhysicsUpdateTime(0) { + + mCurrentScene = NULL; + mIsMultisamplingActive = true; + mWidth = 1280; + mHeight = 720; + mSinglePhysicsStepEnabled = false; + mSinglePhysicsStepDone = false; + mWindowToFramebufferRatio = Vector2(1, 1); + mIsShadowMappingEnabled = true; + mIsVSyncEnabled = true; + mIsContactPointsDisplayed = false; +} + +// Destructor +TestbedApplication::~TestbedApplication() { + + // Destroy all the scenes + destroyScenes(); + + // Destroy the window + glfwDestroyWindow(mWindow); + + // Terminate GLFW + glfwTerminate(); +} + +// Initialize the viewer +void TestbedApplication::init() { + + // Set the GLFW error callback method + glfwSetErrorCallback(error_callback); + + // Initialize the GLFW library + if (!glfwInit()) { + std::exit(EXIT_FAILURE); + } + + // OpenGL version required + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); + + // Active the multi-sampling by default + if (mIsMultisamplingActive) { + glfwWindowHint(GLFW_SAMPLES, 4); + } + + // Create the GLFW window + mWindow = glfwCreateWindow(mWidth, mHeight, + "ReactPhysics3D Testbed", NULL, NULL); + if (!mWindow) { + glfwTerminate(); + std::exit(EXIT_FAILURE); + } + glfwMakeContextCurrent(mWindow); + + // Vertical Synchronization + enableVSync(mIsVSyncEnabled); + + // Initialize the GLEW library + glewExperimental = GL_TRUE; + GLenum errorGLEW = glewInit(); + if (errorGLEW != GLEW_OK) { + + // Problem: glewInit failed, something is wrong + std::cerr << "GLEW Error : " << glewGetErrorString(errorGLEW) << std::endl; + assert(false); + std::exit(EXIT_FAILURE); + } + + if (mIsMultisamplingActive) { + glEnable(GL_MULTISAMPLE); + } + + glfwSetKeyCallback(mWindow, keyboard); + glfwSetMouseButtonCallback(mWindow, mouseButton); + glfwSetCursorPosCallback(mWindow, mouseMotion); + glfwSetScrollCallback(mWindow, scroll); + + // Define the background color (black) + glClearColor(0, 0, 0, 1.0); + + // Create all the scenes + createScenes(); + + Gui::getInstance().setWindow(mWindow); + + // Init the GUI + Gui::getInstance().init(); + + mTimer.start(); +} + +// Create all the scenes +void TestbedApplication::createScenes() { + + // Cubes scene + CubesScene* cubeScene = new CubesScene("Cubes"); + mScenes.push_back(cubeScene); + + // Joints scene + JointsScene* jointsScene = new JointsScene("Joints"); + mScenes.push_back(jointsScene); + + // Collision shapes scene + CollisionShapesScene* collisionShapesScene = new CollisionShapesScene("Collision Shapes"); + mScenes.push_back(collisionShapesScene); + + // Raycast scene + RaycastScene* raycastScene = new RaycastScene("Raycast"); + mScenes.push_back(raycastScene); + + assert(mScenes.size() > 0); + mCurrentScene = mScenes[0]; + + // Get the engine settings from the scene + mEngineSettings = mCurrentScene->getEngineSettings(); + mEngineSettings.timeStep = DEFAULT_TIMESTEP; +} + +// Remove all the scenes +void TestbedApplication::destroyScenes() { + + for (int i=0; iupdatePhysics(); +} + +// Update the physics of the current scene +void TestbedApplication::updatePhysics() { + + // Set the engine settings + mEngineSettings.elapsedTime = mTimer.getPhysicsTime(); + mCurrentScene->setEngineSettings(mEngineSettings); + + if (mTimer.isRunning()) { + + // Compute the time since the last update() call and update the timer + mTimer.update(); + + // While the time accumulator is not empty + while(mTimer.isPossibleToTakeStep(mEngineSettings.timeStep)) { + + // Take a physics simulation step + mCurrentScene->updatePhysics(); + + // Update the timer + mTimer.nextStep(mEngineSettings.timeStep); + } + } +} + +void TestbedApplication::update() { + + double currentTime = glfwGetTime(); + + // Update the physics + if (mSinglePhysicsStepEnabled && !mSinglePhysicsStepDone) { + updateSinglePhysicsStep(); + mSinglePhysicsStepDone = true; + } + else { + updatePhysics(); + } + + // Compute the physics update time + mPhysicsUpdateTime = glfwGetTime() - currentTime; + + // Compute the interpolation factor + float factor = mTimer.computeInterpolationFactor(mEngineSettings.timeStep); + assert(factor >= 0.0f && factor <= 1.0f); + + // Notify the scene about the interpolation factor + mCurrentScene->setInterpolationFactor(factor); + + // Enable/Disable shadow mapping + mCurrentScene->setIsShadowMappingEnabled(mIsShadowMappingEnabled); + + // Display/Hide contact points + mCurrentScene->setIsContactPointsDisplayed(mIsContactPointsDisplayed); + + // Update the scene + mCurrentScene->update(); +} + +// Render +void TestbedApplication::render() { + + int bufferWidth, bufferHeight; + glfwGetFramebufferSize(mWindow, &bufferWidth, &bufferHeight); + + int windowWidth, windowHeight; + glfwGetWindowSize(mWindow, &windowWidth, &windowHeight); + + // Compute the window to framebuffer ratio + mWindowToFramebufferRatio.x = float(bufferWidth) / float(windowWidth); + mWindowToFramebufferRatio.y = float(bufferHeight) / float(windowHeight); + + // Set the viewport of the scene + mCurrentScene->setViewport(mWindowToFramebufferRatio.x * LEFT_PANE_WIDTH, + 0, + bufferWidth - mWindowToFramebufferRatio.x * LEFT_PANE_WIDTH, + bufferHeight); + + // Render the scene + mCurrentScene->render(); + + // Display the GUI + Gui::getInstance().render(); + + // Check the OpenGL errors + checkOpenGLErrors(); +} + +// Set the dimension of the camera viewport +void TestbedApplication::reshape() { + + // Get the framebuffer dimension + int width, height; + glfwGetFramebufferSize(mWindow, &width, &height); + + // Resize the camera viewport + mCurrentScene->reshape(width - LEFT_PANE_WIDTH, height); + + // Update the window size of the scene + int windowWidth, windowHeight; + glfwGetWindowSize(mWindow, &windowWidth, &windowHeight); + mCurrentScene->setWindowDimension(windowWidth, windowHeight); +} + +// Start the main loop where rendering occur +void TestbedApplication::startMainLoop() { + + // Loop until the user closes the window + while (!glfwWindowShouldClose(mWindow)) { + + checkOpenGLErrors(); + + // Reshape the viewport + reshape(); + + // Call the update function + update(); + + // Render the application + render(); + + // Swap front and back buffers + glfwSwapBuffers(mWindow); + + // Process events + glfwPollEvents(); + + // Compute the current framerate + computeFPS(); + + checkOpenGLErrors(); + } +} + +// Change the current scene +void TestbedApplication::switchScene(Scene* newScene) { + + if (newScene == mCurrentScene) return; + + mCurrentScene = newScene; + + // Get the engine settings of the scene + float currentTimeStep = mEngineSettings.timeStep; + mEngineSettings = mCurrentScene->getEngineSettings(); + mEngineSettings.timeStep = currentTimeStep; + + // Reset the scene + mCurrentScene->reset(); +} + +// Check the OpenGL errors +void TestbedApplication::checkOpenGLErrors() { + GLenum glError; + + // Get the OpenGL errors + glError = glGetError(); + + // While there are errors + while (glError != GL_NO_ERROR) { + + // Get the error string + const GLubyte* stringError = gluErrorString(glError); + + // Display the error + if (stringError) + std::cerr << "OpenGL Error #" << glError << "(" << gluErrorString(glError) << ")" << std::endl; + else + std::cerr << "OpenGL Error #" << glError << " (no message available)" << std::endl; + + // Get the next error + glError = glGetError(); + } +} + + +// Compute the FPS +void TestbedApplication::computeFPS() { + + mNbFrames++; + + // Get the number of seconds since start + mCurrentTime = glfwGetTime(); + + // Calculate time passed + mUpdateTime = mCurrentTime - mPreviousTime; + double timeInterval = mUpdateTime * 1000.0; + + // Update the FPS counter each second + if(timeInterval > 0.0001) { + + // calculate the number of frames per second + mFPS = static_cast(mNbFrames) / timeInterval; + mFPS *= 1000.0; + + // Set time + mPreviousTime = mCurrentTime; + + // Reset frame count + mNbFrames = 0; + } +} + +// GLFW error callback method +void TestbedApplication::error_callback(int error, const char* description) { + fputs(description, stderr); +} + +// Display the GUI +void TestbedApplication::displayGUI() { + + // Display the FPS + displayFPS(); +} + +// Display the FPS +void TestbedApplication::displayFPS() { + + /* + std::stringstream ss; + ss << mFPS; + std::string fpsString = ss.str(); + std::string windowTitle = mWindowTitle + " | FPS : " + fpsString; + glfwSetWindowTitle(mWindow, windowTitle.c_str()); + */ +} + +// Callback method to receive keyboard events +void TestbedApplication::keyboard(GLFWwindow* window, int key, int scancode, + int action, int mods) { + getInstance().mCurrentScene->keyboardEvent(key, scancode, action, mods); +} + +// Callback method to receive scrolling events +void TestbedApplication::scroll(GLFWwindow* window, double xAxis, double yAxis) { + + // Update scroll on the GUI + Gui::getInstance().setScroll(xAxis, yAxis); + + getInstance().mCurrentScene->scrollingEvent(xAxis, yAxis, SCROLL_SENSITIVITY); +} + +// Called when a mouse button event occurs +void TestbedApplication::mouseButton(GLFWwindow* window, int button, int action, int mods) { + + // Get the mouse cursor position + double x, y; + glfwGetCursorPos(window, &x, &y); + + getInstance().mCurrentScene->mouseButtonEvent(button, action, mods, x, y); +} + +// Called when a mouse motion event occurs +void TestbedApplication::mouseMotion(GLFWwindow* window, double x, double y) { + + int leftButtonState = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT); + int rightButtonState = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_RIGHT); + int middleButtonState = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_MIDDLE); + int altKeyState = glfwGetKey(window, GLFW_KEY_LEFT_ALT); + + getInstance().mCurrentScene->mouseMotionEvent(x, y, leftButtonState, rightButtonState, + middleButtonState, altKeyState); +} diff --git a/testbed/src/TestbedApplication.h b/testbed/src/TestbedApplication.h new file mode 100644 index 00000000..6abd7b8a --- /dev/null +++ b/testbed/src/TestbedApplication.h @@ -0,0 +1,264 @@ +/******************************************************************************** +* ReactPhysics3D physics library, http://www.reactphysics3d.com * +* Copyright (c) 2010-2015 Daniel Chappuis * +********************************************************************************* +* * +* This software is provided 'as-is', without any express or implied warranty. * +* In no event will the authors be held liable for any damages arising from the * +* use of this software. * +* * +* Permission is granted to anyone to use this software for any purpose, * +* including commercial applications, and to alter it and redistribute it * +* freely, subject to the following restrictions: * +* * +* 1. The origin of this software must not be misrepresented; you must not claim * +* that you wrote the original software. If you use this software in a * +* product, an acknowledgment in the product documentation would be * +* appreciated but is not required. * +* * +* 2. Altered source versions must be plainly marked as such, and must not be * +* misrepresented as being the original software. * +* * +* 3. This notice may not be removed or altered from any source distribution. * +* * +********************************************************************************/ + +#ifndef TESTBED_APPLICATION_H +#define TESTBED_APPLICATION_H + +// Libraries +#include "openglframework.h" +#include "GUI.h" +#include "Scene.h" +#include "Timer.h" +#include + +// Constants +const float DEFAULT_TIMESTEP = 1.0f / 60.0f; + +/// Class TestbedApplication +/// Singleton class representing the application. +class TestbedApplication { + + private : + + // -------------------- Constants -------------------- // + + static const float SCROLL_SENSITIVITY; + + // -------------------- Attributes -------------------- // + + /// GLFW window + GLFWwindow* mWindow; + + /// Timer + Timer mTimer; + + /// List of 3D scenes + std::vector mScenes; + + /// Current 3D scene + Scene* mCurrentScene; + + /// Physics engine settings + EngineSettings mEngineSettings; + + /// Current number of frames per seconds + double mFPS; + + /// Number of frames during the last second + int mNbFrames; + + /// Current time for fps computation (in seconds) + double mCurrentTime; + + /// Previous time for fps computation (in seconds) + double mPreviousTime; + + /// Update time (in seconds) + double mUpdateTime; + + /// Physics update time (in seconds) + double mPhysicsUpdateTime; + + /// True if multisampling is active + bool mIsMultisamplingActive; + + /// Width and height of the window + int mWidth, mHeight; + + /// True if the next simulation update is a single physics step + bool mSinglePhysicsStepEnabled; + + /// True if the single physics step has been taken already + bool mSinglePhysicsStepDone; + + openglframework::Vector2 mWindowToFramebufferRatio; + + /// True if shadow mapping is enabled + bool mIsShadowMappingEnabled; + + /// True if contact points are displayed + bool mIsContactPointsDisplayed; + + /// True if vsync is enabled + bool mIsVSyncEnabled; + + // -------------------- Methods -------------------- // + + /// Private constructor (for the singleton class) + TestbedApplication(); + + /// Private copy-constructor (for the singleton class) + TestbedApplication(TestbedApplication const&); + + /// Private assignment operator (for the singleton class) + void operator=(TestbedApplication const&); + + /// Update the physics of the current scene + void updatePhysics(); + + /// Update + void update(); + + /// Update the simulation by taking a single physics step + void updateSinglePhysicsStep(); + + /// Called when the windows is reshaped + void reshape(); + + /// Render + void render(); + + /// Check the OpenGL errors + static void checkOpenGLErrors(); + + /// Display the FPS + void displayFPS(); + + /// Compute the FPS + void computeFPS(); + + /// Display the GUI + void displayGUI(); + + /// GLFW error callback method + static void error_callback(int error, const char* description); + + /// Callback method to receive keyboard events + static void keyboard(GLFWwindow* window, int key, int scancode, int action, int mods); + + /// Callback method to receive scrolling events + static void scroll(GLFWwindow* window, double xAxis, double yAxis); + + /// Called when a mouse button event occurs + static void mouseButton(GLFWwindow* window, int button, int action, int mods); + + /// Called when a mouse motion event occurs + static void mouseMotion(GLFWwindow* window, double x, double y); + + /// Initialize all the scenes + void createScenes(); + + /// Remove all the scenes + void destroyScenes(); + + /// Return the list of the scenes + std::vector getScenes(); + + /// Start/stop the simulation + void togglePlayPauseSimulation(); + + /// Restart the simulation + void restartSimulation(); + + /// Set the variable to know if we need to take a single physics step + void toggleTakeSinglePhysicsStep(); + + /// Enable/Disable shadow mapping + void enableShadows(bool enable); + + /// Display/Hide contact points + void displayContactPoints(bool display); + + public : + + // -------------------- Methods -------------------- // + + /// Create and return the singleton instance of this class + static TestbedApplication& getInstance(); + + /// Destructor + ~TestbedApplication(); + + /// Initialize the application + void init(); + + /// Start the main loop where rendering occur + void startMainLoop(); + + /// Change the current scene + void switchScene(Scene* newScene); + + /// Enable/Disable Vertical synchronization + void enableVSync(bool enable); + + // -------------------- Friendship -------------------- // + + friend class Gui; +}; + +// Return the list of the scenes +inline std::vector TestbedApplication::getScenes() { + return mScenes; +} + +// Start the simulation +inline void TestbedApplication::togglePlayPauseSimulation() { + + if (mTimer.isRunning()) { + mTimer.stop(); + } + else { + mTimer.start(); + } +} + +// Restart the simulation +inline void TestbedApplication::restartSimulation() { + mCurrentScene->reset(); + mTimer.start(); +} + +// Take a single step of simulation +inline void TestbedApplication::toggleTakeSinglePhysicsStep() { + mSinglePhysicsStepEnabled = true; + mSinglePhysicsStepDone = false; + + if (mTimer.isRunning()) { + mSinglePhysicsStepEnabled = false; + } +} + +// Enable/Disable shadow mapping +inline void TestbedApplication::enableShadows(bool enable) { + mIsShadowMappingEnabled = enable; +} + +/// Display/Hide contact points +inline void TestbedApplication::displayContactPoints(bool display) { + mIsContactPointsDisplayed = display; +} + +// Enable/Disable Vertical synchronization +inline void TestbedApplication::enableVSync(bool enable) { + mIsVSyncEnabled = enable; + if (mIsVSyncEnabled) { + glfwSwapInterval(1); + } + else { + glfwSwapInterval(0); + } +} + +#endif diff --git a/src/engine/Timer.cpp b/testbed/src/Timer.cpp similarity index 93% rename from src/engine/Timer.cpp rename to testbed/src/Timer.cpp index 30698fd4..a7849534 100644 --- a/src/engine/Timer.cpp +++ b/testbed/src/Timer.cpp @@ -26,12 +26,10 @@ // Libraries #include "Timer.h" -// We want to use the ReactPhysics3D namespace -using namespace reactphysics3d; // Constructor -Timer::Timer(double timeStep) : mTimeStep(timeStep), mIsRunning(false) { - assert(timeStep > 0.0); +Timer::Timer() : mIsRunning(false) { + } // Destructor diff --git a/src/engine/Timer.h b/testbed/src/Timer.h similarity index 82% rename from src/engine/Timer.h rename to testbed/src/Timer.h index be7a7cac..0a75d010 100644 --- a/src/engine/Timer.h +++ b/testbed/src/Timer.h @@ -23,8 +23,8 @@ * * ********************************************************************************/ -#ifndef REACTPHYSICS3D_TIMER_H -#define REACTPHYSICS3D_TIMER_H +#ifndef TIMER_H +#define TIMER_H // Libraries #include @@ -40,10 +40,6 @@ #include #endif - -/// Namespace ReactPhysics3D -namespace reactphysics3d { - // Class Timer /** * This class will take care of the time in the physics engine. It @@ -56,9 +52,6 @@ class Timer { // -------------------- Attributes -------------------- // - /// Timestep dt of the physics engine (timestep > 0.0) - double mTimeStep; - /// Last time the timer has been updated long double mLastUpdateTime; @@ -84,17 +77,11 @@ class Timer { // -------------------- Methods -------------------- // /// Constructor - Timer(double timeStep); + Timer(); /// Destructor virtual ~Timer(); - /// Return the timestep of the physics engine - double getTimeStep() const; - - /// Set the timestep of the physics engine - void setTimeStep(double timeStep); - /// Return the current time of the physics engine long double getPhysicsTime() const; @@ -105,42 +92,31 @@ class Timer { void stop(); /// Return true if the timer is running - bool getIsRunning() const; + bool isRunning() const; /// True if it's possible to take a new step - bool isPossibleToTakeStep() const; + bool isPossibleToTakeStep(float timeStep) const; /// Compute the time since the last update() call and add it to the accumulator void update(); /// Take a new step => update the timer by adding the timeStep value to the current time - void nextStep(); + void nextStep(float timeStep); /// Compute the interpolation factor - decimal computeInterpolationFactor(); + float computeInterpolationFactor(float timeStep); /// Return the current time of the system in seconds static long double getCurrentSystemTime(); }; -// Return the timestep of the physics engine -inline double Timer::getTimeStep() const { - return mTimeStep; -} - -// Set the timestep of the physics engine -inline void Timer::setTimeStep(double timeStep) { - assert(timeStep > 0.0f); - mTimeStep = timeStep; -} - // Return the current time inline long double Timer::getPhysicsTime() const { return mLastUpdateTime; } // Return if the timer is running -inline bool Timer::getIsRunning() const { +inline bool Timer::isRunning() const { return mIsRunning; } @@ -162,21 +138,21 @@ inline void Timer::stop() { } // True if it's possible to take a new step -inline bool Timer::isPossibleToTakeStep() const { - return (mAccumulator >= mTimeStep); +inline bool Timer::isPossibleToTakeStep(float timeStep) const { + return (mAccumulator >= timeStep); } // Take a new step => update the timer by adding the timeStep value to the current time -inline void Timer::nextStep() { +inline void Timer::nextStep(float timeStep) { assert(mIsRunning); // Update the accumulator value - mAccumulator -= mTimeStep; + mAccumulator -= timeStep; } // Compute the interpolation factor -inline decimal Timer::computeInterpolationFactor() { - return (decimal(mAccumulator / mTimeStep)); +inline float Timer::computeInterpolationFactor(float timeStep) { + return (float(mAccumulator) / timeStep); } // Compute the time since the last update() call and add it to the accumulator @@ -193,8 +169,6 @@ inline void Timer::update() { // Update the accumulator value mAccumulator += mDeltaTime; -} - } #endif