From b36d54968565af883cdbdca8d712678a24bcba69 Mon Sep 17 00:00:00 2001 From: Erin Catto Date: Sat, 27 Jul 2024 23:16:25 -0700 Subject: [PATCH] minor fixes (#174) issues #168, #167, #170 pull request #169 --- CMakeLists.txt | 13 ++++--- docs/collision.md | 2 +- docs/simulation.md | 4 +- samples/sample_bodies.cpp | 80 +++++++++++++++++++++++++++++++++++++++ src/body.c | 20 ++++++---- 5 files changed, 102 insertions(+), 17 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b5f944ee..8c1390e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,14 +34,15 @@ if (MSVC OR APPLE) endif() endif() - if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64") option(BOX2D_AVX2 "Enable AVX2 (faster)" ON) endif() -# Needed for samples.exe to find box2d.dll -# set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin") -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin") +if(PROJECT_IS_TOP_LEVEL) + # Needed for samples.exe to find box2d.dll + # set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin") + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin") +endif() # C++17 needed for imgui set(CMAKE_CXX_STANDARD 17) @@ -56,7 +57,7 @@ add_subdirectory(extern/simde) add_subdirectory(src) # This hides samples, test, and doxygen from apps that use box2d via FetchContent -if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) +if(PROJECT_IS_TOP_LEVEL) option(BOX2D_SAMPLES "Build the Box2D samples" ON) option(BOX2D_BENCHMARKS "Build the Box2D benchmarks" OFF) option(BOX2D_DOCS "Build the Box2D documentation" OFF) @@ -119,4 +120,4 @@ endif() # # Building on clang in windows # cmake -S .. -B . -G "Visual Studio 17 2022" -A x64 -T ClangCL -# https://clang.llvm.org/docs/UsersManual.html#clang-cl \ No newline at end of file +# https://clang.llvm.org/docs/UsersManual.html#clang-cl diff --git a/docs/collision.md b/docs/collision.md index debd31a4..7977de0d 100644 --- a/docs/collision.md +++ b/docs/collision.md @@ -261,7 +261,7 @@ This is called the *time of impact* (TOI). The main purpose of `b2TimeOfImpact() tunnel prevention. Box2D uses this internally to prevent moving objects from tunneling through static shapes. -The `b2TimeOfImpact()` identities an initial separating axis and +The `b2TimeOfImpact()` identifies an initial separating axis and ensures the shapes do not cross on that axis. This process is repeated as shapes are moved closer together, until they touch or pass by each other. diff --git a/docs/simulation.md b/docs/simulation.md index ab7a3e0a..c27ab827 100644 --- a/docs/simulation.md +++ b/docs/simulation.md @@ -723,7 +723,7 @@ Restitution is combined this way so that you can have a bouncy super ball without having a bouncy floor. When a shape develops multiple contacts, restitution is simulated -approximately. This is because Box2D uses an sequential solver. Box2D +approximately. This is because Box2D uses a sequential solver. Box2D also uses inelastic collisions when the collision velocity is small. This is done to prevent jitter. See `b2WorldDef::restitutionThreshold`. @@ -927,7 +927,7 @@ For convenience, this is stored as an impulse. #### contact point id Box2D tries to re-use the contact impulse results from a time step as the initial guess for the next time step. Box2D uses contact point ids to match -contact points across time steps. The ids contain geometric features +contact points across time steps. The ids contain geometric feature indices that help to distinguish one contact point from another. #### speculative contact diff --git a/samples/sample_bodies.cpp b/samples/sample_bodies.cpp index 4615a329..69ddc480 100644 --- a/samples/sample_bodies.cpp +++ b/samples/sample_bodies.cpp @@ -759,3 +759,83 @@ class Sleep : public Sample }; static int sampleSleep = RegisterSample("Bodies", "Sleep", Sleep::Create); + +class BadBody : public Sample +{ +public: + explicit BadBody(Settings& settings) + : Sample(settings) + { + if (settings.restart == false) + { + g_camera.m_center = {2.3f, 10.0f}; + g_camera.m_zoom = 25.0f * 0.5f; + } + + b2BodyId groundId = b2_nullBodyId; + { + b2BodyDef bodyDef = b2DefaultBodyDef(); + groundId = b2CreateBody(m_worldId, &bodyDef); + + b2Segment segment = {{-20.0f, 0.0f}, {20.0f, 0.0f}}; + b2ShapeDef shapeDef = b2DefaultShapeDef(); + b2CreateSegmentShape(groundId, &shapeDef, &segment); + } + + // Build a bad body + { + b2BodyDef bodyDef = b2DefaultBodyDef(); + bodyDef.type = b2_dynamicBody; + bodyDef.position = {0.0f, 3.0f}; + bodyDef.angularVelocity = 0.2f; + bodyDef.angle = 0.25f * b2_pi; + + m_badBodyId = b2CreateBody(m_worldId, &bodyDef); + + b2Capsule capsule = {{0.0f, -1.0f}, {0.0f, 1.0f}, 1.0f}; + b2ShapeDef shapeDef = b2DefaultShapeDef(); + + // density set to zero intentionally to create a bad body + shapeDef.density = 0.0f; + b2CreateCapsuleShape(m_badBodyId, &shapeDef, &capsule); + } + + // Build a normal body + { + b2BodyDef bodyDef = b2DefaultBodyDef(); + bodyDef.type = b2_dynamicBody; + bodyDef.position = {2.0f, 3.0f}; + bodyDef.angle = 0.25f * b2_pi; + + b2BodyId bodyId = b2CreateBody(m_worldId, &bodyDef); + + b2Capsule capsule = {{0.0f, -1.0f}, {0.0f, 1.0f}, 1.0f}; + b2ShapeDef shapeDef = b2DefaultShapeDef(); + + b2CreateCapsuleShape(bodyId, &shapeDef, &capsule); + } + } + + void Step(Settings& settings) override + { + Sample::Step(settings); + + g_draw.DrawString(5, m_textLine, "A bad body is a dynamic body with no mass and behaves like a kinematic body."); + m_textLine += m_textIncrement; + + g_draw.DrawString(5, m_textLine, "Bad bodies are considered invalid and a user bug. Behavior is not guaranteed."); + m_textLine += m_textIncrement; + + // For science + b2Body_ApplyForceToCenter(m_badBodyId, {0.0f, 10.0f}, true); + } + + static Sample* Create(Settings& settings) + { + return new BadBody(settings); + } + + b2BodyId m_badBodyId; +}; + +static int sampleBadBody = RegisterSample("Bodies", "Bad", BadBody::Create); diff --git a/src/body.c b/src/body.c index e18b1044..0d78aa9a 100644 --- a/src/body.c +++ b/src/body.c @@ -761,6 +761,12 @@ void b2Body_SetLinearVelocity(b2BodyId bodyId, b2Vec2 linearVelocity) { b2World* world = b2GetWorld(bodyId.world0); b2Body* body = b2GetBodyFullId(world, bodyId); + + if (b2LengthSquared(linearVelocity) > 0.0f) + { + b2WakeBody(world, body); + } + b2BodyState* state = b2GetBodyState(world, body); if (state == NULL) { @@ -768,16 +774,18 @@ void b2Body_SetLinearVelocity(b2BodyId bodyId, b2Vec2 linearVelocity) } state->linearVelocity = linearVelocity; - if (b2LengthSquared(linearVelocity) > 0.0f) - { - b2WakeBody(world, body); - } } void b2Body_SetAngularVelocity(b2BodyId bodyId, float angularVelocity) { b2World* world = b2GetWorld(bodyId.world0); b2Body* body = b2GetBodyFullId(world, bodyId); + + if (angularVelocity != 0.0f) + { + b2WakeBody(world, body); + } + b2BodyState* state = b2GetBodyState(world, body); if (state == NULL) { @@ -785,10 +793,6 @@ void b2Body_SetAngularVelocity(b2BodyId bodyId, float angularVelocity) } state->angularVelocity = angularVelocity; - if (angularVelocity != 0.0f) - { - b2WakeBody(world, body); - } } void b2Body_ApplyForce(b2BodyId bodyId, b2Vec2 force, b2Vec2 point, bool wake)