Loading age @ e48cfb1e Compare dbf48ebc to e48cfb1e Original line number Diff line number Diff line Subproject commit dbf48ebcbba760f0c5469a7e26042c076b1ec3c2 Subproject commit e48cfb1e0e6fea04804a4bbc0de2794fa2afd1c7 src/tut_collisions/include/tut_collisions/presenter.hpp +51 −17 Original line number Diff line number Diff line Loading @@ -11,6 +11,7 @@ # include "osi/index.hpp" #include "phx/body_system.hpp" # include "phx/collisions/collision_info.hpp" #include "phx/collisions/collision_system.hpp" namespace com { struct Frame; Loading @@ -28,6 +29,7 @@ namespace tut_collisions { ~Presenter() override; void next_round() override; void next_round_tutorial(); com::Folder* light_directional() const; com::Folder* light_ambient() const; Loading @@ -44,6 +46,8 @@ namespace tut_collisions { void handle_inputs(); void get_collisions(); void draw_aabbs(); void draw_debug_colliders(); Loading Loading @@ -84,7 +88,7 @@ namespace tut_collisions { com::Folder* m_world; // Debug drawing bool m_draw_objects = true; bool m_draw_objects_transparent = false; bool m_draw_aabbs = false; bool m_draw_debug_colliders = false; bool m_is_sim_running = false; Loading Loading @@ -179,7 +183,7 @@ namespace tut_collisions { // Helper function to create a material for the crate inline void Presenter::create_gfx_materials() { // Crate material auto shader_crate = gfx::shader_system()->insert_shader<Graph>({name()}, "box-shader", Graph::LIT); auto shader_crate = gfx::shader_system()->insert_shader<Graph>({name()}, "box-shader", Graph::LIT, Graph::PHONG, Graph::PARTIAL); auto normal = shader_crate->insert<Graph::ConstantNode>(vec3{0.5, 0.5, 1.}); shader_crate->connect(shader_crate->root(), Graph::MasterNodeLit::inputs::normal_ts, normal, 0); Loading @@ -192,6 +196,9 @@ namespace tut_collisions { shader_crate->connect(shader_crate->root(), Graph::MasterNodeLit::inputs::diffuse, container, 0); shader_crate->connect(shader_crate->root(), Graph::MasterNodeLit::inputs::specular, container, 0); auto alpha = shader_crate->insert<Graph::ConstantNode>(0.5f); shader_crate->connect(shader_crate->root(), Graph::MasterNodeLit::inputs::alpha, alpha, 0); auto emission = shader_crate->insert<Graph::ConstantNode>(vec3{0.}); shader_crate->connect(shader_crate->root(), Graph::MasterNodeLit::inputs::emission, emission, 0); Loading Loading @@ -299,7 +306,7 @@ namespace tut_collisions { // Debug shapes auto line_mesh = gfx::buffer_generators()->insert_procedural_box_wireframe( vec3{0.001, 0.001, 1}, "line_mesh", {"tutorial"}); m_gfx_instances_lines = gfx::object_system()->insert_object({"tutorial", "lines"}, m_material_blue, line_mesh); m_gfx_instances_lines = gfx::object_system()->insert_object({"tutorial", "lines"}, m_material_cyan, line_mesh); auto sphere_ind_mesh = gfx::buffer_generators()->insert_procedural_sphere_solid( 0.025f, 10, "sphere_ind_mesh", {"tutorial"}); Loading Loading @@ -334,22 +341,46 @@ namespace tut_collisions { const auto insert_text = [&](const std::string& text, size_t line, float offset_left = 0.0f) { auto* static_text = gfx::ui_generators()->create_ui_static_text( "static_text_" + std::to_string(line), vec2i{1000, 50}, nullptr, ui_scene, ui_layer, text, false); static_text->get_transform()->set_anchor(1.0f, offset_left, (static_cast<float>(line) * 0.1f), 1.0f); static_text->get_transform()->set_anchor(1.0f, offset_left, (static_cast<float>(line) * 0.025f) + 0.1f, 1.0f); static_text->register_handler(buffer_handler); static_text->request_fonts_to_load("OpenSans", 48U, true, true); static_text->request_fonts_to_load("OpenSans", 12U, true, true); static_text->set_horizontal_alignment(gfx::StaticText::LEFT); static_text->set_vertical_alignment(gfx::StaticText::TOP); gfx::ui_system()->insert_objects(static_text); }; insert_text("1-6 - Select objects", 0); insert_text("F1 - Show/hide objects", 1); insert_text("F2 - Show/hide AABBs", 2); insert_text("F3 - Show/hide debug colliders", 3); insert_text("Arrow keys - Move selected object", 4); insert_text("Insert/Delete/Home/End - Rotate selected object", 5); insert_text("Comma/Period - Precision movement", 6); insert_text("1-6 - Select objects", 1); insert_text("F1 - Show/hide objects", 2); insert_text("F2 - Show/hide AABBs", 3); insert_text("F3 - Show/hide debug colliders", 4); insert_text("Arrow keys - Move selected object", 5); insert_text("Insert/Delete/Home/End - Rotate selected object", 6); insert_text("Comma/Period - Precision movement", 7); } inline void Presenter::next_round() { // Clear the debug shapes clear_lines(); clear_spheres(); handle_inputs(); next_round_tutorial(); // Debug drawing get_collisions(); if (m_draw_aabbs) draw_aabbs(); if (m_draw_debug_colliders) draw_debug_colliders(); // Rendering gfx::renderer()->clear_render_buffers(); gfx::camera_system()->activate_default_camera(); gfx::renderer()->present_collection(gfx::object_system()->objects()->find<com::Folder>("tutorial"), m_draw_objects_transparent ? gfx::Renderer::pipeline::TRANSPARENT : gfx::Renderer::pipeline::FORWARD); gfx::ui_system()->present_current_scene(true, 2.0f); } inline void Presenter::handle_inputs() { Loading Loading @@ -384,13 +415,9 @@ namespace tut_collisions { m_selected_object = &m_body_convex; } if (osi::keyboard()->just_released().contains(SDL_GetKeyName(SDLK_SPACE))) { m_is_sim_running = !m_is_sim_running; } // Debug toggles if (osi::keyboard()->just_released().contains(SDL_GetKeyName(SDLK_F1))) { m_draw_objects = !m_draw_objects; m_draw_objects_transparent = !m_draw_objects_transparent; } if (osi::keyboard()->just_released().contains(SDL_GetKeyName(SDLK_F2))) { Loading Loading @@ -464,6 +491,11 @@ namespace tut_collisions { } } inline void Presenter::get_collisions() { m_broad_collisions = phx::collision_system()->get_broad_collisions(m_world); m_narrow_collisions = phx::collision_system()->get_collisions(m_world); } inline void Presenter::draw_aabbs() { const auto objects = std::vector({m_body_box, m_body_sphere, m_body_cylinder, m_body_cone, m_body_capsule, m_body_convex}); Loading Loading @@ -537,6 +569,8 @@ namespace tut_collisions { const auto direction = normalize(destination - origin); const auto length = distance(origin, destination); if (length < Num::epsilon) return; const auto direction_quat = look_at(direction, vec3{0, 0, 1}, vec3{0, 1, 0}); const auto frame = m_lines_frame_folder->push_back<com::Folder>(m_lines_frame_folder->generate_unique_name("line"))->push_back<com::Frame>(); Loading src/tut_collisions/src/presenter.cpp +141 −18 Original line number Diff line number Diff line Loading @@ -40,13 +40,29 @@ namespace tut_collisions { phx::body_system()->insert_sphere_collider(sphere_box, 0.5f); phx::body_system()->insert_cylinder_collider(cylinder_box, 0.5f, 2.0f); // We can also use a slightly different syntax. The following two lines are equivalent. phx::body_system()->insert_collider<phx::ConeCollider>(cone_box, 0.5f, 1.0f); phx::body_system()->insert_collider<phx::CapsuleCollider>(capsule_box, 0.5f, 2.0f); // To insert a collider into a different physics layer, we can pass an optional parameter to the insert_collider // function; by default, if no parameter is passed, the collider is inserted into the default layer. // Let's create a new layer and insert the rest of the colliders into it, we can do so by using the layer_system // module. auto layer = phx::layer_system()->create_layer("new_layer"); // By default, the new layer interacts will all other layers, but you can supply an optional parameter to the // create_layer function to say, if that layer should interact with all other layers or not. // If you want to specifically exclude a layer from interacting with another layer, you'll have to modify the // collision_matrix, like so: phx::collision_matrix()->disable_collision(phx::layer_system()->default_layer(), layer); // We have now disabled the default layer from interacting with the new layer. // Let's create the rest of the colliders with the new layer. phx::body_system()->insert_cone_collider(cone_box, 0.5f, 1.0f, layer); // If you wish to set a layer to a collider later, you can do so in the body_system module. phx::body_system()->insert_capsule_collider(capsule_box, 0.5f, 2.0f); phx::body_system()->insert_layer(capsule_box, layer); // We can also explicitly declare a convex polyhedron as the collider. This requires the vertices and indices of // the polyhedron to be passed to the insert_collider function. The polyhedron must be convex. phx::body_system()->insert_collider<phx::ConvexHullCollider>(convex_box, m_rand_mesh_vertices, m_rand_mesh_indices); phx::body_system()->insert_convex_hull_collider(convex_box, m_rand_mesh_vertices, m_rand_mesh_indices, layer); // We'll create the world and insert the bodies into it. m_world = phx::world_system()->insert_world({"world"}); Loading @@ -73,6 +89,15 @@ namespace tut_collisions { gfx::object_system()->push_frame_back(m_gfx_instances_capsule, capsule_frame); gfx::object_system()->push_frame_back(m_gfx_instances_convex, convex_frame); // If you wish to exclude two specific instances from colliding, you can exclude them in the collision_matrix. // For example, if you want to exclude the box and the sphere from colliding, you can do so like this: auto box_instance_collider = phx::body_system()->get_collider(m_body_box); auto sphere_instance_collider = phx::body_system()->get_collider(m_body_sphere); phx::collision_matrix()->override_colliders({box_instance_collider, sphere_instance_collider}, false); // To remove the exclusion, you can do so like this: // phx::collision_matrix()->remove_override({box_instance_collider, sphere_instance_collider}); // Lastly we'll position the shapes around the origin of the world. box_frame->set_origin({5, 0, 0}); sphere_frame->set_origin({2, 5, 0}); Loading @@ -81,28 +106,126 @@ namespace tut_collisions { capsule_frame->set_origin({-2, -5, 0}); convex_frame->set_origin({2, -5, 0}); // Running the simulation now, using the debug features, you should see the shapes, their colliders and the // bounding boxes of the colliders in the world. // Running the simulation now, using the debug features, you'll be able to see the shapes in the world and if // you press F3, you'll get a wireframe view of the colliders. // The colliders, however, don't interact with each other yet. We'll implement that in the next step. } void Presenter::next_round_tutorial() { // # 2. DETECTING COLLISIONS # // Now that we have successfully set up the bodies and colliders, we can now detect the collisions between them. // We can now just simply call the detect_collisions function in the collision_system module. This will populate // a file, within the root folder of the world, with the appropriate collision information about colliding // pairs of colliders. // Let's set it up so that we can start and stop the collision detection by pressing the 'space' key. For this // there is a member boolean m_is_sim_running that we'll just toggle when the 'space' key is pressed. if (osi::keyboard()->just_released().contains(SDL_GetKeyName(SDLK_SPACE))) { m_is_sim_running = !m_is_sim_running; } void Presenter::next_round() { handle_inputs(); clear_lines(); clear_spheres(); // Now, if the simulation is running, we'll simply call the detect_collisions function. // We can specify the broad and narrow phase algorithms to use in the function call. This is important to do // in the first call of the function, as the system creates the necessary data structures based on the types // specified. // You can however change the broad and narrow phase algorithms on the fly, but beware, as this will clear the // cache of the previous algorithm and will cause the system to rebuild some of the data structures. // For broad phase algorithms, you can choose between: // - Bruteforce - phx::BroadPhaseType::Brute // - The simplest algorithm, compares every pair of colliders, having a complexity of O(n^2). However, it // can be useful and even faster for small scenes, as it doesn't require any preprocessing or data // structures. // - Sweep and Prune - phx::BroadPhaseType::SweepAndPrune // - A more efficient algorithm, having a complexity of O(n+m), where n is the number of colliders and m is // the number of overlapping pairs. // - Dynamic AABB Tree - phx::BroadPhaseType::AABBTree // - A more advanced algorithm, having a complexity of O(n log n), where n is the number of colliders. // // For narrow phase algorithms, you can choose between: // - GJK - phx::NarrowPhaseType::GJK // - A fast and robust algorithm for convex shapes; works with implicit shapes. The implementation uses // the GJK algorithm with the EPA algorithm for calculating the deepest penetration depth. // - V-Clip - phx::NarrowPhaseType::VClip // - A more advanced algorithm for convex shapes; works with explicit shapes. It returns the specific // two features which are either colliding or closest to each other. if (m_is_sim_running) { phx::collision_system()->detect_collisions(m_world, phx::BroadPhaseType::SweepAndPrune, phx::NarrowPhaseType::GJK); // Feel free to swap between the algorithms to see how they affect the performance of the simulation and // how they act in different scenarios. const auto collision_file = phx::collision_system()->detect_collisions(m_world, phx::BroadPhaseType::SweepAndPrune, phx::NarrowPhaseType::GJK); } if (m_draw_aabbs) draw_aabbs(); if (m_draw_debug_colliders) draw_debug_colliders(); gfx::renderer()->clear_render_buffers(); // If you run the simulation now, you can enable the AABB's by pressing F2, where you'll see the AABB's changing // color based on the collision status of the colliders. Green means no collision, orange means they collided // in the broad phase and red means they collided in the narrow phase. // Let's move on to the next step where we'll use some of the information from the collisions. // # 3. VISUALISING COLLISIONS # // We'll now use the information from the collisions to draw points at the collision points. We will use the // debug functions to draw the points, which you can explore on your own in the header file. // First we'll iterate over the collisions and get the points of the collisions. // We can get the collisions by calling the get_collisions function in the collision_system module. auto collisions = phx::collision_system()->get_collisions(m_world); for (const auto& collision : collisions) { // The information in the collision info struct is: // id - The id of the colliders that collided - i.e. the pair of colliders that collided. // is_colliding - A boolean that tells if the colliders are colliding or not. // is_valid - A boolean that tells if the rest of the information is valid or not. Sometimes, algorithms // can't find the collision points, especially in the case of a very deep penetration. // points - A pair of vec3's that represent the collision points. // normal - The normal of the collision. // distance - The penetration depth of the collision. // features - A pair of geometry::Feature's that represent the features of the colliders that collided. // This is only available if the narrow phase algorithm is V-Clip. // Let's use the draw_sphere function to draw the points of the collision. draw_sphere(collision.points.first); draw_sphere(collision.points.second); // If you run the simulation now, you'll see the points of the collisions as spheres in the world. It may // be hard to see the points, so if you press F1, you can toggle the bodies to be transparent, which // should make it easier to see the points. // Remember, the box and the sphere are excluded from colliding and the capsule, cone and convex hull are // in a separate layer, so they will collide only with each other. } gfx::camera_system()->activate_default_camera(); gfx::renderer()->present_collection(gfx::object_system()->objects(), gfx::Renderer::pipeline::FORWARD); // # 4. DISTANCE QUERY # // Regardless of whether two colliders are colliding or not, we can query the distance between them. We'll set // it up so that we can toggle the distance query by pressing the 'v' key, which will draw lines between the // colliders and spheres at the closest points between them. if (osi::keyboard()->just_released().contains(SDL_GetKeyName(SDLK_v))) { // We'll call the distance query now. You can do so by calling the distance_query function in the // collision_system module. You'll need to supply, which narrow phase algorithm to use. // We'll call the distance function between the box and the sphere. The distance query ignores the collision // matrix and will always return the distance between the colliders. auto box_collider = phx::body_system()->get_collider(m_body_box); auto sphere_collider = phx::body_system()->get_collider(m_body_sphere); auto result = phx::collision_system()->distance_query(box_collider, sphere_collider, phx::NarrowPhaseType::VClip); // The result contains the following information: // distance - The distance between the colliders, negative if they are colliding. // closest_point_a - The closest point on the first collider. // closest_point_b - The closest point on the second collider. // We'll print the distance result to the console. std::cout << "Distance: " << result.value().distance << std::endl; // We'll clear all the previous lines and spheres and draw the new ones, passing the optional parameter // to clear persistent debug objects. clear_lines(true); clear_spheres(true); // Let's draw the points and the line between them. We'll set the optional persistent parameter to true, // so that the points and the line are not cleared in the next frame. draw_sphere(result.value().closest_point_a, true); draw_sphere(result.value().closest_point_b, true); draw_line(result.value().closest_point_a, result.value().closest_point_b, true); } gfx::ui_system()->present_current_scene(true, 2.0f); // If you run the simulation now, you can press 'v' to see the closest points between the box and the sphere. // Feel free to swap the colliders for different objects and see how the distance query works between them. } void Presenter::release() { Loading Loading
age @ e48cfb1e Compare dbf48ebc to e48cfb1e Original line number Diff line number Diff line Subproject commit dbf48ebcbba760f0c5469a7e26042c076b1ec3c2 Subproject commit e48cfb1e0e6fea04804a4bbc0de2794fa2afd1c7
src/tut_collisions/include/tut_collisions/presenter.hpp +51 −17 Original line number Diff line number Diff line Loading @@ -11,6 +11,7 @@ # include "osi/index.hpp" #include "phx/body_system.hpp" # include "phx/collisions/collision_info.hpp" #include "phx/collisions/collision_system.hpp" namespace com { struct Frame; Loading @@ -28,6 +29,7 @@ namespace tut_collisions { ~Presenter() override; void next_round() override; void next_round_tutorial(); com::Folder* light_directional() const; com::Folder* light_ambient() const; Loading @@ -44,6 +46,8 @@ namespace tut_collisions { void handle_inputs(); void get_collisions(); void draw_aabbs(); void draw_debug_colliders(); Loading Loading @@ -84,7 +88,7 @@ namespace tut_collisions { com::Folder* m_world; // Debug drawing bool m_draw_objects = true; bool m_draw_objects_transparent = false; bool m_draw_aabbs = false; bool m_draw_debug_colliders = false; bool m_is_sim_running = false; Loading Loading @@ -179,7 +183,7 @@ namespace tut_collisions { // Helper function to create a material for the crate inline void Presenter::create_gfx_materials() { // Crate material auto shader_crate = gfx::shader_system()->insert_shader<Graph>({name()}, "box-shader", Graph::LIT); auto shader_crate = gfx::shader_system()->insert_shader<Graph>({name()}, "box-shader", Graph::LIT, Graph::PHONG, Graph::PARTIAL); auto normal = shader_crate->insert<Graph::ConstantNode>(vec3{0.5, 0.5, 1.}); shader_crate->connect(shader_crate->root(), Graph::MasterNodeLit::inputs::normal_ts, normal, 0); Loading @@ -192,6 +196,9 @@ namespace tut_collisions { shader_crate->connect(shader_crate->root(), Graph::MasterNodeLit::inputs::diffuse, container, 0); shader_crate->connect(shader_crate->root(), Graph::MasterNodeLit::inputs::specular, container, 0); auto alpha = shader_crate->insert<Graph::ConstantNode>(0.5f); shader_crate->connect(shader_crate->root(), Graph::MasterNodeLit::inputs::alpha, alpha, 0); auto emission = shader_crate->insert<Graph::ConstantNode>(vec3{0.}); shader_crate->connect(shader_crate->root(), Graph::MasterNodeLit::inputs::emission, emission, 0); Loading Loading @@ -299,7 +306,7 @@ namespace tut_collisions { // Debug shapes auto line_mesh = gfx::buffer_generators()->insert_procedural_box_wireframe( vec3{0.001, 0.001, 1}, "line_mesh", {"tutorial"}); m_gfx_instances_lines = gfx::object_system()->insert_object({"tutorial", "lines"}, m_material_blue, line_mesh); m_gfx_instances_lines = gfx::object_system()->insert_object({"tutorial", "lines"}, m_material_cyan, line_mesh); auto sphere_ind_mesh = gfx::buffer_generators()->insert_procedural_sphere_solid( 0.025f, 10, "sphere_ind_mesh", {"tutorial"}); Loading Loading @@ -334,22 +341,46 @@ namespace tut_collisions { const auto insert_text = [&](const std::string& text, size_t line, float offset_left = 0.0f) { auto* static_text = gfx::ui_generators()->create_ui_static_text( "static_text_" + std::to_string(line), vec2i{1000, 50}, nullptr, ui_scene, ui_layer, text, false); static_text->get_transform()->set_anchor(1.0f, offset_left, (static_cast<float>(line) * 0.1f), 1.0f); static_text->get_transform()->set_anchor(1.0f, offset_left, (static_cast<float>(line) * 0.025f) + 0.1f, 1.0f); static_text->register_handler(buffer_handler); static_text->request_fonts_to_load("OpenSans", 48U, true, true); static_text->request_fonts_to_load("OpenSans", 12U, true, true); static_text->set_horizontal_alignment(gfx::StaticText::LEFT); static_text->set_vertical_alignment(gfx::StaticText::TOP); gfx::ui_system()->insert_objects(static_text); }; insert_text("1-6 - Select objects", 0); insert_text("F1 - Show/hide objects", 1); insert_text("F2 - Show/hide AABBs", 2); insert_text("F3 - Show/hide debug colliders", 3); insert_text("Arrow keys - Move selected object", 4); insert_text("Insert/Delete/Home/End - Rotate selected object", 5); insert_text("Comma/Period - Precision movement", 6); insert_text("1-6 - Select objects", 1); insert_text("F1 - Show/hide objects", 2); insert_text("F2 - Show/hide AABBs", 3); insert_text("F3 - Show/hide debug colliders", 4); insert_text("Arrow keys - Move selected object", 5); insert_text("Insert/Delete/Home/End - Rotate selected object", 6); insert_text("Comma/Period - Precision movement", 7); } inline void Presenter::next_round() { // Clear the debug shapes clear_lines(); clear_spheres(); handle_inputs(); next_round_tutorial(); // Debug drawing get_collisions(); if (m_draw_aabbs) draw_aabbs(); if (m_draw_debug_colliders) draw_debug_colliders(); // Rendering gfx::renderer()->clear_render_buffers(); gfx::camera_system()->activate_default_camera(); gfx::renderer()->present_collection(gfx::object_system()->objects()->find<com::Folder>("tutorial"), m_draw_objects_transparent ? gfx::Renderer::pipeline::TRANSPARENT : gfx::Renderer::pipeline::FORWARD); gfx::ui_system()->present_current_scene(true, 2.0f); } inline void Presenter::handle_inputs() { Loading Loading @@ -384,13 +415,9 @@ namespace tut_collisions { m_selected_object = &m_body_convex; } if (osi::keyboard()->just_released().contains(SDL_GetKeyName(SDLK_SPACE))) { m_is_sim_running = !m_is_sim_running; } // Debug toggles if (osi::keyboard()->just_released().contains(SDL_GetKeyName(SDLK_F1))) { m_draw_objects = !m_draw_objects; m_draw_objects_transparent = !m_draw_objects_transparent; } if (osi::keyboard()->just_released().contains(SDL_GetKeyName(SDLK_F2))) { Loading Loading @@ -464,6 +491,11 @@ namespace tut_collisions { } } inline void Presenter::get_collisions() { m_broad_collisions = phx::collision_system()->get_broad_collisions(m_world); m_narrow_collisions = phx::collision_system()->get_collisions(m_world); } inline void Presenter::draw_aabbs() { const auto objects = std::vector({m_body_box, m_body_sphere, m_body_cylinder, m_body_cone, m_body_capsule, m_body_convex}); Loading Loading @@ -537,6 +569,8 @@ namespace tut_collisions { const auto direction = normalize(destination - origin); const auto length = distance(origin, destination); if (length < Num::epsilon) return; const auto direction_quat = look_at(direction, vec3{0, 0, 1}, vec3{0, 1, 0}); const auto frame = m_lines_frame_folder->push_back<com::Folder>(m_lines_frame_folder->generate_unique_name("line"))->push_back<com::Frame>(); Loading
src/tut_collisions/src/presenter.cpp +141 −18 Original line number Diff line number Diff line Loading @@ -40,13 +40,29 @@ namespace tut_collisions { phx::body_system()->insert_sphere_collider(sphere_box, 0.5f); phx::body_system()->insert_cylinder_collider(cylinder_box, 0.5f, 2.0f); // We can also use a slightly different syntax. The following two lines are equivalent. phx::body_system()->insert_collider<phx::ConeCollider>(cone_box, 0.5f, 1.0f); phx::body_system()->insert_collider<phx::CapsuleCollider>(capsule_box, 0.5f, 2.0f); // To insert a collider into a different physics layer, we can pass an optional parameter to the insert_collider // function; by default, if no parameter is passed, the collider is inserted into the default layer. // Let's create a new layer and insert the rest of the colliders into it, we can do so by using the layer_system // module. auto layer = phx::layer_system()->create_layer("new_layer"); // By default, the new layer interacts will all other layers, but you can supply an optional parameter to the // create_layer function to say, if that layer should interact with all other layers or not. // If you want to specifically exclude a layer from interacting with another layer, you'll have to modify the // collision_matrix, like so: phx::collision_matrix()->disable_collision(phx::layer_system()->default_layer(), layer); // We have now disabled the default layer from interacting with the new layer. // Let's create the rest of the colliders with the new layer. phx::body_system()->insert_cone_collider(cone_box, 0.5f, 1.0f, layer); // If you wish to set a layer to a collider later, you can do so in the body_system module. phx::body_system()->insert_capsule_collider(capsule_box, 0.5f, 2.0f); phx::body_system()->insert_layer(capsule_box, layer); // We can also explicitly declare a convex polyhedron as the collider. This requires the vertices and indices of // the polyhedron to be passed to the insert_collider function. The polyhedron must be convex. phx::body_system()->insert_collider<phx::ConvexHullCollider>(convex_box, m_rand_mesh_vertices, m_rand_mesh_indices); phx::body_system()->insert_convex_hull_collider(convex_box, m_rand_mesh_vertices, m_rand_mesh_indices, layer); // We'll create the world and insert the bodies into it. m_world = phx::world_system()->insert_world({"world"}); Loading @@ -73,6 +89,15 @@ namespace tut_collisions { gfx::object_system()->push_frame_back(m_gfx_instances_capsule, capsule_frame); gfx::object_system()->push_frame_back(m_gfx_instances_convex, convex_frame); // If you wish to exclude two specific instances from colliding, you can exclude them in the collision_matrix. // For example, if you want to exclude the box and the sphere from colliding, you can do so like this: auto box_instance_collider = phx::body_system()->get_collider(m_body_box); auto sphere_instance_collider = phx::body_system()->get_collider(m_body_sphere); phx::collision_matrix()->override_colliders({box_instance_collider, sphere_instance_collider}, false); // To remove the exclusion, you can do so like this: // phx::collision_matrix()->remove_override({box_instance_collider, sphere_instance_collider}); // Lastly we'll position the shapes around the origin of the world. box_frame->set_origin({5, 0, 0}); sphere_frame->set_origin({2, 5, 0}); Loading @@ -81,28 +106,126 @@ namespace tut_collisions { capsule_frame->set_origin({-2, -5, 0}); convex_frame->set_origin({2, -5, 0}); // Running the simulation now, using the debug features, you should see the shapes, their colliders and the // bounding boxes of the colliders in the world. // Running the simulation now, using the debug features, you'll be able to see the shapes in the world and if // you press F3, you'll get a wireframe view of the colliders. // The colliders, however, don't interact with each other yet. We'll implement that in the next step. } void Presenter::next_round_tutorial() { // # 2. DETECTING COLLISIONS # // Now that we have successfully set up the bodies and colliders, we can now detect the collisions between them. // We can now just simply call the detect_collisions function in the collision_system module. This will populate // a file, within the root folder of the world, with the appropriate collision information about colliding // pairs of colliders. // Let's set it up so that we can start and stop the collision detection by pressing the 'space' key. For this // there is a member boolean m_is_sim_running that we'll just toggle when the 'space' key is pressed. if (osi::keyboard()->just_released().contains(SDL_GetKeyName(SDLK_SPACE))) { m_is_sim_running = !m_is_sim_running; } void Presenter::next_round() { handle_inputs(); clear_lines(); clear_spheres(); // Now, if the simulation is running, we'll simply call the detect_collisions function. // We can specify the broad and narrow phase algorithms to use in the function call. This is important to do // in the first call of the function, as the system creates the necessary data structures based on the types // specified. // You can however change the broad and narrow phase algorithms on the fly, but beware, as this will clear the // cache of the previous algorithm and will cause the system to rebuild some of the data structures. // For broad phase algorithms, you can choose between: // - Bruteforce - phx::BroadPhaseType::Brute // - The simplest algorithm, compares every pair of colliders, having a complexity of O(n^2). However, it // can be useful and even faster for small scenes, as it doesn't require any preprocessing or data // structures. // - Sweep and Prune - phx::BroadPhaseType::SweepAndPrune // - A more efficient algorithm, having a complexity of O(n+m), where n is the number of colliders and m is // the number of overlapping pairs. // - Dynamic AABB Tree - phx::BroadPhaseType::AABBTree // - A more advanced algorithm, having a complexity of O(n log n), where n is the number of colliders. // // For narrow phase algorithms, you can choose between: // - GJK - phx::NarrowPhaseType::GJK // - A fast and robust algorithm for convex shapes; works with implicit shapes. The implementation uses // the GJK algorithm with the EPA algorithm for calculating the deepest penetration depth. // - V-Clip - phx::NarrowPhaseType::VClip // - A more advanced algorithm for convex shapes; works with explicit shapes. It returns the specific // two features which are either colliding or closest to each other. if (m_is_sim_running) { phx::collision_system()->detect_collisions(m_world, phx::BroadPhaseType::SweepAndPrune, phx::NarrowPhaseType::GJK); // Feel free to swap between the algorithms to see how they affect the performance of the simulation and // how they act in different scenarios. const auto collision_file = phx::collision_system()->detect_collisions(m_world, phx::BroadPhaseType::SweepAndPrune, phx::NarrowPhaseType::GJK); } if (m_draw_aabbs) draw_aabbs(); if (m_draw_debug_colliders) draw_debug_colliders(); gfx::renderer()->clear_render_buffers(); // If you run the simulation now, you can enable the AABB's by pressing F2, where you'll see the AABB's changing // color based on the collision status of the colliders. Green means no collision, orange means they collided // in the broad phase and red means they collided in the narrow phase. // Let's move on to the next step where we'll use some of the information from the collisions. // # 3. VISUALISING COLLISIONS # // We'll now use the information from the collisions to draw points at the collision points. We will use the // debug functions to draw the points, which you can explore on your own in the header file. // First we'll iterate over the collisions and get the points of the collisions. // We can get the collisions by calling the get_collisions function in the collision_system module. auto collisions = phx::collision_system()->get_collisions(m_world); for (const auto& collision : collisions) { // The information in the collision info struct is: // id - The id of the colliders that collided - i.e. the pair of colliders that collided. // is_colliding - A boolean that tells if the colliders are colliding or not. // is_valid - A boolean that tells if the rest of the information is valid or not. Sometimes, algorithms // can't find the collision points, especially in the case of a very deep penetration. // points - A pair of vec3's that represent the collision points. // normal - The normal of the collision. // distance - The penetration depth of the collision. // features - A pair of geometry::Feature's that represent the features of the colliders that collided. // This is only available if the narrow phase algorithm is V-Clip. // Let's use the draw_sphere function to draw the points of the collision. draw_sphere(collision.points.first); draw_sphere(collision.points.second); // If you run the simulation now, you'll see the points of the collisions as spheres in the world. It may // be hard to see the points, so if you press F1, you can toggle the bodies to be transparent, which // should make it easier to see the points. // Remember, the box and the sphere are excluded from colliding and the capsule, cone and convex hull are // in a separate layer, so they will collide only with each other. } gfx::camera_system()->activate_default_camera(); gfx::renderer()->present_collection(gfx::object_system()->objects(), gfx::Renderer::pipeline::FORWARD); // # 4. DISTANCE QUERY # // Regardless of whether two colliders are colliding or not, we can query the distance between them. We'll set // it up so that we can toggle the distance query by pressing the 'v' key, which will draw lines between the // colliders and spheres at the closest points between them. if (osi::keyboard()->just_released().contains(SDL_GetKeyName(SDLK_v))) { // We'll call the distance query now. You can do so by calling the distance_query function in the // collision_system module. You'll need to supply, which narrow phase algorithm to use. // We'll call the distance function between the box and the sphere. The distance query ignores the collision // matrix and will always return the distance between the colliders. auto box_collider = phx::body_system()->get_collider(m_body_box); auto sphere_collider = phx::body_system()->get_collider(m_body_sphere); auto result = phx::collision_system()->distance_query(box_collider, sphere_collider, phx::NarrowPhaseType::VClip); // The result contains the following information: // distance - The distance between the colliders, negative if they are colliding. // closest_point_a - The closest point on the first collider. // closest_point_b - The closest point on the second collider. // We'll print the distance result to the console. std::cout << "Distance: " << result.value().distance << std::endl; // We'll clear all the previous lines and spheres and draw the new ones, passing the optional parameter // to clear persistent debug objects. clear_lines(true); clear_spheres(true); // Let's draw the points and the line between them. We'll set the optional persistent parameter to true, // so that the points and the line are not cleared in the next frame. draw_sphere(result.value().closest_point_a, true); draw_sphere(result.value().closest_point_b, true); draw_line(result.value().closest_point_a, result.value().closest_point_b, true); } gfx::ui_system()->present_current_scene(true, 2.0f); // If you run the simulation now, you can press 'v' to see the closest points between the box and the sphere. // Feel free to swap the colliders for different objects and see how the distance query works between them. } void Presenter::release() { Loading