diff --git a/benchmark/main.c b/benchmark/main.c index 2ad7b9300..e75537290 100644 --- a/benchmark/main.c +++ b/benchmark/main.c @@ -1,7 +1,9 @@ // SPDX-FileCopyrightText: 2024 Erin Catto // SPDX-License-Identifier: MIT +#if defined( _MSC_VER ) && !defined( _CRT_SECURE_NO_WARNINGS ) #define _CRT_SECURE_NO_WARNINGS +#endif #include "TaskScheduler_c.h" #include "benchmarks.h" diff --git a/docs/simulation.md b/docs/simulation.md index c5d1a2494..882102b66 100644 --- a/docs/simulation.md +++ b/docs/simulation.md @@ -1061,6 +1061,10 @@ Sensor events should be processed after the world step and before other game log help you avoid processing stale data. ### Contact Events + +todo discuss the expected number of events and how this can change with the time step. +see https://www.iforce2d.net/b2dtut/collision-anatomy + Contact events are available after each world step. Like sensor events these should be retrieved and processed before performing other game logic. Otherwise you may be accessing orphaned/invalid data. diff --git a/include/box2d/base.h b/include/box2d/base.h index 1980979d2..76620edd1 100644 --- a/include/box2d/base.h +++ b/include/box2d/base.h @@ -117,7 +117,8 @@ B2_API uint64_t b2GetTicks( void ); /// Get the milliseconds passed from an initial tick value. B2_API float b2GetMilliseconds( uint64_t ticks ); -/// Get the milliseconds passed from an initial tick value. +/// Get the milliseconds passed from an initial tick value. Resets the passed in +/// value to the current tick value. B2_API float b2GetMillisecondsAndReset( uint64_t* ticks ); /// Yield to be used in a busy loop. diff --git a/include/box2d/box2d.h b/include/box2d/box2d.h index bee6e5c8a..f74ac8ceb 100644 --- a/include/box2d/box2d.h +++ b/include/box2d/box2d.h @@ -52,23 +52,23 @@ B2_API b2ContactEvents b2World_GetContactEvents( b2WorldId worldId ); /// Overlap test for all shapes that *potentially* overlap the provided AABB B2_API b2TreeStats b2World_OverlapAABB( b2WorldId worldId, b2AABB aabb, b2QueryFilter filter, b2OverlapResultFcn* fcn, - void* context ); + void* context ); /// Overlap test for for all shapes that overlap the provided point. -B2_API b2TreeStats b2World_OverlapPoint( b2WorldId worldId, b2Vec2 point, b2Transform transform, - b2QueryFilter filter, b2OverlapResultFcn* fcn, void* context ); +B2_API b2TreeStats b2World_OverlapPoint( b2WorldId worldId, b2Vec2 point, b2Transform transform, b2QueryFilter filter, + b2OverlapResultFcn* fcn, void* context ); /// Overlap test for for all shapes that overlap the provided circle. A zero radius may be used for a point query. -B2_API b2TreeStats b2World_OverlapCircle( b2WorldId worldId, const b2Circle* circle, b2Transform transform, - b2QueryFilter filter, b2OverlapResultFcn* fcn, void* context ); +B2_API b2TreeStats b2World_OverlapCircle( b2WorldId worldId, const b2Circle* circle, b2Transform transform, b2QueryFilter filter, + b2OverlapResultFcn* fcn, void* context ); /// Overlap test for all shapes that overlap the provided capsule B2_API b2TreeStats b2World_OverlapCapsule( b2WorldId worldId, const b2Capsule* capsule, b2Transform transform, - b2QueryFilter filter, b2OverlapResultFcn* fcn, void* context ); + b2QueryFilter filter, b2OverlapResultFcn* fcn, void* context ); /// Overlap test for all shapes that overlap the provided polygon B2_API b2TreeStats b2World_OverlapPolygon( b2WorldId worldId, const b2Polygon* polygon, b2Transform transform, - b2QueryFilter filter, b2OverlapResultFcn* fcn, void* context ); + b2QueryFilter filter, b2OverlapResultFcn* fcn, void* context ); /// Cast a ray into the world to collect shapes in the path of the ray. /// Your callback function controls whether you get the closest point, any point, or n-points. @@ -82,7 +82,7 @@ B2_API b2TreeStats b2World_OverlapPolygon( b2WorldId worldId, const b2Polygon* p /// @param context A user context that is passed along to the callback function /// @return traversal performance counters B2_API b2TreeStats b2World_CastRay( b2WorldId worldId, b2Vec2 origin, b2Vec2 translation, b2QueryFilter filter, - b2CastResultFcn* fcn, void* context ); + b2CastResultFcn* fcn, void* context ); /// Cast a ray into the world to collect the closest hit. This is a convenience function. /// This is less general than b2World_CastRay() and does not allow for custom filtering. @@ -90,18 +90,24 @@ B2_API b2RayResult b2World_CastRayClosest( b2WorldId worldId, b2Vec2 origin, b2V /// Cast a circle through the world. Similar to a cast ray except that a circle is cast instead of a point. /// @see b2World_CastRay -B2_API b2TreeStats b2World_CastCircle( b2WorldId worldId, const b2Circle* circle, b2Transform originTransform, - b2Vec2 translation, b2QueryFilter filter, b2CastResultFcn* fcn, void* context ); +B2_API b2TreeStats b2World_CastCircle( b2WorldId worldId, const b2Circle* circle, b2Transform originTransform, b2Vec2 translation, + b2QueryFilter filter, b2CastResultFcn* fcn, void* context ); /// Cast a capsule through the world. Similar to a cast ray except that a capsule is cast instead of a point. /// @see b2World_CastRay B2_API b2TreeStats b2World_CastCapsule( b2WorldId worldId, const b2Capsule* capsule, b2Transform originTransform, - b2Vec2 translation, b2QueryFilter filter, b2CastResultFcn* fcn, void* context ); + b2Vec2 translation, b2QueryFilter filter, b2CastResultFcn* fcn, void* context ); /// Cast a polygon through the world. Similar to a cast ray except that a polygon is cast instead of a point. /// @see b2World_CastRay B2_API b2TreeStats b2World_CastPolygon( b2WorldId worldId, const b2Polygon* polygon, b2Transform originTransform, - b2Vec2 translation, b2QueryFilter filter, b2CastResultFcn* fcn, void* context ); + b2Vec2 translation, b2QueryFilter filter, b2CastResultFcn* fcn, void* context ); + +/// Cast a character through the world. This resolves initial overlap then attempts to reach the target. The character can be +/// an arbitrary rounded polygon. It is recommended to use a capsule. The minimum radius is 0.02m (2 centimeters). +/// Returns the final position. +B2_API b2Vec2 b2World_MoveCharacter( b2WorldId worldId, const b2ShapeProxy* shapeProxy, b2Transform originTransform, + b2Vec2 translation, b2QueryFilter filter ); /// Enable/disable sleep. If your application does not need sleeping, you can gain some performance /// by disabling sleep completely at the world level. @@ -177,7 +183,7 @@ B2_API void b2World_SetMaximumLinearSpeed( b2WorldId worldId, float maximumLinea B2_API float b2World_GetMaximumLinearSpeed( b2WorldId worldId ); /// Enable/disable constraint warm starting. Advanced feature for testing. Disabling -/// sleeping greatly reduces stability and provides no performance gain. +/// warm starting greatly reduces stability and provides no performance gain. B2_API void b2World_EnableWarmStarting( b2WorldId worldId, bool flag ); /// Is constraint warm starting enabled? @@ -562,7 +568,7 @@ B2_API float b2Shape_GetRestitution( b2ShapeId shapeId ); /// @see b2ShapeDef::material B2_API void b2Shape_SetMaterial( b2ShapeId shapeId, int material ); -/// Get the shape material identifier +/// Get the shape material identifier B2_API int b2Shape_GetMaterial( b2ShapeId shapeId ); /// Get the shape filter @@ -574,6 +580,13 @@ B2_API b2Filter b2Shape_GetFilter( b2ShapeId shapeId ); /// @see b2ShapeDef::filter B2_API void b2Shape_SetFilter( b2ShapeId shapeId, b2Filter filter ); +/// Enable sensor events for this shape. +/// @see b2ShapeDef::enableSensorEvents +B2_API void b2Shape_EnableSensorEvents( b2ShapeId shapeId, bool flag ); + +/// Returns true if sensor events are enabled. +B2_API bool b2Shape_AreSensorEventsEnabled( b2ShapeId shapeId ); + /// Enable contact events for this shape. Only applies to kinematic and dynamic bodies. Ignored for sensors. /// @see b2ShapeDef::enableContactEvents /// @warning changing this at run-time may lead to lost begin/end events diff --git a/include/box2d/collision.h b/include/box2d/collision.h index c00946cf2..3cee0ed9e 100644 --- a/include/box2d/collision.h +++ b/include/box2d/collision.h @@ -382,11 +382,10 @@ typedef struct b2DistanceInput /// Output for b2ShapeDistance typedef struct b2DistanceOutput { - b2Vec2 pointA; ///< Closest point on shapeA - b2Vec2 pointB; ///< Closest point on shapeB - // todo_erin implement this - // b2Vec2 normal; ///< Normal vector that points from A to B - float distance; ///< The final distance, zero if overlapped + b2Vec2 pointA; ///< Closest point on shapeA + b2Vec2 pointB; ///< Closest point on shapeB + b2Vec2 normal; ///< Normal vector that points from A to B + float distance; ///< The final distance, zero if overlapped int iterations; ///< Number of GJK iterations used int simplexCount; ///< The number of simplexes stored in the simplex array } b2DistanceOutput; @@ -394,10 +393,10 @@ typedef struct b2DistanceOutput /// Simplex vertex for debugging the GJK algorithm typedef struct b2SimplexVertex { - b2Vec2 wA; ///< support point in proxyA - b2Vec2 wB; ///< support point in proxyB - b2Vec2 w; ///< wB - wA - float a; ///< barycentric coordinate for closest point + b2Vec2 wA; ///< support point in proxyA + b2Vec2 wB; ///< support point in proxyB + b2Vec2 w; ///< wB - wA + float a; ///< barycentric coordinate for closest point int indexA; ///< wA index int indexB; ///< wB index } b2SimplexVertex; @@ -406,7 +405,7 @@ typedef struct b2SimplexVertex typedef struct b2Simplex { b2SimplexVertex v1, v2, v3; ///< vertices - int count; ///< number of valid vertices + int count; ///< number of valid vertices } b2Simplex; /// Compute the closest points between two shapes represented as point clouds. @@ -491,7 +490,7 @@ B2_API b2TOIOutput b2TimeOfImpact( const b2TOIInput* input ); /// A manifold point is a contact point belonging to a contact manifold. /// It holds details related to the geometry and dynamics of the contact points. /// Box2D uses speculative collision so some contact points may be separated. -/// You may use the maxNormalImpulse to determine if there was an interaction during +/// You may use the totalNormalImpulse to determine if there was an interaction during /// the time step. typedef struct b2ManifoldPoint { @@ -516,9 +515,9 @@ typedef struct b2ManifoldPoint /// The friction impulse float tangentImpulse; - /// The maximum normal impulse applied during sub-stepping. This is important + /// The total normal impulse applied across sub-stepping and restitution. This is important /// to identify speculative contact points that had an interaction in the time step. - float maxNormalImpulse; + float totalNormalImpulse; /// Relative normal velocity pre-solve. Used for hit events. If the normal impulse is /// zero then there was no hit. Negative means shapes are approaching. @@ -760,6 +759,4 @@ B2_API void b2DynamicTree_Validate( const b2DynamicTree* tree ); /// Validate this tree has no enlarged AABBs. For testing. B2_API void b2DynamicTree_ValidateNoEnlarged( const b2DynamicTree* tree ); - - /**@}*/ diff --git a/include/box2d/types.h b/include/box2d/types.h index c7b2d5c1e..06dac756e 100644 --- a/include/box2d/types.h +++ b/include/box2d/types.h @@ -97,7 +97,7 @@ typedef struct b2WorldDef /// This parameter controls how fast overlap is resolved and usually has units of meters per second. This only /// puts a cap on the resolution speed. The resolution speed is increased by increasing the hertz and/or /// decreasing the damping ratio. - float contactPushMaxSpeed; + float maxContactPushSpeed; /// Joint stiffness. Cycles per second. float jointHertz; @@ -365,14 +365,19 @@ typedef struct b2ShapeDef uint32_t customColor; /// A sensor shape generates overlap events but never generates a collision response. - /// Sensors do not collide with other sensors and do not have continuous collision. - /// Instead, use a ray or shape cast for those scenarios. + /// Sensors do not have continuous collision. Instead, use a ray or shape cast for those scenarios. + /// Sensors still contribute to the body mass if they have non-zero density. + /// @note Sensor events are disabled by default. + /// @see enableSensorEvents bool isSensor; - /// Enable contact events for this shape. Only applies to kinematic and dynamic bodies. Ignored for sensors. + /// Enable sensor events for this shape. This applies to sensors and non-sensors. False by default, even for sensors. + bool enableSensorEvents; + + /// Enable contact events for this shape. Only applies to kinematic and dynamic bodies. Ignored for sensors. False by default. bool enableContactEvents; - /// Enable hit events for this shape. Only applies to kinematic and dynamic bodies. Ignored for sensors. + /// Enable hit events for this shape. Only applies to kinematic and dynamic bodies. Ignored for sensors. False by default. bool enableHitEvents; /// Enable pre-solve contact events for this shape. Only applies to dynamic bodies. These are expensive @@ -464,6 +469,9 @@ typedef struct b2ChainDef /// Indicates a closed chain formed by connecting the first and last points bool isLoop; + /// Enable sensors to detect this chain. False by default. + bool enableSensorEvents; + /// Used internally to detect a valid definition. DO NOT SET. int internalValue; } b2ChainDef; @@ -1377,32 +1385,32 @@ typedef enum b2HexColor typedef struct b2DebugDraw { /// Draw a closed polygon provided in CCW order. - void ( *DrawPolygon )( const b2Vec2* vertices, int vertexCount, b2HexColor color, void* context ); + void ( *DrawPolygonFcn )( const b2Vec2* vertices, int vertexCount, b2HexColor color, void* context ); /// Draw a solid closed polygon provided in CCW order. - void ( *DrawSolidPolygon )( b2Transform transform, const b2Vec2* vertices, int vertexCount, float radius, b2HexColor color, + void ( *DrawSolidPolygonFcn )( b2Transform transform, const b2Vec2* vertices, int vertexCount, float radius, b2HexColor color, void* context ); /// Draw a circle. - void ( *DrawCircle )( b2Vec2 center, float radius, b2HexColor color, void* context ); + void ( *DrawCircleFcn )( b2Vec2 center, float radius, b2HexColor color, void* context ); /// Draw a solid circle. - void ( *DrawSolidCircle )( b2Transform transform, float radius, b2HexColor color, void* context ); + void ( *DrawSolidCircleFcn )( b2Transform transform, float radius, b2HexColor color, void* context ); /// Draw a solid capsule. - void ( *DrawSolidCapsule )( b2Vec2 p1, b2Vec2 p2, float radius, b2HexColor color, void* context ); + void ( *DrawSolidCapsuleFcn )( b2Vec2 p1, b2Vec2 p2, float radius, b2HexColor color, void* context ); /// Draw a line segment. - void ( *DrawSegment )( b2Vec2 p1, b2Vec2 p2, b2HexColor color, void* context ); + void ( *DrawSegmentFcn )( b2Vec2 p1, b2Vec2 p2, b2HexColor color, void* context ); /// Draw a transform. Choose your own length scale. - void ( *DrawTransform )( b2Transform transform, void* context ); + void ( *DrawTransformFcn )( b2Transform transform, void* context ); /// Draw a point. - void ( *DrawPoint )( b2Vec2 p, float size, b2HexColor color, void* context ); + void ( *DrawPointFcn )( b2Vec2 p, float size, b2HexColor color, void* context ); /// Draw a string in world space - void ( *DrawString )( b2Vec2 p, const char* s, b2HexColor color, void* context ); + void ( *DrawStringFcn )( b2Vec2 p, const char* s, b2HexColor color, void* context ); /// Bounds to use if restricting drawing to a rectangular region b2AABB drawingBounds; @@ -1420,7 +1428,7 @@ typedef struct b2DebugDraw bool drawJointExtras; /// Option to draw the bounding boxes for shapes - bool drawAABBs; + bool drawBounds; /// Option to draw the mass and center of mass of dynamic bodies bool drawMass; @@ -1440,9 +1448,15 @@ typedef struct b2DebugDraw /// Option to draw contact normal impulses bool drawContactImpulses; + /// Option to draw contact feature ids + bool drawContactFeatures; + /// Option to draw contact friction impulses bool drawFrictionImpulses; + /// Option to draw islands as bounding boxes + bool drawIslands; + /// User context that is passed as an argument to drawing callback functions void* context; } b2DebugDraw; diff --git a/samples/donut.cpp b/samples/donut.cpp index 7acad387d..4dfd41334 100644 --- a/samples/donut.cpp +++ b/samples/donut.cpp @@ -19,7 +19,7 @@ Donut::Donut() m_isSpawned = false; } -void Donut::Spawn( b2WorldId worldId, b2Vec2 position, float scale, int groupIndex, void* userData ) +void Donut::Create( b2WorldId worldId, b2Vec2 position, float scale, int groupIndex, bool enableSensorEvents, void* userData ) { assert( m_isSpawned == false ); @@ -42,7 +42,7 @@ void Donut::Spawn( b2WorldId worldId, b2Vec2 position, float scale, int groupInd bodyDef.userData = userData; b2ShapeDef shapeDef = b2DefaultShapeDef(); - shapeDef.density = 1.0f; + shapeDef.enableSensorEvents = enableSensorEvents; shapeDef.filter.groupIndex = -groupIndex; shapeDef.friction = 0.3f; @@ -81,7 +81,7 @@ void Donut::Spawn( b2WorldId worldId, b2Vec2 position, float scale, int groupInd m_isSpawned = true; } -void Donut::Despawn() +void Donut::Destroy() { assert( m_isSpawned == true ); diff --git a/samples/donut.h b/samples/donut.h index aecc016c5..2f4b3359a 100644 --- a/samples/donut.h +++ b/samples/donut.h @@ -15,8 +15,8 @@ class Donut public: Donut(); - void Spawn( b2WorldId worldId, b2Vec2 position, float scale, int groupIndex, void* userData ); - void Despawn(); + void Create( b2WorldId worldId, b2Vec2 position, float scale, int groupIndex, bool enableSensorEvents, void* userData ); + void Destroy(); b2BodyId m_bodyIds[e_sides]; b2JointId m_jointIds[e_sides]; diff --git a/samples/draw.cpp b/samples/draw.cpp index 990082a89..55fc56edf 100644 --- a/samples/draw.cpp +++ b/samples/draw.cpp @@ -12,7 +12,7 @@ #include #include -#if defined( _WIN32 ) +#if defined( _MSC_VER ) #define _CRTDBG_MAP_ALLOC #include #include @@ -157,7 +157,7 @@ struct GLBackground glBufferData( GL_ARRAY_BUFFER, sizeof( vertices ), vertices, GL_STATIC_DRAW ); glVertexAttribPointer( vertexAttribute, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET( 0 ) ); - CheckErrorGL(); + CheckOpenGL(); // Cleanup glBindBuffer( GL_ARRAY_BUFFER, 0 ); @@ -186,8 +186,8 @@ struct GLBackground glUseProgram( m_programId ); float time = (float)glfwGetTime(); - time = fmodf(time, 100.0f); - + time = fmodf( time, 100.0f ); + glUniform1f( m_timeUniform, time ); glUniform2f( m_resolutionUniform, (float)g_camera.m_width, (float)g_camera.m_height ); @@ -270,7 +270,7 @@ struct GLPoints glVertexAttribPointer( colorAttribute, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof( PointData ), (void*)offsetof( PointData, rgba ) ); - CheckErrorGL(); + CheckOpenGL(); // Cleanup glBindBuffer( GL_ARRAY_BUFFER, 0 ); @@ -329,7 +329,7 @@ struct GLPoints glBufferSubData( GL_ARRAY_BUFFER, 0, batchCount * sizeof( PointData ), &m_points[base] ); glDrawArrays( GL_POINTS, 0, batchCount ); - CheckErrorGL(); + CheckOpenGL(); count -= e_batchSize; base += e_batchSize; @@ -366,24 +366,30 @@ struct GLLines { void Create() { - const char* vs = "#version 330\n" - "uniform mat4 projectionMatrix;\n" - "layout(location = 0) in vec2 v_position;\n" - "layout(location = 1) in vec4 v_color;\n" - "out vec4 f_color;\n" - "void main(void)\n" - "{\n" - " f_color = v_color;\n" - " gl_Position = projectionMatrix * vec4(v_position, 0.0f, 1.0f);\n" - "}\n"; - - const char* fs = "#version 330\n" - "in vec4 f_color;\n" - "out vec4 color;\n" - "void main(void)\n" - "{\n" - " color = f_color;\n" - "}\n"; + const char* vs = + R"( + #version 330 + uniform mat4 projectionMatrix; + layout(location = 0) in vec2 v_position; + layout(location = 1) in vec4 v_color; + out vec4 f_color; + void main(void) + { + f_color = v_color; + gl_Position = projectionMatrix * vec4(v_position, 0.0f, 1.0f); + } + )"; + + const char* fs = + R"( + #version 330 + in vec4 f_color; + out vec4 color; + void main(void) + { + color = f_color; + } + )"; m_programId = CreateProgramFromStrings( vs, fs ); m_projectionUniform = glGetUniformLocation( m_programId, "projectionMatrix" ); @@ -408,7 +414,7 @@ struct GLLines glVertexAttribPointer( colorAttribute, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof( VertexData ), (void*)offsetof( VertexData, rgba ) ); - CheckErrorGL(); + CheckOpenGL(); // Cleanup glBindBuffer( GL_ARRAY_BUFFER, 0 ); @@ -468,7 +474,7 @@ struct GLLines glDrawArrays( GL_LINES, 0, batchCount ); - CheckErrorGL(); + CheckOpenGL(); count -= e_batchSize; base += e_batchSize; @@ -497,145 +503,6 @@ struct GLLines GLint m_projectionUniform; }; -// todo this is not used anymore and has untested changes -struct GLTriangles -{ - void Create() - { - const char* vs = "#version 330\n" - "uniform mat4 projectionMatrix;\n" - "layout(location = 0) in vec2 v_position;\n" - "layout(location = 1) in vec4 v_color;\n" - "out vec4 f_color;\n" - "void main(void)\n" - "{\n" - " f_color = v_color;\n" - " gl_Position = projectionMatrix * vec4(v_position, 0.0f, 1.0f);\n" - "}\n"; - - const char* fs = "#version 330\n" - "in vec4 f_color;\n" - "out vec4 color;\n" - "void main(void)\n" - "{\n" - " color = f_color;\n" - "}\n"; - - m_programId = CreateProgramFromStrings( vs, fs ); - m_projectionUniform = glGetUniformLocation( m_programId, "projectionMatrix" ); - int vertexAttribute = 0; - int colorAttribute = 1; - - // Generate - glGenVertexArrays( 1, &m_vaoId ); - glGenBuffers( 1, &m_vboId ); - - glBindVertexArray( m_vaoId ); - glEnableVertexAttribArray( vertexAttribute ); - glEnableVertexAttribArray( colorAttribute ); - - // Vertex buffer - glBindBuffer( GL_ARRAY_BUFFER, m_vboId ); - glBufferData( GL_ARRAY_BUFFER, e_batchSize * sizeof( VertexData ), nullptr, GL_DYNAMIC_DRAW ); - - glVertexAttribPointer( vertexAttribute, 2, GL_FLOAT, GL_FALSE, sizeof( VertexData ), - (void*)offsetof( VertexData, position ) ); - // color will get automatically expanded to floats in the shader - glVertexAttribPointer( colorAttribute, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof( VertexData ), - (void*)offsetof( VertexData, rgba ) ); - - CheckErrorGL(); - - // Cleanup - glBindBuffer( GL_ARRAY_BUFFER, 0 ); - glBindVertexArray( 0 ); - } - - void Destroy() - { - if ( m_vaoId ) - { - glDeleteVertexArrays( 1, &m_vaoId ); - glDeleteBuffers( 1, &m_vboId ); - m_vaoId = 0; - m_vboId = 0; - } - - if ( m_programId ) - { - glDeleteProgram( m_programId ); - m_programId = 0; - } - } - - void AddTriangle( b2Vec2 p1, b2Vec2 p2, b2Vec2 p3, b2HexColor c ) - { - RGBA8 rgba = MakeRGBA8( c, 1.0f ); - m_points.push_back( { p1, rgba } ); - m_points.push_back( { p2, rgba } ); - m_points.push_back( { p3, rgba } ); - } - - void Flush() - { - int count = (int)m_points.size(); - if ( count == 0 ) - { - return; - } - - assert( count % 3 == 0 ); - - glUseProgram( m_programId ); - - float proj[16] = { 0.0f }; - g_camera.BuildProjectionMatrix( proj, 0.2f ); - - glUniformMatrix4fv( m_projectionUniform, 1, GL_FALSE, proj ); - - glBindVertexArray( m_vaoId ); - - glBindBuffer( GL_ARRAY_BUFFER, m_vboId ); - glEnable( GL_BLEND ); - glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); - - int base = 0; - while ( count > 0 ) - { - int batchCount = b2MinInt( count, e_batchSize ); - - glBufferSubData( GL_ARRAY_BUFFER, 0, batchCount * sizeof( VertexData ), &m_points[base] ); - glDrawArrays( GL_TRIANGLES, 0, batchCount ); - - CheckErrorGL(); - - count -= e_batchSize; - base += e_batchSize; - } - - glDisable( GL_BLEND ); - - glBindBuffer( GL_ARRAY_BUFFER, 0 ); - glBindVertexArray( 0 ); - glUseProgram( 0 ); - - m_points.clear(); - } - - enum - { - // must be multiple of 3 - e_batchSize = 3 * 512 - }; - - std::vector m_points; - - GLuint m_vaoId; - GLuint m_vboId; - GLuint m_programId; - GLint m_projectionUniform; -}; - struct CircleData { b2Vec2 position; @@ -687,7 +554,7 @@ struct GLCircles glVertexAttribDivisor( radiusInstance, 1 ); glVertexAttribDivisor( colorInstance, 1 ); - CheckErrorGL(); + CheckOpenGL(); // Cleanup glBindBuffer( GL_ARRAY_BUFFER, 0 ); @@ -748,7 +615,7 @@ struct GLCircles glBufferSubData( GL_ARRAY_BUFFER, 0, batchCount * sizeof( CircleData ), &m_circles[base] ); glDrawArraysInstanced( GL_TRIANGLES, 0, 6, batchCount ); - CheckErrorGL(); + CheckOpenGL(); count -= e_batchSize; base += e_batchSize; @@ -832,7 +699,7 @@ struct GLSolidCircles glVertexAttribDivisor( radiusInstance, 1 ); glVertexAttribDivisor( colorInstance, 1 ); - CheckErrorGL(); + CheckOpenGL(); // Cleanup glBindBuffer( GL_ARRAY_BUFFER, 0 ); @@ -893,7 +760,7 @@ struct GLSolidCircles glBufferSubData( GL_ARRAY_BUFFER, 0, batchCount * sizeof( SolidCircleData ), &m_circles[base] ); glDrawArraysInstanced( GL_TRIANGLES, 0, 6, batchCount ); - CheckErrorGL(); + CheckOpenGL(); count -= e_batchSize; base += e_batchSize; @@ -982,7 +849,7 @@ struct GLSolidCapsules glVertexAttribDivisor( lengthInstance, 1 ); glVertexAttribDivisor( colorInstance, 1 ); - CheckErrorGL(); + CheckOpenGL(); // Cleanup glBindBuffer( GL_ARRAY_BUFFER, 0 ); @@ -1058,7 +925,7 @@ struct GLSolidCapsules glBufferSubData( GL_ARRAY_BUFFER, 0, batchCount * sizeof( CapsuleData ), &m_capsules[base] ); glDrawArraysInstanced( GL_TRIANGLES, 0, 6, batchCount ); - CheckErrorGL(); + CheckOpenGL(); count -= e_batchSize; base += e_batchSize; @@ -1169,7 +1036,7 @@ struct GLSolidPolygons glVertexAttribDivisor( instanceRadius, 1 ); glVertexAttribDivisor( instanceColor, 1 ); - CheckErrorGL(); + CheckOpenGL(); // Cleanup glBindBuffer( GL_ARRAY_BUFFER, 0 ); @@ -1240,7 +1107,7 @@ struct GLSolidPolygons glBufferSubData( GL_ARRAY_BUFFER, 0, batchCount * sizeof( PolygonData ), &m_polygons[base] ); glDrawArraysInstanced( GL_TRIANGLES, 0, 6, batchCount ); - CheckErrorGL(); + CheckOpenGL(); count -= e_batchSize; base += e_batchSize; @@ -1320,7 +1187,6 @@ Draw::Draw() m_showUI = true; m_points = nullptr; m_lines = nullptr; - m_triangles = nullptr; m_circles = nullptr; m_solidCircles = nullptr; m_solidCapsules = nullptr; @@ -1337,7 +1203,6 @@ Draw::~Draw() { assert( m_points == nullptr ); assert( m_lines == nullptr ); - assert( m_triangles == nullptr ); assert( m_circles == nullptr ); assert( m_solidCircles == nullptr ); assert( m_solidCapsules == nullptr ); @@ -1353,8 +1218,6 @@ void Draw::Create() m_points->Create(); m_lines = new GLLines; m_lines->Create(); - m_triangles = new GLTriangles; - m_triangles->Create(); m_circles = new GLCircles; m_circles->Create(); m_solidCircles = new GLSolidCircles; @@ -1368,28 +1231,30 @@ void Draw::Create() m_debugDraw = {}; - m_debugDraw.DrawPolygon = DrawPolygonFcn; - m_debugDraw.DrawSolidPolygon = DrawSolidPolygonFcn; - m_debugDraw.DrawCircle = DrawCircleFcn; - m_debugDraw.DrawSolidCircle = DrawSolidCircleFcn; - m_debugDraw.DrawSolidCapsule = DrawSolidCapsuleFcn; - m_debugDraw.DrawSegment = DrawSegmentFcn; - m_debugDraw.DrawTransform = DrawTransformFcn; - m_debugDraw.DrawPoint = DrawPointFcn; - m_debugDraw.DrawString = DrawStringFcn; + m_debugDraw.DrawPolygonFcn = DrawPolygonFcn; + m_debugDraw.DrawSolidPolygonFcn = DrawSolidPolygonFcn; + m_debugDraw.DrawCircleFcn = DrawCircleFcn; + m_debugDraw.DrawSolidCircleFcn = DrawSolidCircleFcn; + m_debugDraw.DrawSolidCapsuleFcn = DrawSolidCapsuleFcn; + m_debugDraw.DrawSegmentFcn = DrawSegmentFcn; + m_debugDraw.DrawTransformFcn = DrawTransformFcn; + m_debugDraw.DrawPointFcn = DrawPointFcn; + m_debugDraw.DrawStringFcn = DrawStringFcn; m_debugDraw.drawingBounds = bounds; m_debugDraw.useDrawingBounds = false; m_debugDraw.drawShapes = true; m_debugDraw.drawJoints = true; m_debugDraw.drawJointExtras = false; - m_debugDraw.drawAABBs = false; + m_debugDraw.drawBounds = false; m_debugDraw.drawMass = false; m_debugDraw.drawContacts = false; m_debugDraw.drawGraphColors = false; m_debugDraw.drawContactNormals = false; m_debugDraw.drawContactImpulses = false; + m_debugDraw.drawContactFeatures = false; m_debugDraw.drawFrictionImpulses = false; + m_debugDraw.drawIslands = false; m_debugDraw.context = this; } @@ -1408,10 +1273,6 @@ void Draw::Destroy() delete m_lines; m_lines = nullptr; - m_triangles->Destroy(); - delete m_triangles; - m_triangles = nullptr; - m_circles->Destroy(); delete m_circles; m_circles = nullptr; @@ -1536,11 +1397,10 @@ void Draw::Flush() m_solidCircles->Flush(); m_solidCapsules->Flush(); m_solidPolygons->Flush(); - m_triangles->Flush(); m_circles->Flush(); m_lines->Flush(); m_points->Flush(); - CheckErrorGL(); + CheckOpenGL(); } void Draw::DrawBackground() diff --git a/samples/draw.h b/samples/draw.h index 4b3f44fe1..a21cca95d 100644 --- a/samples/draw.h +++ b/samples/draw.h @@ -60,7 +60,6 @@ class Draw struct GLBackground* m_background; struct GLPoints* m_points; struct GLLines* m_lines; - struct GLTriangles* m_triangles; struct GLCircles* m_circles; struct GLSolidCircles* m_solidCircles; struct GLSolidCapsules* m_solidCapsules; diff --git a/samples/main.cpp b/samples/main.cpp index d80c821be..5c15e5c7b 100644 --- a/samples/main.cpp +++ b/samples/main.cpp @@ -1,8 +1,13 @@ // SPDX-FileCopyrightText: 2023 Erin Catto // SPDX-License-Identifier: MIT +#if defined( _MSC_VER ) +#ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS +#endif #define _CRTDBG_MAP_ALLOC +#endif + #define IMGUI_DISABLE_OBSOLETE_FUNCTIONS 1 #include "TaskScheduler.h" @@ -23,7 +28,6 @@ #include "imgui_impl_glfw.h" #include "imgui_impl_opengl3.h" - #include #include @@ -33,7 +37,7 @@ #define FrameMark #endif -#if defined( _WIN32 ) && 0 +#if defined( _MSC_VER ) && 0 #include static int MyAllocHook( int allocType, void* userData, size_t size, int blockType, long requestNumber, @@ -71,7 +75,7 @@ void* AllocFcn( uint32_t size, int32_t alignment ) size_t sizeAligned = ( ( size - 1 ) | ( alignment - 1 ) ) + 1; assert( ( sizeAligned & ( alignment - 1 ) ) == 0 ); -#if defined( _WIN64 ) || defined( _WIN32 ) +#if defined( _MSC_VER ) void* ptr = _aligned_malloc( sizeAligned, alignment ); #else void* ptr = aligned_alloc( alignment, sizeAligned ); @@ -82,7 +86,7 @@ void* AllocFcn( uint32_t size, int32_t alignment ) void FreeFcn( void* mem ) { -#if defined( _WIN64 ) || defined( _WIN32 ) +#if defined( _MSC_VER ) _aligned_free( mem ); #else free( mem ); @@ -418,14 +422,16 @@ static void UpdateUI() ImGui::Checkbox( "Shapes", &s_settings.drawShapes ); ImGui::Checkbox( "Joints", &s_settings.drawJoints ); ImGui::Checkbox( "Joint Extras", &s_settings.drawJointExtras ); - ImGui::Checkbox( "AABBs", &s_settings.drawAABBs ); + ImGui::Checkbox( "Bounds", &s_settings.drawBounds ); ImGui::Checkbox( "Contact Points", &s_settings.drawContactPoints ); ImGui::Checkbox( "Contact Normals", &s_settings.drawContactNormals ); ImGui::Checkbox( "Contact Impulses", &s_settings.drawContactImpulses ); + ImGui::Checkbox( "Contact Features", &s_settings.drawContactFeatures ); ImGui::Checkbox( "Friction Impulses", &s_settings.drawFrictionImpulses ); - ImGui::Checkbox( "Center of Masses", &s_settings.drawMass ); + ImGui::Checkbox( "Mass", &s_settings.drawMass ); ImGui::Checkbox( "Body Names", &s_settings.drawBodyNames ); ImGui::Checkbox( "Graph Colors", &s_settings.drawGraphColors ); + ImGui::Checkbox( "Islands", &s_settings.drawIslands ); ImGui::Checkbox( "Counters", &s_settings.drawCounters ); ImGui::Checkbox( "Profile", &s_settings.drawProfile ); @@ -519,13 +525,13 @@ static void UpdateUI() ImGui::End(); - s_sample->UpdateUI(); + s_sample->UpdateGui(); } } int main( int, char** ) { -#if defined( _WIN32 ) +#if defined( _MSC_VER ) // Enable memory-leak reports _CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE ); _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDOUT ); @@ -665,7 +671,7 @@ int main( int, char** ) glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); - //g_draw.DrawBackground(); + // g_draw.DrawBackground(); double cursorPosX = 0, cursorPosY = 0; glfwGetCursorPos( g_mainWindow, &cursorPosX, &cursorPosY ); @@ -772,7 +778,7 @@ int main( int, char** ) s_settings.Save(); -#if defined( _WIN32 ) +#if defined( _MSC_VER ) _CrtDumpMemoryLeaks(); #endif diff --git a/samples/sample.cpp b/samples/sample.cpp index fe3c14108..4551cf278 100644 --- a/samples/sample.cpp +++ b/samples/sample.cpp @@ -1,7 +1,9 @@ // SPDX-FileCopyrightText: 2023 Erin Catto // SPDX-License-Identifier: MIT +#if defined( _MSC_VER ) && !defined( _CRT_SECURE_NO_WARNINGS ) #define _CRT_SECURE_NO_WARNINGS +#endif #include "sample.h" @@ -15,8 +17,8 @@ #include "box2d/math_functions.h" #include -#include #include +#include class SampleTask : public enki::ITaskSet { @@ -147,7 +149,6 @@ void Sample::CreateWorld() worldDef.finishTask = FinishTask; worldDef.userTaskContext = this; worldDef.enableSleep = m_settings->enableSleep; - m_worldId = b2CreateWorld( &worldDef ); } @@ -316,14 +317,16 @@ void Sample::Step( Settings& settings ) g_draw.m_debugDraw.drawShapes = settings.drawShapes; g_draw.m_debugDraw.drawJoints = settings.drawJoints; g_draw.m_debugDraw.drawJointExtras = settings.drawJointExtras; - g_draw.m_debugDraw.drawAABBs = settings.drawAABBs; + g_draw.m_debugDraw.drawBounds = settings.drawBounds; g_draw.m_debugDraw.drawMass = settings.drawMass; g_draw.m_debugDraw.drawBodyNames = settings.drawBodyNames; g_draw.m_debugDraw.drawContacts = settings.drawContactPoints; g_draw.m_debugDraw.drawGraphColors = settings.drawGraphColors; g_draw.m_debugDraw.drawContactNormals = settings.drawContactNormals; g_draw.m_debugDraw.drawContactImpulses = settings.drawContactImpulses; + g_draw.m_debugDraw.drawContactFeatures = settings.drawContactFeatures; g_draw.m_debugDraw.drawFrictionImpulses = settings.drawFrictionImpulses; + g_draw.m_debugDraw.drawIslands = settings.drawIslands; b2World_EnableSleeping( m_worldId, settings.enableSleep ); b2World_EnableWarmStarting( m_worldId, settings.enableWarmStarting ); @@ -346,36 +349,25 @@ void Sample::Step( Settings& settings ) { b2Counters s = b2World_GetCounters( m_worldId ); - g_draw.DrawString( 5, m_textLine, "bodies/shapes/contacts/joints = %d/%d/%d/%d", s.bodyCount, s.shapeCount, - s.contactCount, s.jointCount ); - m_textLine += m_textIncrement; - - g_draw.DrawString( 5, m_textLine, "islands/tasks = %d/%d", s.islandCount, s.taskCount ); - m_textLine += m_textIncrement; - - g_draw.DrawString( 5, m_textLine, "tree height static/movable = %d/%d", s.staticTreeHeight, s.treeHeight ); - m_textLine += m_textIncrement; + DrawTextLine( "bodies/shapes/contacts/joints = %d/%d/%d/%d", s.bodyCount, s.shapeCount, s.contactCount, s.jointCount ); + DrawTextLine( "islands/tasks = %d/%d", s.islandCount, s.taskCount ); + DrawTextLine( "tree height static/movable = %d/%d", s.staticTreeHeight, s.treeHeight ); int totalCount = 0; char buffer[256] = { 0 }; - static_assert( std::size( s.colorCounts ) == 12 ); + int colorCount = sizeof( s.colorCounts ) / sizeof( s.colorCounts[0] ); // todo fix this int offset = snprintf( buffer, 256, "colors: " ); - for ( int i = 0; i < 12; ++i ) + for ( int i = 0; i < colorCount; ++i ) { offset += snprintf( buffer + offset, 256 - offset, "%d/", s.colorCounts[i] ); totalCount += s.colorCounts[i]; } snprintf( buffer + offset, 256 - offset, "[%d]", totalCount ); - g_draw.DrawString( 5, m_textLine, buffer ); - m_textLine += m_textIncrement; - - g_draw.DrawString( 5, m_textLine, "stack allocator size = %d K", s.stackUsed / 1024 ); - m_textLine += m_textIncrement; - - g_draw.DrawString( 5, m_textLine, "total allocation = %d K", s.byteCount / 1024 ); - m_textLine += m_textIncrement; + DrawTextLine( buffer ); + DrawTextLine( "stack allocator size = %d K", s.stackUsed / 1024 ); + DrawTextLine( "total allocation = %d K", s.byteCount / 1024 ); } // Track maximum profile times @@ -638,7 +630,6 @@ int Sample::ParsePath( const char* svgPath, b2Vec2 offset, b2Vec2* points, int c if ( reverseOrder ) { - } return pointCount; } diff --git a/samples/sample.h b/samples/sample.h index 259b88a04..f26bf25f2 100644 --- a/samples/sample.h +++ b/samples/sample.h @@ -46,7 +46,7 @@ class Sample void DrawTitle( const char* string ); virtual void Step( Settings& settings ); - virtual void UpdateUI() + virtual void UpdateGui() { } virtual void Keyboard( int ) diff --git a/samples/sample_benchmark.cpp b/samples/sample_benchmark.cpp index c02dbf64a..54efdbf94 100644 --- a/samples/sample_benchmark.cpp +++ b/samples/sample_benchmark.cpp @@ -294,7 +294,7 @@ class BenchmarkBarrel : public Sample } } - void UpdateUI() override + void UpdateGui() override { float height = 80.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -468,7 +468,7 @@ class BenchmarkManyTumblers : public Sample m_bodyIndex = 0; } - void UpdateUI() override + void UpdateGui() override { float height = 110.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -1199,7 +1199,7 @@ class BenchmarkCast : public Sample m_minTime = 1e6f; } - void UpdateUI() override + void UpdateGui() override { float height = 240.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); diff --git a/samples/sample_bodies.cpp b/samples/sample_bodies.cpp index 987e0ffa0..2cc21a3bc 100644 --- a/samples/sample_bodies.cpp +++ b/samples/sample_bodies.cpp @@ -183,7 +183,7 @@ class BodyType : public Sample } } - void UpdateUI() override + void UpdateGui() override { float height = 140.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -534,7 +534,7 @@ class Weeble : public Sample m_explosionMagnitude = 8.0f; } - void UpdateUI() override + void UpdateGui() override { float height = 120.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -612,6 +612,7 @@ class Sleep : public Sample b2Segment segment = { { -20.0f, 0.0f }, { 20.0f, 0.0f } }; b2ShapeDef shapeDef = b2DefaultShapeDef(); + shapeDef.enableSensorEvents = true; m_groundShapeId = b2CreateSegmentShape( groundId, &shapeDef, &segment ); } @@ -630,6 +631,7 @@ class Sleep : public Sample b2CreateCapsuleShape( bodyId, &shapeDef, &capsule ); shapeDef.isSensor = true; + shapeDef.enableSensorEvents = true; capsule.radius = 1.0f; m_sensorIds[i] = b2CreateCapsuleShape( bodyId, &shapeDef, &capsule ); m_sensorTouching[i] = false; @@ -700,7 +702,7 @@ class Sleep : public Sample } } - void UpdateUI() override + void UpdateGui() override { float height = 100.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); diff --git a/samples/sample_collision.cpp b/samples/sample_collision.cpp index b4e0b540c..f7ed8d536 100644 --- a/samples/sample_collision.cpp +++ b/samples/sample_collision.cpp @@ -16,6 +16,22 @@ constexpr int SIMPLEX_CAPACITY = 20; +/* +- input 0x0000008f63efcee0 {proxyA={points=0x0000008f63efcee0 {{...}, {...}, {...}, {...}, {...}, {...}, {...}, ...} ...} ...} const b2DistanceInput * ++ [0] {x=-0.400000006 y=-0.400000006 } b2Vec2 ++ [1] {x=0.400000006 y=-0.400000006 } b2Vec2 ++ [2] {x=0.400000006 y=0.400000006 } b2Vec2 ++ [3] {x=-0.400000006 y=0.400000006 } b2Vec2 + ++ [0] {x=-0.500000000 y=-0.500000000 } b2Vec2 ++ [1] {x=0.500000000 y=-0.500000000 } b2Vec2 ++ [2] {x=0.500000000 y=0.500000000 } b2Vec2 ++ [3] {x=-0.500000000 y=0.500000000 } b2Vec2 + ++ transformA {p={x=3.00000000 y=5.00000000 } q={c=1.00000000 s=0.00000000 } } b2Transform ++ transformB {p={x=3.00000000 y=5.00000000 } q={c=1.00000000 s=0.00000000 } } b2Transform + useRadii true bool + */ class ShapeDistance : public Sample { public: @@ -43,11 +59,14 @@ class ShapeDistance : public Sample b2Vec2 points[3] = { { -0.5f, 0.0f }, { 0.5f, 0.0f }, { 0.0f, 1.0f } }; b2Hull hull = b2ComputeHull( points, 3 ); m_triangle = b2MakePolygon( &hull, 0.0f ); + + m_triangle = b2MakeSquare( 0.4f ); } - m_box = b2MakeBox( 0.5f, 0.5f ); + m_box = b2MakeSquare( 0.5f ); - m_transform = { { 1.5f, -1.5f }, b2Rot_identity }; + //m_transform = { { 1.5f, -1.5f }, b2Rot_identity }; + m_transform = { { 0.0f, 0.0f }, b2Rot_identity }; m_angle = 0.0f; m_cache = b2_emptySimplexCache; @@ -90,10 +109,11 @@ class ShapeDistance : public Sample break; case e_triangle: - proxy.points[0] = m_triangle.vertices[0]; - proxy.points[1] = m_triangle.vertices[1]; - proxy.points[2] = m_triangle.vertices[2]; - proxy.count = 3; + for (int i = 0; i < m_triangle.count; ++i) + { + proxy.points[i] = m_triangle.vertices[i]; + } + proxy.count = m_triangle.count; break; case e_box: @@ -146,11 +166,11 @@ class ShapeDistance : public Sample break; case e_triangle: - g_draw.DrawSolidPolygon( transform, m_triangle.vertices, 3, radius, color ); + g_draw.DrawSolidPolygon( transform, m_triangle.vertices, m_triangle.count, radius, color ); break; case e_box: - g_draw.DrawSolidPolygon( transform, m_box.vertices, 4, radius, color ); + g_draw.DrawSolidPolygon( transform, m_box.vertices, m_box.count, radius, color ); break; default: @@ -158,7 +178,7 @@ class ShapeDistance : public Sample } } - void UpdateUI() override + void UpdateGui() override { float height = 310.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -311,7 +331,7 @@ class ShapeDistance : public Sample input.proxyB = m_proxyB; input.transformA = b2Transform_identity; input.transformB = m_transform; - input.useRadii = m_radiusA > 0.0f || m_radiusB > 0.0f; + input.useRadii = true || m_radiusA > 0.0f || m_radiusB > 0.0f; if ( m_useCache == false ) { @@ -352,9 +372,11 @@ class ShapeDistance : public Sample } else { - g_draw.DrawSegment( output.pointA, output.pointB, b2_colorWhite ); + g_draw.DrawSegment( output.pointA, output.pointB, b2_colorDimGray ); g_draw.DrawPoint( output.pointA, 5.0f, b2_colorWhite ); g_draw.DrawPoint( output.pointB, 5.0f, b2_colorWhite ); + + g_draw.DrawSegment( output.pointA, output.pointA + 0.5f * output.normal, b2_colorYellow ); } if ( m_showIndices ) @@ -567,7 +589,7 @@ class DynamicTree : public Sample } } - void UpdateUI() override + void UpdateGui() override { float height = 320.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -912,7 +934,7 @@ class RayCast : public Sample m_showFraction = false; } - void UpdateUI() override + void UpdateGui() override { float height = 230.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -1209,7 +1231,7 @@ struct ShapeUserData }; // Context for ray cast callbacks. Do what you want with this. -struct RayCastContext +struct CastContext { b2Vec2 points[3]; b2Vec2 normals[3]; @@ -1220,7 +1242,7 @@ struct RayCastContext // This callback finds the closest hit. This is the most common callback used in games. static float RayCastClosestCallback( b2ShapeId shapeId, b2Vec2 point, b2Vec2 normal, float fraction, void* context ) { - RayCastContext* rayContext = (RayCastContext*)context; + CastContext* rayContext = (CastContext*)context; ShapeUserData* userData = (ShapeUserData*)b2Shape_GetUserData( shapeId ); if ( userData != nullptr && userData->ignore ) @@ -1246,7 +1268,7 @@ static float RayCastClosestCallback( b2ShapeId shapeId, b2Vec2 point, b2Vec2 nor // NOTE: shape hits are not ordered, so this may not return the closest hit static float RayCastAnyCallback( b2ShapeId shapeId, b2Vec2 point, b2Vec2 normal, float fraction, void* context ) { - RayCastContext* rayContext = (RayCastContext*)context; + CastContext* rayContext = (CastContext*)context; ShapeUserData* userData = (ShapeUserData*)b2Shape_GetUserData( shapeId ); if ( userData != nullptr && userData->ignore ) @@ -1274,7 +1296,7 @@ static float RayCastAnyCallback( b2ShapeId shapeId, b2Vec2 point, b2Vec2 normal, // behavior in the sample. static float RayCastMultipleCallback( b2ShapeId shapeId, b2Vec2 point, b2Vec2 normal, float fraction, void* context ) { - RayCastContext* rayContext = (RayCastContext*)context; + CastContext* rayContext = (CastContext*)context; ShapeUserData* userData = (ShapeUserData*)b2Shape_GetUserData( shapeId ); if ( userData != nullptr && userData->ignore ) @@ -1306,7 +1328,7 @@ static float RayCastMultipleCallback( b2ShapeId shapeId, b2Vec2 point, b2Vec2 no // This ray cast collects multiple hits along the ray and sorts them. static float RayCastSortedCallback( b2ShapeId shapeId, b2Vec2 point, b2Vec2 normal, float fraction, void* context ) { - RayCastContext* rayContext = (RayCastContext*)context; + CastContext* rayContext = (CastContext*)context; ShapeUserData* userData = (ShapeUserData*)b2Shape_GetUserData( shapeId ); if ( userData != nullptr && userData->ignore ) @@ -1360,7 +1382,7 @@ static float RayCastSortedCallback( b2ShapeId shapeId, b2Vec2 point, b2Vec2 norm return 1.0f; } -class RayCastWorld : public Sample +class CastWorld : public Sample { public: enum Mode @@ -1384,7 +1406,7 @@ class RayCastWorld : public Sample e_maxCount = 64 }; - explicit RayCastWorld( Settings& settings ) + explicit CastWorld( Settings& settings ) : Sample( settings ) { if ( settings.restart == false ) @@ -1580,9 +1602,9 @@ class RayCastWorld : public Sample } } - void UpdateUI() override + void UpdateGui() override { - float height = 300.0f; + float height = 320.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); ImGui::SetNextWindowSize( ImVec2( 200.0f, height ) ); @@ -1719,6 +1741,10 @@ class RayCastWorld : public Sample case e_sorted: g_draw.DrawString( 5, m_textLine, "Cast mode: sorted - gather up to 3 shapes sorted by closeness" ); break; + + default: + assert( false ); + break; } m_textLine += m_textIncrement; @@ -1727,7 +1753,7 @@ class RayCastWorld : public Sample RayCastSortedCallback }; b2CastResultFcn* modeFcn = fcns[m_mode]; - RayCastContext context = { }; + CastContext context = { }; // Must initialize fractions for sorting context.fractions[0] = FLT_MAX; @@ -1827,7 +1853,7 @@ class RayCastWorld : public Sample static Sample* Create( Settings& settings ) { - return new RayCastWorld( settings ); + return new CastWorld( settings ); } int m_bodyIndex; @@ -1856,7 +1882,7 @@ class RayCastWorld : public Sample bool m_dragging; }; -static int sampleRayCastWorld = RegisterSample( "Collision", "Ray Cast World", RayCastWorld::Create ); +static int sampleRayCastWorld = RegisterSample( "Collision", "Ray Cast World", CastWorld::Create ); class OverlapWorld : public Sample { @@ -2066,7 +2092,7 @@ class OverlapWorld : public Sample } } - void UpdateUI() override + void UpdateGui() override { float height = 330.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -2272,7 +2298,7 @@ class Manifold : public Sample m_wedge = b2ComputeHull( points, 3 ); } - void UpdateUI() override + void UpdateGui() override { float height = 300.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -2963,7 +2989,7 @@ class SmoothManifold : public Sample free( m_segments ); } - void UpdateUI() override + void UpdateGui() override { float height = 290.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); diff --git a/samples/sample_continuous.cpp b/samples/sample_continuous.cpp index 3d1749e81..de4435fc0 100644 --- a/samples/sample_continuous.cpp +++ b/samples/sample_continuous.cpp @@ -119,7 +119,7 @@ class BounceHouse : public Sample } } - void UpdateUI() override + void UpdateGui() override { float height = 100.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -341,7 +341,7 @@ class ChainDrop : public Sample //m_shapeId = b2CreatePolygonShape( m_bodyId, &shapeDef, &box ); } - void UpdateUI() override + void UpdateGui() override { float height = 140.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -463,6 +463,66 @@ class ChainSlide : public Sample static int sampleChainSlide = RegisterSample( "Continuous", "Chain Slide", ChainSlide::Create ); +class SegmentSlide : public Sample +{ +public: + explicit SegmentSlide( Settings& settings ) + : Sample( settings ) + { + if ( settings.restart == false ) + { + g_camera.m_center = { 0.0f, 10.0f }; + g_camera.m_zoom = 15.0f; + } + +#ifndef NDEBUG + b2_toiHitCount = 0; +#endif + + { + b2BodyDef bodyDef = b2DefaultBodyDef(); + b2BodyId groundId = b2CreateBody( m_worldId, &bodyDef ); + + b2ShapeDef shapeDef = b2DefaultShapeDef(); + b2Segment segment = { { -40.0f, 0.0f }, { 40.0f, 0.0f } }; + b2CreateSegmentShape( groundId, &shapeDef, &segment ); + + segment = { { 40.0f, 0.0f }, { 40.0f, 10.0f } }; + b2CreateSegmentShape( groundId, &shapeDef, &segment ); + } + + { + b2BodyDef bodyDef = b2DefaultBodyDef(); + bodyDef.type = b2_dynamicBody; + bodyDef.linearVelocity = { 100.0f, 0.0f }; + bodyDef.position = { -20.0f, 0.7f }; + b2BodyId bodyId = b2CreateBody( m_worldId, &bodyDef ); + + b2ShapeDef shapeDef = b2DefaultShapeDef(); + //shapeDef.friction = 0.0f; + b2Circle circle = { { 0.0f, 0.0f }, 0.5f }; + b2CreateCircleShape( bodyId, &shapeDef, &circle ); + } + } + + void Step( Settings& settings ) override + { + Sample::Step( settings ); + +#ifndef NDEBUG + g_draw.DrawString( 5, m_textLine, "toi hits = %d", b2_toiHitCount ); + m_textLine += m_textIncrement; +#endif + } + + static Sample* Create( Settings& settings ) + { + return new SegmentSlide( settings ); + } +}; + +static int sampleSegmentSlide = RegisterSample( "Continuous", "Segment Slide", SegmentSlide::Create ); + class SkinnyBox : public Sample { public: @@ -547,7 +607,7 @@ class SkinnyBox : public Sample } } - void UpdateUI() override + void UpdateGui() override { float height = 110.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -816,7 +876,7 @@ class GhostBumps : public Sample } } - void UpdateUI() override + void UpdateGui() override { float height = 140.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -937,6 +997,50 @@ class SpeculativeFallback : public Sample static int sampleSpeculativeFallback = RegisterSample( "Continuous", "Speculative Fallback", SpeculativeFallback::Create ); +class SpeculativeSliver : public Sample +{ +public: + explicit SpeculativeSliver( Settings& settings ) + : Sample( settings ) + { + if ( settings.restart == false ) + { + g_camera.m_center = { 0.0f, 1.75f }; + g_camera.m_zoom = 2.5f; + } + + { + b2BodyDef bodyDef = b2DefaultBodyDef(); + b2BodyId groundId = b2CreateBody( m_worldId, &bodyDef ); + + b2ShapeDef shapeDef = b2DefaultShapeDef(); + b2Segment segment = { { -10.0f, 0.0f }, { 10.0f, 0.0f } }; + b2CreateSegmentShape( groundId, &shapeDef, &segment ); + } + + { + b2BodyDef bodyDef = b2DefaultBodyDef(); + bodyDef.type = b2_dynamicBody; + bodyDef.position = { 0.0f, 12.0f }; + bodyDef.linearVelocity = { 0.0f, -100.0f }; + b2BodyId bodyId = b2CreateBody( m_worldId, &bodyDef ); + + b2ShapeDef shapeDef = b2DefaultShapeDef(); + b2Vec2 points[3] = { { -2.0f, 0.0f }, { -1.0f, 0.0f }, { 2.0f, 0.5f }}; + b2Hull hull = b2ComputeHull( points, 3 ); + b2Polygon poly = b2MakePolygon( &hull, 0.0f ); + b2CreatePolygonShape( bodyId, &shapeDef, &poly ); + } + } + + static Sample* Create( Settings& settings ) + { + return new SpeculativeSliver( settings ); + } +}; + +static int sampleSpeculativeSliver = RegisterSample( "Continuous", "Speculative Sliver", SpeculativeSliver::Create ); + // This shows that while Box2D uses speculative collision, it does not lead to speculative ghost collisions at small distances class SpeculativeGhost : public Sample { diff --git a/samples/sample_determinism.cpp b/samples/sample_determinism.cpp index 03b34ccd9..30f7baae8 100644 --- a/samples/sample_determinism.cpp +++ b/samples/sample_determinism.cpp @@ -91,13 +91,13 @@ class FallingHinges : public Sample bodyDef.position.x = x + offset * i; bodyDef.position.y = h + 2.0f * h * i; - + // this tests the deterministic cosine and sine functions bodyDef.rotation = b2MakeRot( 0.1f * i - 1.0f ); b2BodyId bodyId = b2CreateBody( m_worldId, &bodyDef ); - if ((i & 1) == 0) + if ( ( i & 1 ) == 0 ) { prevBodyId = bodyId; } @@ -121,7 +121,7 @@ class FallingHinges : public Sample m_hash = 0; m_sleepStep = -1; - //PrintTransforms(); + // PrintTransforms(); } void PrintTransforms() @@ -138,11 +138,11 @@ class FallingHinges : public Sample printf( "hash = 0x%08x\n", hash ); } - void Step(Settings& settings) override + void Step( Settings& settings ) override { Sample::Step( settings ); - if (m_hash == 0) + if ( m_hash == 0 ) { b2BodyEvents bodyEvents = b2World_GetBodyEvents( m_worldId ); @@ -153,10 +153,10 @@ class FallingHinges : public Sample for ( int i = 0; i < bodyCount; ++i ) { b2Transform xf = b2Body_GetTransform( m_bodies[i] ); - //printf( "%d %.9f %.9f %.9f %.9f\n", i, xf.p.x, xf.p.y, xf.q.c, xf.q.s ); + // printf( "%d %.9f %.9f %.9f %.9f\n", i, xf.p.x, xf.p.y, xf.q.c, xf.q.s ); hash = b2Hash( hash, reinterpret_cast( &xf ), sizeof( b2Transform ) ); } - + m_sleepStep = m_stepCount - 1; m_hash = hash; printf( "sleep step = %d, hash = 0x%08x\n", m_sleepStep, m_hash ); @@ -178,3 +178,466 @@ class FallingHinges : public Sample }; static int sampleFallingHinges = RegisterSample( "Determinism", "Falling Hinges", FallingHinges::Create ); + +#if 0 + +#include + +#define WALL_THICKNESS 4.0f +#define STANDARD_WALL_RESTITUTION 0.01f +#define WALL_DENSITY 4.0f + +#define DRONE_RADIUS 1.0f +#define DRONE_DENSITY 1.25f +#define DRONE_LINEAR_DAMPING 1.0f + +enum entityType +{ + STANDARD_WALL_ENTITY, + PROJECTILE_ENTITY, + DRONE_ENTITY, +}; + +enum shapeCategory +{ + WALL_SHAPE = 1, + PROJECTILE_SHAPE = 4, + DRONE_SHAPE = 16, +}; + +typedef struct entity +{ + enum entityType type; + void* entityPtr; +} entity; + +typedef struct wallEntity +{ + b2BodyId bodyID; + b2ShapeId shapeID; + b2Vec2 pos; + b2Rot rot; + b2Vec2 velocity; + b2Vec2 extent; + int16_t mapCellIdx; + bool isFloating; + enum entityType type; + bool isSuddenDeath; +} wallEntity; + +typedef struct weaponInformation +{ + const float fireMagnitude; + const float recoilMagnitude; + const float charge; + const float coolDown; + const float maxDistance; + const float radius; + const float density; + const float invMass; + const uint8_t maxBounces; +} weaponInformation; + +typedef struct droneEntity +{ + b2BodyId bodyID; + b2ShapeId shapeID; + weaponInformation* weaponInfo; + int8_t ammo; + float weaponCooldown; + + uint8_t idx; + b2Vec2 initalPos; + b2Vec2 pos; + b2Vec2 lastPos; + b2Vec2 lastMove; + b2Vec2 lastAim; + b2Vec2 velocity; + b2Vec2 lastVelocity; + bool dead; +} droneEntity; + +typedef struct projectileEntity +{ + uint8_t droneIdx; + + b2BodyId bodyID; + b2ShapeId shapeID; + weaponInformation* weaponInfo; + b2Vec2 pos; + b2Vec2 lastPos; + b2Vec2 velocity; + float lastSpeed; + float distance; + uint8_t bounces; +} projectileEntity; + +#ifndef PI +#define PI 3.14159265358979323846f +#endif + +#define INV_MASS( density, radius ) ( 1.0f / ( density * PI * radius * radius ) ) + +#define MACHINEGUN_RADIUS 0.15f +#define MACHINEGUN_DENSITY 3.0f + +weaponInformation machineGun = { + 25.0f, + 12.8f, + 0.0f, + 0.07f, + 225.0f, + MACHINEGUN_RADIUS, + MACHINEGUN_DENSITY, + INV_MASS( MACHINEGUN_DENSITY, MACHINEGUN_RADIUS ), + 2, +}; + +// clang-format off + +const char boringLayout[] = { + 'W','W','W','W','W','W','W','W','W','W','W','W','W','W','W','W','W','W','W','W','W', + 'W','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','W', + 'W','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','W', + 'W','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','W', + 'W','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','W', + 'W','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','W', + 'W','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','W', + 'W','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','W', + 'W','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','W', + 'W','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','W', + 'W','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','W', + 'W','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','W', + 'W','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','W', + 'W','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','W', + 'W','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','W', + 'W','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','W', + 'W','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','W', + 'W','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','W', + 'W','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','W', + 'W','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','O','W', + 'W','W','W','W','W','W','W','W','W','W','W','W','W','W','W','W','W','W','W','W','W', +}; + +// clang-format on + +static inline bool b2VecEqual( const b2Vec2 v1, const b2Vec2 v2 ) +{ + return v1.x == v2.x && v1.y == v2.y; +} + +entity* createWall( b2WorldId worldID, const float posX, const float posY, uint16_t cellIdx ) +{ + const b2Vec2 pos = { posX, posY }; + b2BodyDef wallBodyDef = b2DefaultBodyDef(); + wallBodyDef.position = pos; + b2BodyId wallBodyID = b2CreateBody( worldID, &wallBodyDef ); + b2Vec2 extent = { WALL_THICKNESS / 2.0f, WALL_THICKNESS / 2.0f }; + b2ShapeDef wallShapeDef = b2DefaultShapeDef(); + wallShapeDef.density = WALL_DENSITY; + wallShapeDef.restitution = STANDARD_WALL_RESTITUTION; + wallShapeDef.filter.categoryBits = WALL_SHAPE; + wallShapeDef.filter.maskBits = PROJECTILE_SHAPE | DRONE_SHAPE; + + wallEntity* wall = (wallEntity*)calloc( 1, sizeof( wallEntity ) ); + wall->bodyID = wallBodyID; + wall->pos = pos; + wall->rot = b2Rot_identity; + wall->velocity = b2Vec2_zero; + wall->extent = extent; + wall->mapCellIdx = cellIdx; + + entity* ent = (entity*)calloc( 1, sizeof( entity ) ); + ent->type = STANDARD_WALL_ENTITY; + ent->entityPtr = wall; + + wallShapeDef.userData = ent; + const b2Polygon wallPolygon = b2MakeBox( extent.x, extent.y ); + wall->shapeID = b2CreatePolygonShape( wallBodyID, &wallShapeDef, &wallPolygon ); + b2Body_SetUserData( wall->bodyID, ent ); + + return ent; +} + +void setupMap( b2WorldId worldID ) +{ + const uint8_t columns = 21; + const uint8_t rows = 21; + const char* layout = boringLayout; + + uint16_t cellIdx = 0; + for ( int row = 0; row < rows; row++ ) + { + for ( int col = 0; col < columns; col++ ) + { + char cellType = layout[col + ( row * columns )]; + if ( cellType == 'O' ) + { + continue; + } + + const float x = ( col - ( ( columns - 1 ) / 2.0f ) ) * WALL_THICKNESS; + const float y = ( row - ( rows - 1 ) / 2.0f ) * WALL_THICKNESS; + + createWall( worldID, x, y, cellIdx ); + cellIdx++; + } + } +} + +droneEntity* createDrone( b2WorldId worldID ) +{ + b2BodyDef droneBodyDef = b2DefaultBodyDef(); + droneBodyDef.type = b2_dynamicBody; + droneBodyDef.position = b2Vec2_zero; + droneBodyDef.fixedRotation = true; + droneBodyDef.linearDamping = DRONE_LINEAR_DAMPING; + b2BodyId droneBodyID = b2CreateBody( worldID, &droneBodyDef ); + b2ShapeDef droneShapeDef = b2DefaultShapeDef(); + droneShapeDef.density = DRONE_DENSITY; + droneShapeDef.friction = 0.0f; + droneShapeDef.restitution = 0.3f; + droneShapeDef.filter.categoryBits = DRONE_SHAPE; + droneShapeDef.filter.maskBits = WALL_SHAPE | PROJECTILE_SHAPE | DRONE_SHAPE; + droneShapeDef.enableContactEvents = true; + // droneShapeDef.enableSensorEvents = true; + const b2Circle droneCircle = { b2Vec2_zero, DRONE_RADIUS }; + + droneEntity* drone = (droneEntity*)calloc( 1, sizeof( droneEntity ) ); + drone->bodyID = droneBodyID; + drone->weaponInfo = &machineGun; + drone->ammo = -1; + drone->weaponCooldown = 0.0f; + drone->initalPos = droneBodyDef.position; + drone->pos = droneBodyDef.position; + drone->lastPos = b2Vec2_zero; + drone->lastMove = b2Vec2_zero; + drone->lastAim = { 0.0f, -1.0f }; + drone->velocity = b2Vec2_zero; + drone->lastVelocity = b2Vec2_zero; + drone->dead = false; + + entity* ent = (entity*)calloc( 1, sizeof( entity ) ); + ent->type = DRONE_ENTITY; + ent->entityPtr = drone; + + droneShapeDef.userData = ent; + drone->shapeID = b2CreateCircleShape( droneBodyID, &droneShapeDef, &droneCircle ); + b2Body_SetUserData( drone->bodyID, ent ); + + return drone; +} + +void createProjectile( b2WorldId worldID, droneEntity* drone, const b2Vec2 normAim ) +{ + b2BodyDef projectileBodyDef = b2DefaultBodyDef(); + projectileBodyDef.type = b2_dynamicBody; + projectileBodyDef.fixedRotation = true; + projectileBodyDef.isBullet = true; + projectileBodyDef.enableSleep = false; + float radius = drone->weaponInfo->radius; + + projectileBodyDef.position = b2MulAdd( drone->pos, 1.0f + ( radius * 1.5f ), normAim ); + printf( "projectile: %g %g\n", projectileBodyDef.position.x, projectileBodyDef.position.y ); + + b2BodyId projectileBodyID = b2CreateBody( worldID, &projectileBodyDef ); + b2ShapeDef projectileShapeDef = b2DefaultShapeDef(); + projectileShapeDef.enableContactEvents = true; + projectileShapeDef.density = drone->weaponInfo->density; + projectileShapeDef.friction = 0.0f; + projectileShapeDef.restitution = 1.0f; + projectileShapeDef.filter.categoryBits = PROJECTILE_SHAPE; + projectileShapeDef.filter.maskBits = WALL_SHAPE | PROJECTILE_SHAPE | DRONE_SHAPE; + const b2Circle projectileCircle = { b2Vec2_zero, radius }; + + b2ShapeId projectileShapeID = b2CreateCircleShape( projectileBodyID, &projectileShapeDef, &projectileCircle ); + + b2Vec2 fire = b2MulSV( machineGun.fireMagnitude, normAim ); + b2Body_ApplyLinearImpulseToCenter( projectileBodyID, fire, true ); + + projectileEntity* projectile = (projectileEntity*)calloc( 1, sizeof( projectileEntity ) ); + projectile->droneIdx = drone->idx; + projectile->bodyID = projectileBodyID; + projectile->shapeID = projectileShapeID; + projectile->weaponInfo = drone->weaponInfo; + projectile->pos = projectileBodyDef.position; + projectile->lastPos = projectileBodyDef.position; + projectile->velocity = b2Body_GetLinearVelocity( projectileBodyID ); + projectile->lastSpeed = b2Length( projectile->velocity ); + projectile->distance = 0.0f; + projectile->bounces = 0; + + entity* ent = (entity*)calloc( 1, sizeof( entity ) ); + ent->type = PROJECTILE_ENTITY; + ent->entityPtr = projectile; + + b2Body_SetUserData( projectile->bodyID, ent ); + b2Shape_SetUserData( projectile->shapeID, ent ); +} + +static void droneShoot( b2WorldId worldID, droneEntity* drone, const b2Vec2 aim ) +{ + if ( drone->weaponCooldown != 0.0f ) + { + return; + } + + drone->weaponCooldown = drone->weaponInfo->coolDown; + + b2Vec2 normAim = drone->lastAim; + if ( !b2VecEqual( aim, b2Vec2_zero ) ) + { + normAim = b2Normalize( aim ); + } + b2Vec2 recoil = b2MulSV( -drone->weaponInfo->recoilMagnitude, normAim ); + b2Body_ApplyLinearImpulseToCenter( drone->bodyID, recoil, true ); + + createProjectile( worldID, drone, normAim ); +} + +class BulletBug : public Sample +{ +public: + explicit BulletBug( Settings& settings ) + : Sample( settings ) + { + if ( settings.restart == false ) + { + g_camera.m_center = { 0.0f, 0.0f }; + g_camera.m_zoom = 80.0f; + } + + b2World_SetGravity( m_worldId, b2Vec2_zero ); + + setupMap( m_worldId ); + m_drone = createDrone( m_worldId ); + } + + void Step( Settings& settings ) override + { + float aimX = tanhf( rand() / (float)RAND_MAX ); + float aimY = tanhf( rand() / (float)RAND_MAX ); + + droneShoot( m_worldId, m_drone, { aimX, aimY } ); + + g_draw.DrawPoint( { -40.0f, -40.0f }, 5.0f, b2_colorViolet ); + g_draw.DrawPoint( { 40.0f, -40.0f }, 5.0f, b2_colorViolet ); + g_draw.DrawPoint( { -40.0f, 40.0f }, 5.0f, b2_colorViolet ); + g_draw.DrawPoint( { 40.0f, 40.0f }, 5.0f, b2_colorViolet ); + + if (m_wasOut) + { + g_draw.DrawPoint( m_pos, 5.0f, b2_colorLightBlue ); + } + + Sample::Step( settings ); + + //b2World_Step( m_worldId, 1.0f / 10.0f, 1 ); + + b2BodyEvents events = b2World_GetBodyEvents( m_worldId ); + for ( int i = 0; i < events.moveCount; i++ ) + { + const b2BodyMoveEvent* event = events.moveEvents + i; + assert( b2Body_IsValid( event->bodyId ) ); + entity* ent = (entity*)event->userData; + if ( ent == NULL ) + { + continue; + } + + const b2Vec2 pos = event->transform.p; + m_pos = pos; + + // check if the body is beyond the outside bounds of map + if ( pos.x < -48.0f || pos.x > 48.0f || pos.y < -48.0f || pos.y > 48.0f ) + { + printf( "body is outside of wall bounds: (%f, %f)\n", pos.x, pos.y ); + m_wasOut = true; + m_pos = pos; + settings.pause = true; + //exit( 1 ); + } + } + + g_draw.DrawPoint( m_pos, 10.0f, b2_colorWhite ); + } + + static Sample* Create( Settings& settings ) + { + return new BulletBug( settings ); + } + + b2Vec2 m_pos = {0.0f, 0.0f}; + bool m_wasOut = false; + droneEntity* m_drone; +}; + +static int sampleBulletBug = RegisterSample( "Bugs", "Bullet Bug", BulletBug::Create ); + +class OverlapBug : public Sample +{ +public: + explicit OverlapBug( Settings& settings ) + : Sample( settings ) + { + if ( settings.restart == false ) + { + g_camera.m_center = { 0.0f, 2.5f }; + g_camera.m_zoom = 3.5f; + } + + float boxSize = 0.5f; + b2BodyDef body_def = b2DefaultBodyDef(); + body_def.type = b2_staticBody; + body_def.position = { m_x, m_y }; + b2BodyId body_id = b2CreateBody( m_worldId, &body_def ); + b2Polygon polygon = b2MakeSquare( boxSize ); + b2ShapeDef shape_def = b2DefaultShapeDef(); + b2CreatePolygonShape( body_id, &shape_def, &polygon ); + } + + static bool Callback( b2ShapeId id, void* context ) + { + OverlapBug* self = static_cast( context ); + self->m_overlap = true; + return false; + } + + void Step( Settings& settings ) override + { + Sample::Step( settings ); + + float testSize = 0.4f; + b2Polygon test_polygon = b2MakeSquare( testSize ); + b2Transform tfm = { { m_x, m_y }, { 1.0f, 0.0f } }; + b2World_OverlapPolygon( m_worldId, &test_polygon, tfm, b2DefaultQueryFilter(), OverlapBug::Callback, this ); + + b2Vec2 vertices[4]; + vertices[0] = b2TransformPoint(tfm, test_polygon.vertices[0]); + vertices[1] = b2TransformPoint(tfm, test_polygon.vertices[1]); + vertices[2] = b2TransformPoint(tfm, test_polygon.vertices[2]); + vertices[3] = b2TransformPoint(tfm, test_polygon.vertices[3]); + g_draw.DrawPolygon(vertices, 4, b2_colorOrange); + + if ( m_overlap ) + { + DrawTextLine( "overlap" ); + } + else + { + DrawTextLine( "no overlap" ); + } + } + + static Sample* Create( Settings& settings ) + { + return new OverlapBug( settings ); + } + + float m_x = 3.0f; + float m_y = 5.0f; + bool m_overlap = false; +}; + +static int sampleSingleBox = RegisterSample( "Bugs", "Overlap", OverlapBug::Create ); +#endif diff --git a/samples/sample_events.cpp b/samples/sample_events.cpp index 789cdccde..30f0bcd86 100644 --- a/samples/sample_events.cpp +++ b/samples/sample_events.cpp @@ -139,6 +139,8 @@ class SensorFunnel : public Sample b2Polygon box = b2MakeOffsetBox( 4.0f, 1.0f, { 0.0f, -30.5f }, b2Rot_identity ); b2ShapeDef shapeDef = b2DefaultShapeDef(); shapeDef.isSensor = true; + shapeDef.enableSensorEvents = true; + b2CreatePolygonShape( groundId, &shapeDef, &box ); } } @@ -179,8 +181,7 @@ class SensorFunnel : public Sample if ( m_type == e_donut ) { Donut* donut = m_donuts + index; - // donut->Spawn(m_worldId, center, index + 1, donut); - donut->Spawn( m_worldId, center, 1.0f, 0, donut ); + donut->Create( m_worldId, center, 1.0f, 0, true, donut ); } else { @@ -191,6 +192,7 @@ class SensorFunnel : public Sample float jointDamping = 0.5f; bool colorize = true; CreateHuman( human, m_worldId, center, scale, jointFriction, jointHertz, jointDamping, index + 1, human, colorize ); + Human_EnableSensorEvents( human, true ); } m_isSpawned[index] = true; @@ -202,7 +204,7 @@ class SensorFunnel : public Sample if ( m_type == e_donut ) { Donut* donut = m_donuts + index; - donut->Despawn(); + donut->Destroy(); } else { @@ -221,7 +223,7 @@ class SensorFunnel : public Sample { if ( m_type == e_donut ) { - m_donuts[i].Despawn(); + m_donuts[i].Destroy(); } else { @@ -233,7 +235,7 @@ class SensorFunnel : public Sample } } - void UpdateUI() override + void UpdateGui() override { float height = 90.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -266,7 +268,7 @@ class SensorFunnel : public Sample Sample::Step( settings ); // Discover rings that touch the bottom sensor - bool deferredDestructions[e_count] = {}; + bool deferredDestruction[e_count] = {}; b2SensorEvents sensorEvents = b2World_GetSensorEvents( m_worldId ); for ( int i = 0; i < sensorEvents.beginCount; ++i ) { @@ -283,7 +285,7 @@ class SensorFunnel : public Sample assert( 0 <= index && index < e_count ); // Defer destruction to avoid double destruction and event invalidation (orphaned shape ids) - deferredDestructions[index] = true; + deferredDestruction[index] = true; } } else @@ -295,7 +297,7 @@ class SensorFunnel : public Sample assert( 0 <= index && index < e_count ); // Defer destruction to avoid double destruction and event invalidation (orphaned shape ids) - deferredDestructions[index] = true; + deferredDestruction[index] = true; } } } @@ -305,7 +307,7 @@ class SensorFunnel : public Sample // Safely destroy rings that hit the bottom sensor for ( int i = 0; i < e_count; ++i ) { - if ( deferredDestructions[i] ) + if ( deferredDestruction[i] ) { DestroyElement( i ); } @@ -363,25 +365,50 @@ class SensorBookend : public Sample groundSegment = { { 10.0f, 0.0f }, { 10.0f, 10.0f } }; b2CreateSegmentShape( groundId, &shapeDef, &groundSegment ); - m_isVisiting = false; + m_isVisiting1 = false; + m_isVisiting2 = false; + m_sensorsOverlapCount = 0; } - CreateSensor(); - + CreateSensor1(); + CreateSensor2(); CreateVisitor(); } - void CreateSensor() + void CreateSensor1() { b2BodyDef bodyDef = b2DefaultBodyDef(); - bodyDef.position = { 0.0f, 1.0f }; - m_sensorBodyId = b2CreateBody( m_worldId, &bodyDef ); + bodyDef.position = { -2.0f, 1.0f }; + m_sensorBodyId1 = b2CreateBody( m_worldId, &bodyDef ); b2ShapeDef shapeDef = b2DefaultShapeDef(); shapeDef.isSensor = true; + shapeDef.enableSensorEvents = true; + b2Polygon box = b2MakeSquare( 1.0f ); - m_sensorShapeId = b2CreatePolygonShape( m_sensorBodyId, &shapeDef, &box ); + m_sensorShapeId1 = b2CreatePolygonShape( m_sensorBodyId1, &shapeDef, &box ); + } + + void CreateSensor2() + { + b2BodyDef bodyDef = b2DefaultBodyDef(); + bodyDef.type = b2_dynamicBody; + bodyDef.position = { 2.0f, 1.0f }; + m_sensorBodyId2 = b2CreateBody( m_worldId, &bodyDef ); + + b2ShapeDef shapeDef = b2DefaultShapeDef(); + shapeDef.isSensor = true; + shapeDef.enableSensorEvents = true; + + b2Polygon box = b2MakeRoundedBox( 0.5f, 0.5f, 0.5f ); + m_sensorShapeId2 = b2CreatePolygonShape( m_sensorBodyId2, &shapeDef, &box ); + + // Solid middle + shapeDef.isSensor = false; + shapeDef.enableSensorEvents = false; + box = b2MakeSquare( 0.5f ); + b2CreatePolygonShape( m_sensorBodyId2, &shapeDef, &box ); } void CreateVisitor() @@ -393,16 +420,17 @@ class SensorBookend : public Sample m_visitorBodyId = b2CreateBody( m_worldId, &bodyDef ); b2ShapeDef shapeDef = b2DefaultShapeDef(); + shapeDef.enableSensorEvents = true; b2Circle circle = { { 0.0f, 0.0f }, 0.5f }; m_visitorShapeId = b2CreateCircleShape( m_visitorBodyId, &shapeDef, &circle ); } - void UpdateUI() override + void UpdateGui() override { - float height = 90.0f; + float height = 260.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); - ImGui::SetNextWindowSize( ImVec2( 140.0f, height ) ); + ImGui::SetNextWindowSize( ImVec2( 200.0f, height ) ); ImGui::Begin( "Sensor Bookend", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize ); @@ -419,24 +447,108 @@ class SensorBookend : public Sample { b2DestroyBody( m_visitorBodyId ); m_visitorBodyId = b2_nullBodyId; - m_visitorShapeId = b2_nullShapeId; + // Retain m_visitorShapeId for end events. + } + else + { + bool enabledEvents = b2Shape_AreSensorEventsEnabled( m_visitorShapeId ); + if ( ImGui::Checkbox( "visitor events", &enabledEvents ) ) + { + b2Shape_EnableSensorEvents( m_visitorShapeId, enabledEvents ); + } + + bool enabledBody = b2Body_IsEnabled( m_visitorBodyId ); + if ( ImGui::Checkbox( "enable visitor body", &enabledBody ) ) + { + if (enabledBody) + { + b2Body_Enable( m_visitorBodyId ); + } + else + { + b2Body_Disable( m_visitorBodyId ); + } + } } } - if ( B2_IS_NULL( m_sensorBodyId ) ) + ImGui::Separator(); + + if ( B2_IS_NULL( m_sensorBodyId1 ) ) { - if ( ImGui::Button( "create sensor" ) ) + if ( ImGui::Button( "create sensor1" ) ) { - CreateSensor(); + CreateSensor1(); } } else { - if ( ImGui::Button( "destroy sensor" ) ) + if ( ImGui::Button( "destroy sensor1" ) ) + { + b2DestroyBody( m_sensorBodyId1 ); + m_sensorBodyId1 = b2_nullBodyId; + // Retain m_sensorShapeId1 for end events. + } + else { - b2DestroyBody( m_sensorBodyId ); - m_sensorBodyId = b2_nullBodyId; - m_sensorShapeId = b2_nullShapeId; + bool enabledEvents = b2Shape_AreSensorEventsEnabled( m_sensorShapeId1 ); + if ( ImGui::Checkbox( "sensor 1 events", &enabledEvents ) ) + { + b2Shape_EnableSensorEvents( m_sensorShapeId1, enabledEvents ); + } + + bool enabledBody = b2Body_IsEnabled( m_sensorBodyId1 ); + if ( ImGui::Checkbox( "enable sensor1 body", &enabledBody ) ) + { + if ( enabledBody ) + { + b2Body_Enable( m_sensorBodyId1 ); + } + else + { + b2Body_Disable( m_sensorBodyId1 ); + } + } + } + } + + ImGui::Separator(); + + if ( B2_IS_NULL( m_sensorBodyId2 ) ) + { + if ( ImGui::Button( "create sensor2" ) ) + { + CreateSensor2(); + } + } + else + { + if ( ImGui::Button( "destroy sensor2" ) ) + { + b2DestroyBody( m_sensorBodyId2 ); + m_sensorBodyId2 = b2_nullBodyId; + // Retain m_sensorShapeId2 for end events. + } + else + { + bool enabledEvents = b2Shape_AreSensorEventsEnabled( m_sensorShapeId2 ); + if ( ImGui::Checkbox( "sensor2 events", &enabledEvents ) ) + { + b2Shape_EnableSensorEvents( m_sensorShapeId2, enabledEvents ); + } + + bool enabledBody = b2Body_IsEnabled( m_sensorBodyId2 ); + if ( ImGui::Checkbox( "enable sensor2 body", &enabledBody ) ) + { + if ( enabledBody ) + { + b2Body_Enable( m_sensorBodyId2 ); + } + else + { + b2Body_Disable( m_sensorBodyId2 ); + } + } } } @@ -452,27 +564,93 @@ class SensorBookend : public Sample { b2SensorBeginTouchEvent event = sensorEvents.beginEvents[i]; - if ( B2_ID_EQUALS( event.visitorShapeId, m_visitorShapeId ) ) + if ( B2_ID_EQUALS( event.sensorShapeId, m_sensorShapeId1 ) ) { - assert( m_isVisiting == false ); - m_isVisiting = true; + if ( B2_ID_EQUALS( event.visitorShapeId, m_visitorShapeId ) ) + { + assert( m_isVisiting1 == false ); + m_isVisiting1 = true; + } + else + { + assert( B2_ID_EQUALS( event.visitorShapeId, m_sensorShapeId2 ) ); + m_sensorsOverlapCount += 1; + } + } + else + { + assert( B2_ID_EQUALS( event.sensorShapeId, m_sensorShapeId2 ) ); + + if ( B2_ID_EQUALS( event.visitorShapeId, m_visitorShapeId ) ) + { + assert( m_isVisiting2 == false ); + m_isVisiting2 = true; + } + else + { + assert( B2_ID_EQUALS( event.visitorShapeId, m_sensorShapeId1 ) ); + m_sensorsOverlapCount += 1; + } } } + assert( m_sensorsOverlapCount == 0 || m_sensorsOverlapCount == 2 ); + for ( int i = 0; i < sensorEvents.endCount; ++i ) { b2SensorEndTouchEvent event = sensorEvents.endEvents[i]; - bool wasVisitorDestroyed = b2Shape_IsValid( event.visitorShapeId ) == false; - if ( B2_ID_EQUALS( event.visitorShapeId, m_visitorShapeId ) || wasVisitorDestroyed ) + if ( B2_ID_EQUALS( event.sensorShapeId, m_sensorShapeId1 ) ) { - assert( m_isVisiting == true ); - m_isVisiting = false; + if ( B2_ID_EQUALS( event.visitorShapeId, m_visitorShapeId )) + { + assert( m_isVisiting1 == true ); + m_isVisiting1 = false; + } + else + { + assert( B2_ID_EQUALS( event.visitorShapeId, m_sensorShapeId2 ) ); + m_sensorsOverlapCount -= 1; + } + } + else + { + assert( B2_ID_EQUALS( event.sensorShapeId, m_sensorShapeId2 ) ); + + if ( B2_ID_EQUALS( event.visitorShapeId, m_visitorShapeId ) ) + { + assert( m_isVisiting2 == true ); + m_isVisiting2 = false; + } + else + { + assert( B2_ID_EQUALS( event.visitorShapeId, m_sensorShapeId1 ) ); + m_sensorsOverlapCount -= 1; + } } } - g_draw.DrawString( 5, m_textLine, "visiting == %s", m_isVisiting ? "true" : "false" ); - m_textLine += m_textIncrement; + assert( m_sensorsOverlapCount == 0 || m_sensorsOverlapCount == 2 ); + + // Nullify invalid shape ids after end events are processed. + if (b2Shape_IsValid(m_visitorShapeId) == false) + { + m_visitorShapeId = b2_nullShapeId; + } + + if (b2Shape_IsValid(m_sensorShapeId1) == false) + { + m_sensorShapeId1 = b2_nullShapeId; + } + + if (b2Shape_IsValid(m_sensorShapeId2) == false) + { + m_sensorShapeId2 = b2_nullShapeId; + } + + DrawTextLine( "visiting 1 == %s", m_isVisiting1 ? "true" : "false" ); + DrawTextLine( "visiting 2 == %s", m_isVisiting2 ? "true" : "false" ); + DrawTextLine( "sensors overlap count == %d", m_sensorsOverlapCount ); } static Sample* Create( Settings& settings ) @@ -480,12 +658,18 @@ class SensorBookend : public Sample return new SensorBookend( settings ); } - b2BodyId m_sensorBodyId; - b2ShapeId m_sensorShapeId; + b2BodyId m_sensorBodyId1; + b2ShapeId m_sensorShapeId1; + + b2BodyId m_sensorBodyId2; + b2ShapeId m_sensorShapeId2; b2BodyId m_visitorBodyId; b2ShapeId m_visitorShapeId; - bool m_isVisiting; + + bool m_isVisiting1; + bool m_isVisiting2; + int m_sensorsOverlapCount; }; static int sampleSensorBookendEvent = RegisterSample( "Events", "Sensor Bookend", SensorBookend::Create ); @@ -529,6 +713,7 @@ class FootSensor : public Sample chainDef.filter.categoryBits = GROUND; chainDef.filter.maskBits = FOOT | PLAYER; chainDef.isLoop = false; + chainDef.enableSensorEvents = true; b2CreateChain( groundId, &chainDef ); } @@ -550,6 +735,7 @@ class FootSensor : public Sample shapeDef.filter.categoryBits = FOOT; shapeDef.filter.maskBits = GROUND; shapeDef.isSensor = true; + shapeDef.enableSensorEvents = true; m_sensorId = b2CreatePolygonShape( m_playerId, &shapeDef, &box ); } @@ -740,7 +926,7 @@ class ContactEvent : public Sample } } - void UpdateUI() override + void UpdateGui() override { float height = 60.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -830,7 +1016,7 @@ class ContactEvent : public Sample for ( int k = 0; k < manifold.pointCount; ++k ) { b2ManifoldPoint point = manifold.points[k]; - g_draw.DrawSegment( point.point, point.point + point.maxNormalImpulse * normal, b2_colorBlueViolet ); + g_draw.DrawSegment( point.point, point.point + point.totalNormalImpulse * normal, b2_colorBlueViolet ); g_draw.DrawPoint( point.point, 10.0f, b2_colorWhite ); } } @@ -860,7 +1046,7 @@ class ContactEvent : public Sample for ( int k = 0; k < manifold.pointCount; ++k ) { b2ManifoldPoint point = manifold.points[k]; - g_draw.DrawSegment( point.point, point.point + point.maxNormalImpulse * normal, b2_colorYellowGreen ); + g_draw.DrawSegment( point.point, point.point + point.totalNormalImpulse * normal, b2_colorYellowGreen ); g_draw.DrawPoint( point.point, 10.0f, b2_colorWhite ); } } @@ -1175,7 +1361,7 @@ class Platformer : public Sample return false; } - void UpdateUI() override + void UpdateGui() override { float height = 100.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -1360,6 +1546,7 @@ class BodyMove : public Sample for ( int32_t i = 0; i < 10 && m_count < e_count; ++i ) { bodyDef.position = { x, y }; + bodyDef.isBullet = ( m_count % 12 == 0 ); bodyDef.userData = m_bodyIds + m_count; m_bodyIds[m_count] = b2CreateBody( m_worldId, &bodyDef ); m_sleeping[m_count] = false; @@ -1389,7 +1576,7 @@ class BodyMove : public Sample } } - void UpdateUI() override + void UpdateGui() override { float height = 100.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -1429,6 +1616,12 @@ class BodyMove : public Sample const b2BodyMoveEvent* event = events.moveEvents + i; g_draw.DrawTransform( event->transform ); + b2Transform transform = b2Body_GetTransform( event->bodyId ); + B2_ASSERT( transform.p.x == event->transform.p.x ); + B2_ASSERT( transform.p.y == event->transform.p.y ); + B2_ASSERT( transform.q.c == event->transform.q.c ); + B2_ASSERT( transform.q.s == event->transform.q.s ); + // this shows a somewhat contrived way to track body sleeping b2BodyId* bodyId = static_cast( event->userData ); ptrdiff_t diff = bodyId - m_bodyIds; @@ -1460,8 +1653,8 @@ class BodyMove : public Sample return new BodyMove( settings ); } - b2BodyId m_bodyIds[e_count]; - bool m_sleeping[e_count]; + b2BodyId m_bodyIds[e_count] = {}; + bool m_sleeping[e_count] = {}; int m_count; int m_sleepCount; b2Vec2 m_explosionPosition; @@ -1498,8 +1691,11 @@ class SensorTypes : public Sample b2BodyId groundId = b2CreateBody( m_worldId, &bodyDef ); b2ShapeDef shapeDef = b2DefaultShapeDef(); + + // Enable sensor events, but filter them out as a test shapeDef.filter.categoryBits = GROUND; - shapeDef.filter.maskBits = SENSOR | DEFAULT; + shapeDef.filter.maskBits = DEFAULT; + shapeDef.enableSensorEvents = true; b2Segment groundSegment = { { -6.0f, 0.0f }, { 6.0f, 0.0f } }; b2CreateSegmentShape( groundId, &shapeDef, &groundSegment ); @@ -1521,6 +1717,7 @@ class SensorTypes : public Sample b2ShapeDef shapeDef = b2DefaultShapeDef(); shapeDef.filter.categoryBits = SENSOR; shapeDef.isSensor = true; + shapeDef.enableSensorEvents = true; b2Polygon box = b2MakeSquare( 1.0f ); m_staticSensorId = b2CreatePolygonShape( bodyId, &shapeDef, &box ); } @@ -1536,6 +1733,7 @@ class SensorTypes : public Sample b2ShapeDef shapeDef = b2DefaultShapeDef(); shapeDef.filter.categoryBits = SENSOR; shapeDef.isSensor = true; + shapeDef.enableSensorEvents = true; b2Polygon box = b2MakeSquare( 1.0f ); m_kinematicSensorId = b2CreatePolygonShape( m_kinematicBodyId, &shapeDef, &box ); } @@ -1550,12 +1748,14 @@ class SensorTypes : public Sample b2ShapeDef shapeDef = b2DefaultShapeDef(); shapeDef.filter.categoryBits = SENSOR; shapeDef.isSensor = true; + shapeDef.enableSensorEvents = true; b2Polygon box = b2MakeSquare( 1.0f ); m_dynamicSensorId = b2CreatePolygonShape( bodyId, &shapeDef, &box ); // Add some real collision so the dynamic body is valid shapeDef.filter.categoryBits = DEFAULT; shapeDef.isSensor = false; + shapeDef.enableSensorEvents = false; box = b2MakeSquare( 0.8f ); b2CreatePolygonShape( bodyId, &shapeDef, &box ); } @@ -1571,6 +1771,7 @@ class SensorTypes : public Sample b2ShapeDef shapeDef = b2DefaultShapeDef(); shapeDef.filter.categoryBits = DEFAULT; shapeDef.filter.maskBits = GROUND | DEFAULT | SENSOR; + shapeDef.enableSensorEvents = true; b2Circle circle = { { 0.0f, 0.0f }, 0.5f }; b2CreateCircleShape( bodyId, &shapeDef, &circle ); @@ -1615,12 +1816,12 @@ class SensorTypes : public Sample void Step( Settings& settings ) override { b2Vec2 position = b2Body_GetPosition( m_kinematicBodyId ); - if (position.y < 0.0f) + if ( position.y < 0.0f ) { b2Body_SetLinearVelocity( m_kinematicBodyId, { 0.0f, 1.0f } ); - //b2Body_SetKinematicTarget( m_kinematicBodyId ); + // b2Body_SetKinematicTarget( m_kinematicBodyId ); } - else if (position.y > 3.0f) + else if ( position.y > 3.0f ) { b2Body_SetLinearVelocity( m_kinematicBodyId, { 0.0f, -1.0f } ); } @@ -1636,7 +1837,7 @@ class SensorTypes : public Sample b2RayResult result = b2World_CastRayClosest( m_worldId, origin, translation, b2DefaultQueryFilter() ); g_draw.DrawSegment( origin, origin + translation, b2_colorDimGray ); - if (result.hit) + if ( result.hit ) { g_draw.DrawPoint( result.point, 10.0f, b2_colorCyan ); } diff --git a/samples/sample_joints.cpp b/samples/sample_joints.cpp index 3dd06d951..097cd66b8 100644 --- a/samples/sample_joints.cpp +++ b/samples/sample_joints.cpp @@ -112,7 +112,7 @@ class DistanceJoint : public Sample } } - void UpdateUI() override + void UpdateGui() override { float height = 240.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -276,7 +276,7 @@ class MotorJoint : public Sample m_time = 0.0f; } - void UpdateUI() override + void UpdateGui() override { float height = 140.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -509,7 +509,7 @@ class RevoluteJoint : public Sample } } - void UpdateUI() override + void UpdateGui() override { float height = 220.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -663,7 +663,7 @@ class PrismaticJoint : public Sample } } - void UpdateUI() override + void UpdateGui() override { float height = 220.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -814,7 +814,7 @@ class WheelJoint : public Sample } } - void UpdateUI() override + void UpdateGui() override { float height = 220.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -995,7 +995,7 @@ class Bridge : public Sample } } - void UpdateUI() override + void UpdateGui() override { float height = 80.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -1116,7 +1116,7 @@ class BallAndChain : public Sample } } - void UpdateUI() override + void UpdateGui() override { float height = 60.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -1217,7 +1217,7 @@ class Cantilever : public Sample } } - void UpdateUI() override + void UpdateGui() override { float height = 180.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -1506,7 +1506,7 @@ class FixedRotation : public Sample ++index; } - void UpdateUI() override + void UpdateGui() override { float height = 60.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -1722,7 +1722,7 @@ class BreakableJoint : public Sample m_breakForce = 1000.0f; } - void UpdateUI() override + void UpdateGui() override { float height = 100.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -2079,7 +2079,7 @@ class Driving : public Sample m_car.Spawn( m_worldId, { 0.0f, 0.0f }, 1.0f, m_hertz, m_dampingRatio, m_torque, nullptr ); } - void UpdateUI() override + void UpdateGui() override { float height = 140.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -2201,7 +2201,7 @@ class Ragdoll : public Sample Human_ApplyRandomAngularImpulse( &m_human, 10.0f ); } - void UpdateUI() override + void UpdateGui() override { float height = 140.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -2267,7 +2267,7 @@ class SoftBody : public Sample b2CreateSegmentShape( groundId, &shapeDef, &segment ); } - m_donut.Spawn( m_worldId, { 0.0f, 10.0f }, 2.0f, 0, nullptr ); + m_donut.Create( m_worldId, { 0.0f, 10.0f }, 2.0f, 0, false, nullptr ); } static Sample* Create( Settings& settings ) @@ -2494,7 +2494,7 @@ class ScissorLift : public Sample car.Spawn( m_worldId, { 0.0f, y + 2.0f }, 1.0f, 3.0f, 0.7f, 0.0f, nullptr ); } - void UpdateUI() override + void UpdateGui() override { float height = 140.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); diff --git a/samples/sample_robustness.cpp b/samples/sample_robustness.cpp index 0ae02e6ba..2ee4c319d 100644 --- a/samples/sample_robustness.cpp +++ b/samples/sample_robustness.cpp @@ -274,7 +274,7 @@ class OverlapRecovery : public Sample assert( bodyIndex == m_bodyCount ); } - void UpdateUI() override + void UpdateGui() override { float height = 210.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); diff --git a/samples/sample_shapes.cpp b/samples/sample_shapes.cpp index 6d4ffc6b8..af0a188c5 100644 --- a/samples/sample_shapes.cpp +++ b/samples/sample_shapes.cpp @@ -172,7 +172,7 @@ class ChainShape : public Sample m_stepCount = 0; } - void UpdateUI() override + void UpdateGui() override { float height = 155.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -404,7 +404,7 @@ class CompoundShapes : public Sample } } - void UpdateUI() override + void UpdateGui() override { float height = 100.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -521,7 +521,7 @@ class ShapeFilter : public Sample } } - void UpdateUI() override + void UpdateGui() override { float height = 240.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -855,7 +855,7 @@ class Restitution : public Sample } } - void UpdateUI() override + void UpdateGui() override { float height = 100.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -1167,6 +1167,9 @@ class TangentSpeed : public Sample chainDef.materialCount = count; b2CreateChain( groundId, &chainDef ); + + m_friction = 0.6f; + m_rollingResistance = 0.3f; } } @@ -1180,21 +1183,54 @@ class TangentSpeed : public Sample b2BodyId bodyId = b2CreateBody( m_worldId, &bodyDef ); b2ShapeDef shapeDef = b2DefaultShapeDef(); - shapeDef.rollingResistance = 0.3f; + shapeDef.friction = m_friction; + shapeDef.rollingResistance = m_rollingResistance; b2CreateCircleShape( bodyId, &shapeDef, &circle ); return bodyId; } + void Reset() + { + int count = m_bodyIds.size(); + for (int i = 0; i < count; ++i) + { + b2DestroyBody( m_bodyIds[i] ); + } + + m_bodyIds.clear(); + } + + void UpdateGui() override + { + float height = 80.0f; + ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); + ImGui::SetNextWindowSize( ImVec2( 260.0f, height ) ); + + ImGui::Begin( "Ball Parameters", nullptr, ImGuiWindowFlags_NoResize ); + ImGui::PushItemWidth( 140.0f ); + + if ( ImGui::SliderFloat( "Friction", &m_friction, 0.0f, 2.0f, "%.2f" ) ) + { + Reset(); + } + + if ( ImGui::SliderFloat( "Rolling Resistance", &m_rollingResistance, 0.0f, 1.0f, "%.2f" ) ) + { + Reset(); + } + + ImGui::End(); + } + void Step( Settings& settings ) override { - if ( m_stepCount % 25 == 0 && m_count < m_totalCount && settings.pause == false) + if ( m_stepCount % 25 == 0 && m_bodyIds.size() < m_totalCount && settings.pause == false) { - DropBall(); - m_count += 1; + b2BodyId id = DropBall(); + m_bodyIds.push_back( id ); } Sample::Step( settings ); - } static Sample* Create( Settings& settings ) @@ -1203,7 +1239,9 @@ class TangentSpeed : public Sample } static constexpr int m_totalCount = 200; - int m_count = 0; + std::vector m_bodyIds; + float m_friction; + float m_rollingResistance; }; static int sampleTangentSpeed = RegisterSample( "Shapes", "Tangent Speed", TangentSpeed::Create ); @@ -1286,7 +1324,7 @@ class ModifyGeometry : public Sample b2Body_ApplyMassFromShapes( bodyId ); } - void UpdateUI() override + void UpdateGui() override { float height = 230.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -1630,7 +1668,7 @@ class Explosion : public Sample m_impulse = 10.0f; } - void UpdateUI() override + void UpdateGui() override { float height = 160.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); diff --git a/samples/sample_stacking.cpp b/samples/sample_stacking.cpp index 235372bb7..33b506be3 100644 --- a/samples/sample_stacking.cpp +++ b/samples/sample_stacking.cpp @@ -325,7 +325,7 @@ class VerticalStack : public Sample } } - void UpdateUI() override + void UpdateGui() override { float height = 230.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); @@ -426,29 +426,31 @@ class CircleStack : public Sample b2World_SetGravity( m_worldId, { 0.0f, -20.0f } ); b2World_SetContactTuning( m_worldId, 0.25f * 360.0f, 10.0f, 3.0f ); + b2BodyDef bodyDef = b2DefaultBodyDef(); + bodyDef.type = b2_dynamicBody; + b2Circle circle = {}; - circle.radius = 0.25f; + circle.radius = 0.5f; b2ShapeDef shapeDef = b2DefaultShapeDef(); shapeDef.enableHitEvents = true; - shapeDef.rollingResistance = 0.2f; - - b2BodyDef bodyDef = b2DefaultBodyDef(); - bodyDef.type = b2_dynamicBody; + //shapeDef.rollingResistance = 0.2f; + shapeDef.friction = 0.0f; - float y = 0.5f; + float y = 0.75f; - for ( int i = 0; i < 1; ++i ) + for ( int i = 0; i < 10; ++i ) { bodyDef.position.y = y; b2BodyId bodyId = b2CreateBody( m_worldId, &bodyDef ); shapeDef.userData = reinterpret_cast( intptr_t( shapeIndex ) ); + shapeDef.density = 1.0f + 4.0f * i; shapeIndex += 1; b2CreateCircleShape( bodyId, &shapeDef, &circle ); - y += 2.0f; + y += 1.25f; } } @@ -489,6 +491,67 @@ class CircleStack : public Sample static int sampleCircleStack = RegisterSample( "Stacking", "Circle Stack", CircleStack::Create ); +class CapsuleStack : public Sample +{ +public: + struct Event + { + int indexA, indexB; + }; + + explicit CapsuleStack( Settings& settings ) + : Sample( settings ) + { + if ( settings.restart == false ) + { + g_camera.m_center = { 0.0f, 5.0f }; + g_camera.m_zoom = 6.0f; + } + + { + b2BodyDef bodyDef = b2DefaultBodyDef(); + bodyDef.position = { 0.0f, -1.0f }; + b2BodyId groundId = b2CreateBody( m_worldId, &bodyDef ); + + b2ShapeDef shapeDef = b2DefaultShapeDef(); + b2Polygon polygon = b2MakeBox( 10.0f, 1.0f ); + b2CreatePolygonShape( groundId, &shapeDef, &polygon ); + } + + b2BodyDef bodyDef = b2DefaultBodyDef(); + bodyDef.type = b2_dynamicBody; + + float a = 0.25f; + b2Capsule capsule = { { -4.0f * a, 0.0f }, { 4.0f * a, 0.0f }, a }; + + b2ShapeDef shapeDef = b2DefaultShapeDef(); + + // rolling resistance increases stacking stability + //shapeDef.rollingResistance = 0.2f; + + float y = 2.0f * a; + + for ( int i = 0; i < 20; ++i ) + { + bodyDef.position.y = y; + //bodyDef.position.x += ( i & 1 ) == 1 ? -0.5f * a : 0.5f * a; + //bodyDef.linearVelocity = { 0.0f, -10.0f }; + b2BodyId bodyId = b2CreateBody( m_worldId, &bodyDef ); + + b2CreateCapsuleShape( bodyId, &shapeDef, &capsule ); + + y += 3.0f * a; + } + } + + static Sample* Create( Settings& settings ) + { + return new CapsuleStack( settings ); + } +}; + +static int sampleCapsuleStack = RegisterSample( "Stacking", "Capsule Stack", CapsuleStack::Create ); + class Cliff : public Sample { public: @@ -609,7 +672,7 @@ class Cliff : public Sample } } - void UpdateUI() override + void UpdateGui() override { float height = 60.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); diff --git a/samples/sample_world.cpp b/samples/sample_world.cpp index b10d8ca63..055f19132 100644 --- a/samples/sample_world.cpp +++ b/samples/sample_world.cpp @@ -116,7 +116,7 @@ class LargeWorld : public Sample for ( int i = 0; i < 5; ++i ) { Human human = {}; - CreateHuman(&human, m_worldId, position, 1.5f, 0.05f, 0.0f, 0.0f, humanIndex + 1, NULL, false ); + CreateHuman(&human, m_worldId, position, 1.5f, 0.05f, 0.0f, 0.0f, humanIndex + 1, nullptr, false ); humanIndex += 1; position.x += 1.0f; } @@ -128,7 +128,7 @@ class LargeWorld : public Sample for ( int i = 0; i < 5; ++i ) { Donut donut; - donut.Spawn( m_worldId, position, 0.75f, 0, NULL ); + donut.Create( m_worldId, position, 0.75f, 0, false, nullptr ); position.x += 2.0f; } } @@ -143,7 +143,7 @@ class LargeWorld : public Sample m_followCar = false; } - void UpdateUI() override + void UpdateGui() override { float height = 160.0f; ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); diff --git a/samples/settings.cpp b/samples/settings.cpp index 18b5ecc18..ab9e446b1 100644 --- a/samples/settings.cpp +++ b/samples/settings.cpp @@ -1,7 +1,10 @@ // SPDX-FileCopyrightText: 2023 Erin Catto // SPDX-License-Identifier: MIT +#if defined( _MSC_VER ) && !defined( _CRT_SECURE_NO_WARNINGS ) #define _CRT_SECURE_NO_WARNINGS +#endif + #include "settings.h" // todo consider using https://github.com/skeeto/pdjson @@ -49,10 +52,11 @@ void Settings::Save() fprintf( file, " \"sampleIndex\": %d,\n", sampleIndex ); fprintf( file, " \"drawShapes\": %s,\n", drawShapes ? "true" : "false" ); fprintf( file, " \"drawJoints\": %s,\n", drawJoints ? "true" : "false" ); - fprintf( file, " \"drawAABBs\": %s,\n", drawAABBs ? "true" : "false" ); + fprintf( file, " \"drawBounds\": %s,\n", drawBounds ? "true" : "false" ); fprintf( file, " \"drawContactPoints\": %s,\n", drawContactPoints ? "true" : "false" ); fprintf( file, " \"drawContactNormals\": %s,\n", drawContactNormals ? "true" : "false" ); fprintf( file, " \"drawContactImpulses\": %s,\n", drawContactImpulses ? "true" : "false" ); + fprintf( file, " \"drawContactFeatures\": %s,\n", drawContactFeatures ? "true" : "false" ); fprintf( file, " \"drawFrictionImpulse\": %s,\n", drawFrictionImpulses ? "true" : "false" ); fprintf( file, " \"drawMass\": %s,\n", drawMass ? "true" : "false" ); fprintf( file, " \"drawCounters\": %s,\n", drawCounters ? "true" : "false" ); diff --git a/samples/settings.h b/samples/settings.h index a4bbe02c7..0848cfefb 100644 --- a/samples/settings.h +++ b/samples/settings.h @@ -12,20 +12,24 @@ struct Settings int sampleIndex = 0; int windowWidth = 1920; int windowHeight = 1080; + float hertz = 60.0f; int subStepCount = 4; int workerCount = 1; + bool useCameraBounds = false; bool drawShapes = true; bool drawJoints = true; bool drawJointExtras = false; - bool drawAABBs = false; + bool drawBounds = false; + bool drawMass = false; + bool drawBodyNames = false; bool drawContactPoints = false; bool drawContactNormals = false; bool drawContactImpulses = false; + bool drawContactFeatures = false; bool drawFrictionImpulses = false; - bool drawMass = false; - bool drawBodyNames = false; + bool drawIslands = false; bool drawGraphColors = false; bool drawCounters = false; bool drawProfile = false; diff --git a/samples/shader.cpp b/samples/shader.cpp index 61fdcba17..a7b5f72a1 100644 --- a/samples/shader.cpp +++ b/samples/shader.cpp @@ -1,7 +1,9 @@ // SPDX-FileCopyrightText: 2024 Erin Catto // SPDX-License-Identifier: MIT +#if defined( _MSC_VER ) && !defined( _CRT_SECURE_NO_WARNINGS ) #define _CRT_SECURE_NO_WARNINGS +#endif #include "shader.h" @@ -11,7 +13,7 @@ #include #include -#if defined( _WIN32 ) +#if defined( _MSC_VER ) #define _CRTDBG_MAP_ALLOC #include #include @@ -39,7 +41,7 @@ void DumpInfoGL() printf( "-------------------------------------------------------------\n" ); } -void CheckErrorGL() +void CheckOpenGL() { GLenum errCode = glGetError(); if ( errCode != GL_NO_ERROR ) diff --git a/samples/shader.h b/samples/shader.h index 28202f1f6..ed925b6ec 100644 --- a/samples/shader.h +++ b/samples/shader.h @@ -8,6 +8,6 @@ uint32_t CreateProgramFromFiles( const char* vertexPath, const char* fragmentPath ); uint32_t CreateProgramFromStrings( const char* vertexString, const char* fragmentString ); -void CheckErrorGL(); +void CheckOpenGL(); void DumpInfoGL(); void PrintLogGL( int object ); diff --git a/shared/human.c b/shared/human.c index 5084f261d..d3660e0e7 100644 --- a/shared/human.c +++ b/shared/human.c @@ -10,12 +10,12 @@ #include -void CreateHuman( Human* human, b2WorldId worldId, b2Vec2 position, float scale, float frictionTorque, float hertz, float dampingRatio, - int groupIndex, void* userData, bool colorize ) +void CreateHuman( Human* human, b2WorldId worldId, b2Vec2 position, float scale, float frictionTorque, float hertz, + float dampingRatio, int groupIndex, void* userData, bool colorize ) { assert( human->isSpawned == false ); - for ( int i = 0; i < boneId_count; ++i ) + for ( int i = 0; i < bone_count; ++i ) { human->bones[i].bodyId = b2_nullBodyId; human->bones[i].jointId = b2_nullJointId; @@ -62,7 +62,7 @@ void CreateHuman( Human* human, b2WorldId worldId, b2Vec2 position, float scale, // hip { - Bone* bone = human->bones + boneId_hip; + Bone* bone = human->bones + bone_hip; bone->parentIndex = -1; bodyDef.position = b2Add( ( b2Vec2 ){ 0.0f, 0.95f * s }, position ); @@ -80,8 +80,8 @@ void CreateHuman( Human* human, b2WorldId worldId, b2Vec2 position, float scale, // torso { - Bone* bone = human->bones + boneId_torso; - bone->parentIndex = boneId_hip; + Bone* bone = human->bones + bone_torso; + bone->parentIndex = bone_hip; bodyDef.position = b2Add( ( b2Vec2 ){ 0.0f, 1.2f * s }, position ); bodyDef.linearDamping = 0.0f; @@ -119,8 +119,8 @@ void CreateHuman( Human* human, b2WorldId worldId, b2Vec2 position, float scale, // head { - Bone* bone = human->bones + boneId_head; - bone->parentIndex = boneId_torso; + Bone* bone = human->bones + bone_head; + bone->parentIndex = bone_torso; bodyDef.position = b2Add( ( b2Vec2 ){ 0.0f * s, 1.475f * s }, position ); bodyDef.linearDamping = 0.1f; @@ -161,8 +161,8 @@ void CreateHuman( Human* human, b2WorldId worldId, b2Vec2 position, float scale, // upper left leg { - Bone* bone = human->bones + boneId_upperLeftLeg; - bone->parentIndex = boneId_hip; + Bone* bone = human->bones + bone_upperLeftLeg; + bone->parentIndex = bone_hip; bodyDef.position = b2Add( ( b2Vec2 ){ 0.0f, 0.775f * s }, position ); bodyDef.linearDamping = 0.0f; @@ -208,8 +208,8 @@ void CreateHuman( Human* human, b2WorldId worldId, b2Vec2 position, float scale, // lower left leg { - Bone* bone = human->bones + boneId_lowerLeftLeg; - bone->parentIndex = boneId_upperLeftLeg; + Bone* bone = human->bones + bone_lowerLeftLeg; + bone->parentIndex = bone_upperLeftLeg; bodyDef.position = b2Add( ( b2Vec2 ){ 0.0f, 0.475f * s }, position ); bodyDef.linearDamping = 0.0f; @@ -253,8 +253,8 @@ void CreateHuman( Human* human, b2WorldId worldId, b2Vec2 position, float scale, // upper right leg { - Bone* bone = human->bones + boneId_upperRightLeg; - bone->parentIndex = boneId_hip; + Bone* bone = human->bones + bone_upperRightLeg; + bone->parentIndex = bone_hip; bodyDef.position = b2Add( ( b2Vec2 ){ 0.0f, 0.775f * s }, position ); bodyDef.linearDamping = 0.0f; @@ -290,8 +290,8 @@ void CreateHuman( Human* human, b2WorldId worldId, b2Vec2 position, float scale, // lower right leg { - Bone* bone = human->bones + boneId_lowerRightLeg; - bone->parentIndex = boneId_upperRightLeg; + Bone* bone = human->bones + bone_lowerRightLeg; + bone->parentIndex = bone_upperRightLeg; bodyDef.position = b2Add( ( b2Vec2 ){ 0.0f, 0.475f * s }, position ); bodyDef.linearDamping = 0.0f; @@ -335,8 +335,8 @@ void CreateHuman( Human* human, b2WorldId worldId, b2Vec2 position, float scale, // upper left arm { - Bone* bone = human->bones + boneId_upperLeftArm; - bone->parentIndex = boneId_torso; + Bone* bone = human->bones + bone_upperLeftArm; + bone->parentIndex = bone_torso; bone->frictionScale = 0.5f; bodyDef.position = b2Add( ( b2Vec2 ){ 0.0f, 1.225f * s }, position ); @@ -372,8 +372,8 @@ void CreateHuman( Human* human, b2WorldId worldId, b2Vec2 position, float scale, // lower left arm { - Bone* bone = human->bones + boneId_lowerLeftArm; - bone->parentIndex = boneId_upperLeftArm; + Bone* bone = human->bones + bone_lowerLeftArm; + bone->parentIndex = bone_upperLeftArm; bodyDef.position = b2Add( ( b2Vec2 ){ 0.0f, 0.975f * s }, position ); bodyDef.linearDamping = 0.1f; @@ -410,8 +410,8 @@ void CreateHuman( Human* human, b2WorldId worldId, b2Vec2 position, float scale, // upper right arm { - Bone* bone = human->bones + boneId_upperRightArm; - bone->parentIndex = boneId_torso; + Bone* bone = human->bones + bone_upperRightArm; + bone->parentIndex = bone_torso; bodyDef.position = b2Add( ( b2Vec2 ){ 0.0f, 1.225f * s }, position ); bodyDef.linearDamping = 0.0f; @@ -447,8 +447,8 @@ void CreateHuman( Human* human, b2WorldId worldId, b2Vec2 position, float scale, // lower right arm { - Bone* bone = human->bones + boneId_lowerRightArm; - bone->parentIndex = boneId_upperRightArm; + Bone* bone = human->bones + bone_lowerRightArm; + bone->parentIndex = bone_upperRightArm; bodyDef.position = b2Add( ( b2Vec2 ){ 0.0f, 0.975f * s }, position ); bodyDef.linearDamping = 0.1f; @@ -490,7 +490,7 @@ void DestroyHuman( Human* human ) { assert( human->isSpawned == true ); - for ( int i = 0; i < boneId_count; ++i ) + for ( int i = 0; i < bone_count; ++i ) { if ( B2_IS_NULL( human->bones[i].jointId ) ) { @@ -501,7 +501,7 @@ void DestroyHuman( Human* human ) human->bones[i].jointId = b2_nullJointId; } - for ( int i = 0; i < boneId_count; ++i ) + for ( int i = 0; i < bone_count; ++i ) { if ( B2_IS_NULL( human->bones[i].bodyId ) ) { @@ -517,7 +517,7 @@ void DestroyHuman( Human* human ) void Human_SetVelocity( Human* human, b2Vec2 velocity ) { - for ( int i = 0; i < boneId_count; ++i ) + for ( int i = 0; i < bone_count; ++i ) { b2BodyId bodyId = human->bones[i].bodyId; @@ -534,7 +534,7 @@ void Human_ApplyRandomAngularImpulse( Human* human, float magnitude ) { assert( human->isSpawned == true ); float impulse = RandomFloatRange( -magnitude, magnitude ); - b2Body_ApplyAngularImpulse( human->bones[boneId_torso].bodyId, impulse, true ); + b2Body_ApplyAngularImpulse( human->bones[bone_torso].bodyId, impulse, true ); } void Human_SetJointFrictionTorque( Human* human, float torque ) @@ -542,14 +542,14 @@ void Human_SetJointFrictionTorque( Human* human, float torque ) assert( human->isSpawned == true ); if ( torque == 0.0f ) { - for ( int i = 1; i < boneId_count; ++i ) + for ( int i = 1; i < bone_count; ++i ) { b2RevoluteJoint_EnableMotor( human->bones[i].jointId, false ); } } else { - for ( int i = 1; i < boneId_count; ++i ) + for ( int i = 1; i < bone_count; ++i ) { b2RevoluteJoint_EnableMotor( human->bones[i].jointId, true ); float scale = human->scale * human->bones[i].frictionScale; @@ -563,14 +563,14 @@ void Human_SetJointSpringHertz( Human* human, float hertz ) assert( human->isSpawned == true ); if ( hertz == 0.0f ) { - for ( int i = 1; i < boneId_count; ++i ) + for ( int i = 1; i < bone_count; ++i ) { b2RevoluteJoint_EnableSpring( human->bones[i].jointId, false ); } } else { - for ( int i = 1; i < boneId_count; ++i ) + for ( int i = 1; i < bone_count; ++i ) { b2RevoluteJoint_EnableSpring( human->bones[i].jointId, true ); b2RevoluteJoint_SetSpringHertz( human->bones[i].jointId, hertz ); @@ -581,8 +581,21 @@ void Human_SetJointSpringHertz( Human* human, float hertz ) void Human_SetJointDampingRatio( Human* human, float dampingRatio ) { assert( human->isSpawned == true ); - for ( int i = 1; i < boneId_count; ++i ) + for ( int i = 1; i < bone_count; ++i ) { b2RevoluteJoint_SetSpringDampingRatio( human->bones[i].jointId, dampingRatio ); } } + +void Human_EnableSensorEvents( Human* human, bool enable ) +{ + assert( human->isSpawned == true ); + b2BodyId bodyId = human->bones[bone_torso].bodyId; + + b2ShapeId shapeId; + int count = b2Body_GetShapes( bodyId, &shapeId, 1 ); + if ( count == 1 ) + { + b2Shape_EnableSensorEvents( shapeId, enable ); + } +} diff --git a/shared/human.h b/shared/human.h index 4a070eaa9..15faf9065 100644 --- a/shared/human.h +++ b/shared/human.h @@ -7,18 +7,18 @@ typedef enum BoneId { - boneId_hip = 0, - boneId_torso = 1, - boneId_head = 2, - boneId_upperLeftLeg = 3, - boneId_lowerLeftLeg = 4, - boneId_upperRightLeg = 5, - boneId_lowerRightLeg = 6, - boneId_upperLeftArm = 7, - boneId_lowerLeftArm = 8, - boneId_upperRightArm = 9, - boneId_lowerRightArm = 10, - boneId_count = 11, + bone_hip = 0, + bone_torso = 1, + bone_head = 2, + bone_upperLeftLeg = 3, + bone_lowerLeftLeg = 4, + bone_upperRightLeg = 5, + bone_lowerRightLeg = 6, + bone_upperLeftArm = 7, + bone_lowerLeftArm = 8, + bone_upperRightArm = 9, + bone_lowerRightArm = 10, + bone_count = 11, } BoneId; typedef struct Bone @@ -31,7 +31,7 @@ typedef struct Bone typedef struct Human { - Bone bones[boneId_count]; + Bone bones[bone_count]; float scale; bool isSpawned; } Human; @@ -51,6 +51,7 @@ void Human_ApplyRandomAngularImpulse( Human* human, float magnitude ); void Human_SetJointFrictionTorque( Human* human, float torque ); void Human_SetJointSpringHertz( Human* human, float hertz ); void Human_SetJointDampingRatio( Human* human, float dampingRatio ); +void Human_EnableSensorEvents( Human* human, bool enable ); #ifdef __cplusplus } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cff1021f2..fd91b956c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -147,6 +147,10 @@ if (MSVC) target_compile_options(box2d PRIVATE /arch:AVX2) endif() + if (${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") + target_compile_options(box2d PRIVATE -Wmissing-prototypes) + endif() + elseif (MINGW) message(STATUS "Box2D on MinGW") if (BOX2D_AVX2) diff --git a/src/body.c b/src/body.c index 286954f00..77e6eaeb4 100644 --- a/src/body.c +++ b/src/body.c @@ -232,11 +232,6 @@ b2BodyId b2CreateBody( b2WorldId worldId, const b2BodyDef* def ) bodySim->center = def->position; bodySim->rotation0 = bodySim->transform.q; bodySim->center0 = bodySim->center; - bodySim->localCenter = b2Vec2_zero; - bodySim->force = b2Vec2_zero; - bodySim->torque = 0.0f; - bodySim->invMass = 0.0f; - bodySim->invInertia = 0.0f; bodySim->minExtent = B2_HUGE; bodySim->maxExtent = 0.0f; bodySim->linearDamping = def->linearDamping; @@ -245,9 +240,6 @@ b2BodyId b2CreateBody( b2WorldId worldId, const b2BodyDef* def ) bodySim->bodyId = bodyId; bodySim->isBullet = def->isBullet; bodySim->allowFastRotation = def->allowFastRotation; - bodySim->enlargeAABB = false; - bodySim->isFast = false; - bodySim->isSpeedCapped = false; if ( setId == b2_awakeSet ) { @@ -457,8 +449,6 @@ int b2Body_GetContactCapacity( b2BodyId bodyId ) return body->contactCount; } -// todo what about sensors? -// todo sample needed int b2Body_GetContactData( b2BodyId bodyId, b2ContactData* contactData, int capacity ) { b2World* world = b2GetWorldLocked( bodyId.world0 ); diff --git a/src/body.h b/src/body.h index f0c1ecbd8..b13cdc220 100644 --- a/src/body.h +++ b/src/body.h @@ -71,10 +71,32 @@ typedef struct b2Body bool isMarked; } b2Body; +// Body State // The body state is designed for fast conversion to and from SIMD via scatter-gather. // Only awake dynamic and kinematic bodies have a body state. // This is used in the performance critical constraint solver // +// The solver operates on the body state. The body state array does not hold static bodies. Static bodies are shared +// across worker threads. It would be okay to read their states, but writing to them would cause cache thrashing across +// workers, even if the values don't change. +// This causes some trouble when computing anchors. I rotate joint anchors using the body rotation every sub-step. For static +// bodies the anchor doesn't rotate. Body A or B could be static and this can lead to lots of branching. This branching +// should be minimized. +// +// Solution 1: +// Use delta rotations. This means anchors need to be prepared in world space. The delta rotation for static bodies will be +// identity using a dummy state. Base separation and angles need to be computed. Manifolds will be behind a frame, but that +// is probably best if bodies move fast. +// +// Solution 2: +// Use full rotation. The anchors for static bodies will be in world space while the anchors for dynamic bodies will be in local +// space. Potentially confusing and bug prone. +// +// Note: +// I rotate joint anchors each sub-step but not contact anchors. Joint stability improves a lot by rotating joint anchors +// according to substep progress. Contacts have reduced stability when anchors are rotated during substeps, especially for +// round shapes. + // 32 bytes typedef struct b2BodyState { diff --git a/src/broad_phase.c b/src/broad_phase.c index ebfff392e..fbc4a1053 100644 --- a/src/broad_phase.c +++ b/src/broad_phase.c @@ -388,7 +388,7 @@ void b2UpdateBroadPhasePairs( b2World* world ) b2TracyCZoneNC( update_pairs, "Find Pairs", b2_colorMediumSlateBlue, true ); - b2ArenaAllocator* alloc = &world->stackAllocator; + b2ArenaAllocator* alloc = &world->arena; // todo these could be in the step context bp->moveResults = b2AllocateArenaItem( alloc, moveCount * sizeof( b2MoveResult ), "move results" ); diff --git a/src/constraint_graph.c b/src/constraint_graph.c index c29066b85..1737aea77 100644 --- a/src/constraint_graph.c +++ b/src/constraint_graph.c @@ -260,7 +260,7 @@ static int b2AssignJointColor( b2ConstraintGraph* graph, int bodyIdA, int bodyId } } #else - B2_UNUSED( graph, bodyIdA, bodyIdB ); + B2_UNUSED( graph, bodyIdA, bodyIdB, staticA, staticB ); #endif return B2_OVERFLOW_INDEX; diff --git a/src/contact.c b/src/contact.c index 01566ec30..5a354693c 100644 --- a/src/contact.c +++ b/src/contact.c @@ -569,7 +569,7 @@ bool b2UpdateContact( b2World* world, b2ContactSim* contactSim, b2Shape* shapeA, mp2->normalImpulse = 0.0f; mp2->tangentImpulse = 0.0f; - mp2->maxNormalImpulse = 0.0f; + mp2->totalNormalImpulse = 0.0f; mp2->normalVelocity = 0.0f; mp2->persisted = false; diff --git a/src/contact_solver.c b/src/contact_solver.c index e6f131b14..cbb773480 100644 --- a/src/contact_solver.c +++ b/src/contact_solver.c @@ -12,6 +12,15 @@ #include +// contact separation for sub-stepping +// s = s0 + dot(cB + rB - cA - rA, normal) +// normal is held constant +// body positions c can translation and anchors r can rotate +// s(t) = s0 + dot(cB(t) + rB(t) - cA(t) - rA(t), normal) +// s(t) = s0 + dot(cB0 + dpB + rot(dqB, rB0) - cA0 - dpA - rot(dqA, rA0), normal) +// s(t) = s0 + dot(cB0 - cA0, normal) + dot(dpB - dpA + rot(dqB, rB0) - rot(dqA, rA0), normal) +// s_base = s0 + dot(cB0 - cA0, normal) + void b2PrepareOverflowContacts( b2StepContext* context ) { b2TracyCZoneNC( prepare_overflow_contact, "Prepare Overflow Contact", b2_colorYellow, true ); @@ -119,7 +128,7 @@ void b2PrepareOverflowContacts( b2StepContext* context ) cp->normalImpulse = warmStartScale * mp->normalImpulse; cp->tangentImpulse = warmStartScale * mp->tangentImpulse; - cp->maxNormalImpulse = 0.0f; + cp->totalNormalImpulse = 0.0f; b2Vec2 rA = mp->anchorA; b2Vec2 rB = mp->anchorB; @@ -228,7 +237,7 @@ void b2SolveOverflowContacts( b2StepContext* context, bool useBias ) b2BodyState* states = awakeSet->bodyStates.data; float inv_h = context->inv_h; - const float pushout = context->world->contactMaxPushSpeed; + const float pushout = context->world->maxContactPushSpeed; // This is a dummy body to represent a static body since static bodies don't have a solver body. b2BodyState dummyState = b2_identityBodyState; @@ -266,10 +275,14 @@ void b2SolveOverflowContacts( b2StepContext* context, bool useBias ) { b2ContactConstraintPoint* cp = constraint->points + j; + // fixed anchor points + b2Vec2 rA = cp->anchorA; + b2Vec2 rB = cp->anchorB; + // compute current separation // this is subject to round-off error if the anchor is far from the body center of mass - b2Vec2 ds = b2Add( dp, b2Sub( b2RotateVector( dqB, cp->anchorB ), b2RotateVector( dqA, cp->anchorA ) ) ); - float s = b2Dot( ds, normal ) + cp->baseSeparation; + b2Vec2 ds = b2Add( dp, b2Sub( b2RotateVector( dqB, rB ), b2RotateVector( dqA, rA ) ) ); + float s = cp->baseSeparation + b2Dot( ds, normal ); float velocityBias = 0.0f; float massScale = 1.0f; @@ -286,10 +299,6 @@ void b2SolveOverflowContacts( b2StepContext* context, bool useBias ) impulseScale = softness.impulseScale; } - // fixed anchor points - b2Vec2 rA = cp->anchorA; - b2Vec2 rB = cp->anchorB; - // relative normal velocity at contact b2Vec2 vrA = b2Add( vA, b2CrossSV( wA, rA ) ); b2Vec2 vrB = b2Add( vB, b2CrossSV( wB, rB ) ); @@ -302,7 +311,7 @@ void b2SolveOverflowContacts( b2StepContext* context, bool useBias ) float newImpulse = b2MaxFloat( cp->normalImpulse + impulse, 0.0f ); impulse = newImpulse - cp->normalImpulse; cp->normalImpulse = newImpulse; - cp->maxNormalImpulse = b2MaxFloat( cp->maxNormalImpulse, impulse ); + cp->totalNormalImpulse += newImpulse; totalNormalImpulse += newImpulse; // apply normal impulse @@ -424,7 +433,7 @@ void b2ApplyOverflowRestitution( b2StepContext* context ) // if the normal impulse is zero then there was no collision // this skips speculative contact points that didn't generate an impulse // The max normal impulse is used in case there was a collision that moved away within the sub-step process - if ( cp->relativeVelocity > -threshold || cp->maxNormalImpulse == 0.0f ) + if ( cp->relativeVelocity > -threshold || cp->totalNormalImpulse == 0.0f ) { continue; } @@ -446,7 +455,7 @@ void b2ApplyOverflowRestitution( b2StepContext* context ) float newImpulse = b2MaxFloat( cp->normalImpulse + impulse, 0.0f ); impulse = newImpulse - cp->normalImpulse; cp->normalImpulse = newImpulse; - cp->maxNormalImpulse = b2MaxFloat( cp->maxNormalImpulse, impulse ); + cp->totalNormalImpulse += impulse; // apply contact impulse b2Vec2 P = b2MulSV( impulse, normal ); @@ -476,8 +485,6 @@ void b2StoreOverflowImpulses( b2StepContext* context ) b2ContactSim* contacts = color->contactSims.data; int contactCount = color->contactSims.count; - // float hitEventThreshold = context->world->hitEventThreshold; - for ( int i = 0; i < contactCount; ++i ) { const b2ContactConstraint* constraint = constraints + i; @@ -489,7 +496,7 @@ void b2StoreOverflowImpulses( b2StepContext* context ) { manifold->points[j].normalImpulse = constraint->points[j].normalImpulse; manifold->points[j].tangentImpulse = constraint->points[j].tangentImpulse; - manifold->points[j].maxNormalImpulse = constraint->points[j].maxNormalImpulse; + manifold->points[j].totalNormalImpulse = constraint->points[j].totalNormalImpulse; manifold->points[j].normalVelocity = constraint->points[j].relativeVelocity; } @@ -593,7 +600,7 @@ static inline b2FloatW b2MaxW( b2FloatW a, b2FloatW b ) } // a = clamp(a, -b, b) -static inline b2FloatW b2ClampSymW( b2FloatW a, b2FloatW b ) +static inline b2FloatW b2SymClampW( b2FloatW a, b2FloatW b ) { b2FloatW nb = _mm256_sub_ps( _mm256_setzero_ps(), b ); return _mm256_max_ps(nb, _mm256_min_ps( a, b )); @@ -674,7 +681,7 @@ static inline b2FloatW b2MaxW( b2FloatW a, b2FloatW b ) } // a = clamp(a, -b, b) -static inline b2FloatW b2ClampSymW( b2FloatW a, b2FloatW b ) +static inline b2FloatW b2SymClampW( b2FloatW a, b2FloatW b ) { b2FloatW nb = vnegq_f32( b ); return vmaxq_f32( nb, vminq_f32( a, b ) ); @@ -789,7 +796,7 @@ static inline b2FloatW b2MaxW( b2FloatW a, b2FloatW b ) } // a = clamp(a, -b, b) -static inline b2FloatW b2ClampSymW( b2FloatW a, b2FloatW b ) +static inline b2FloatW b2SymClampW( b2FloatW a, b2FloatW b ) { // Create a mask with the sign bit set for each element __m128 mask = _mm_set1_ps( -0.0f ); @@ -899,7 +906,7 @@ static inline b2FloatW b2MaxW( b2FloatW a, b2FloatW b ) } // a = clamp(a, -b, b) -static inline b2FloatW b2ClampSymW( b2FloatW a, b2FloatW b ) +static inline b2FloatW b2SymClampW( b2FloatW a, b2FloatW b ) { b2FloatW r; r.x = b2ClampFloat(a.x, -b.x, b.x); @@ -992,12 +999,12 @@ typedef struct b2ContactConstraintSIMD b2FloatW normalMass1, tangentMass1; b2FloatW baseSeparation1; b2FloatW normalImpulse1; - b2FloatW maxNormalImpulse1; + b2FloatW totalNormalImpulse1; b2FloatW tangentImpulse1; b2Vec2W anchorA2, anchorB2; b2FloatW baseSeparation2; b2FloatW normalImpulse2; - b2FloatW maxNormalImpulse2; + b2FloatW totalNormalImpulse2; b2FloatW tangentImpulse2; b2FloatW normalMass2, tangentMass2; b2FloatW restitution; @@ -1483,7 +1490,7 @@ void b2PrepareContactsTask( int startIndex, int endIndex, b2StepContext* context ( (float*)&constraint->normalImpulse1 )[j] = warmStartScale * mp->normalImpulse; ( (float*)&constraint->tangentImpulse1 )[j] = warmStartScale * mp->tangentImpulse; - ( (float*)&constraint->maxNormalImpulse1 )[j] = 0.0f; + ( (float*)&constraint->totalNormalImpulse1 )[j] = 0.0f; float rnA = b2Cross( rA, normal ); float rnB = b2Cross( rB, normal ); @@ -1520,7 +1527,7 @@ void b2PrepareContactsTask( int startIndex, int endIndex, b2StepContext* context ( (float*)&constraint->normalImpulse2 )[j] = warmStartScale * mp->normalImpulse; ( (float*)&constraint->tangentImpulse2 )[j] = warmStartScale * mp->tangentImpulse; - ( (float*)&constraint->maxNormalImpulse2 )[j] = 0.0f; + ( (float*)&constraint->totalNormalImpulse2 )[j] = 0.0f; float rnA = b2Cross( rA, normal ); float rnB = b2Cross( rB, normal ); @@ -1543,7 +1550,7 @@ void b2PrepareContactsTask( int startIndex, int endIndex, b2StepContext* context ( (float*)&constraint->baseSeparation2 )[j] = 0.0f; ( (float*)&constraint->normalImpulse2 )[j] = 0.0f; ( (float*)&constraint->tangentImpulse2 )[j] = 0.0f; - ( (float*)&constraint->maxNormalImpulse2 )[j] = 0.0f; + ( (float*)&constraint->totalNormalImpulse2 )[j] = 0.0f; ( (float*)&constraint->anchorA2.X )[j] = 0.0f; ( (float*)&constraint->anchorA2.Y )[j] = 0.0f; ( (float*)&constraint->anchorB2.X )[j] = 0.0f; @@ -1582,7 +1589,7 @@ void b2PrepareContactsTask( int startIndex, int endIndex, b2StepContext* context ( (float*)&constraint->baseSeparation1 )[j] = 0.0f; ( (float*)&constraint->normalImpulse1 )[j] = 0.0f; ( (float*)&constraint->tangentImpulse1 )[j] = 0.0f; - ( (float*)&constraint->maxNormalImpulse1 )[j] = 0.0f; + ( (float*)&constraint->totalNormalImpulse1 )[j] = 0.0f; ( (float*)&constraint->normalMass1 )[j] = 0.0f; ( (float*)&constraint->tangentMass1 )[j] = 0.0f; @@ -1593,7 +1600,7 @@ void b2PrepareContactsTask( int startIndex, int endIndex, b2StepContext* context ( (float*)&constraint->baseSeparation2 )[j] = 0.0f; ( (float*)&constraint->normalImpulse2 )[j] = 0.0f; ( (float*)&constraint->tangentImpulse2 )[j] = 0.0f; - ( (float*)&constraint->maxNormalImpulse2 )[j] = 0.0f; + ( (float*)&constraint->totalNormalImpulse2 )[j] = 0.0f; ( (float*)&constraint->normalMass2 )[j] = 0.0f; ( (float*)&constraint->tangentMass2 )[j] = 0.0f; @@ -1672,7 +1679,8 @@ void b2SolveContactsTask( int startIndex, int endIndex, b2StepContext* context, b2BodyState* states = context->states; b2ContactConstraintSIMD* constraints = context->graph->colors[colorIndex].simdConstraints; b2FloatW inv_h = b2SplatW( context->inv_h ); - b2FloatW minBiasVel = b2SplatW( -context->world->contactMaxPushSpeed ); + b2FloatW minBiasVel = b2SplatW( -context->world->maxContactPushSpeed ); + b2FloatW oneW = b2SplatW( 1.0f ); for ( int i = startIndex; i < endIndex; ++i ) { @@ -1691,7 +1699,7 @@ void b2SolveContactsTask( int startIndex, int endIndex, b2StepContext* context, else { biasRate = b2ZeroW(); - massScale = b2SplatW( 1.0f ); + massScale = oneW; impulseScale = b2ZeroW(); } @@ -1701,9 +1709,13 @@ void b2SolveContactsTask( int startIndex, int endIndex, b2StepContext* context, // point1 non-penetration constraint { - // moving anchors for current separation - b2Vec2W rsA = b2RotateVectorW( bA.dq, c->anchorA1 ); - b2Vec2W rsB = b2RotateVectorW( bB.dq, c->anchorB1 ); + // Fixed anchors for impulses + b2Vec2W rA = c->anchorA1; + b2Vec2W rB = c->anchorB1; + + // Moving anchors for current separation + b2Vec2W rsA = b2RotateVectorW( bA.dq, rA ); + b2Vec2W rsB = b2RotateVectorW( bB.dq, rB ); // compute current separation // this is subject to round-off error if the anchor is far from the body center of mass @@ -1715,11 +1727,12 @@ void b2SolveContactsTask( int startIndex, int endIndex, b2StepContext* context, b2FloatW mask = b2GreaterThanW( s, b2ZeroW() ); b2FloatW specBias = b2MulW( s, inv_h ); b2FloatW softBias = b2MaxW( b2MulW( biasRate, s ), minBiasVel ); + + // todo try b2MaxW(softBias, specBias); b2FloatW bias = b2BlendW( softBias, specBias, mask ); - // fixed anchors for Jacobians - b2Vec2W rA = c->anchorA1; - b2Vec2W rB = c->anchorB1; + b2FloatW pointMassScale = b2BlendW( massScale, oneW, mask ); + b2FloatW pointImpulseScale = b2BlendW( impulseScale, b2ZeroW(), mask ); // Relative velocity at contact b2FloatW dvx = b2SubW( b2SubW( bB.v.X, b2MulW( bB.w, rB.Y ) ), b2SubW( bA.v.X, b2MulW( bA.w, rA.Y ) ) ); @@ -1727,14 +1740,14 @@ void b2SolveContactsTask( int startIndex, int endIndex, b2StepContext* context, b2FloatW vn = b2AddW( b2MulW( dvx, c->normal.X ), b2MulW( dvy, c->normal.Y ) ); // Compute normal impulse - b2FloatW negImpulse = b2AddW( b2MulW( c->normalMass1, b2MulW( massScale, b2AddW( vn, bias ) ) ), - b2MulW( impulseScale, c->normalImpulse1 ) ); + b2FloatW negImpulse = b2AddW( b2MulW( c->normalMass1, b2MulW( pointMassScale, b2AddW( vn, bias ) ) ), + b2MulW( pointImpulseScale, c->normalImpulse1 ) ); // Clamp the accumulated impulse b2FloatW newImpulse = b2MaxW( b2SubW( c->normalImpulse1, negImpulse ), b2ZeroW() ); b2FloatW impulse = b2SubW( newImpulse, c->normalImpulse1 ); c->normalImpulse1 = newImpulse; - c->maxNormalImpulse1 = b2MaxW( c->maxNormalImpulse1, newImpulse ); + c->totalNormalImpulse1 = b2AddW( c->totalNormalImpulse1, newImpulse ); totalNormalImpulse = b2AddW( totalNormalImpulse, newImpulse ); @@ -1766,6 +1779,9 @@ void b2SolveContactsTask( int startIndex, int endIndex, b2StepContext* context, b2FloatW softBias = b2MaxW( b2MulW( biasRate, s ), minBiasVel ); b2FloatW bias = b2BlendW( softBias, specBias, mask ); + b2FloatW pointMassScale = b2BlendW( massScale, oneW, mask ); + b2FloatW pointImpulseScale = b2BlendW( impulseScale, b2ZeroW(), mask ); + // fixed anchors for Jacobians b2Vec2W rA = c->anchorA2; b2Vec2W rB = c->anchorB2; @@ -1776,14 +1792,14 @@ void b2SolveContactsTask( int startIndex, int endIndex, b2StepContext* context, b2FloatW vn = b2AddW( b2MulW( dvx, c->normal.X ), b2MulW( dvy, c->normal.Y ) ); // Compute normal impulse - b2FloatW negImpulse = b2AddW( b2MulW( c->normalMass2, b2MulW( massScale, b2AddW( vn, bias ) ) ), - b2MulW( impulseScale, c->normalImpulse2 ) ); + b2FloatW negImpulse = b2AddW( b2MulW( c->normalMass2, b2MulW( pointMassScale, b2AddW( vn, bias ) ) ), + b2MulW( pointImpulseScale, c->normalImpulse2 ) ); // Clamp the accumulated impulse b2FloatW newImpulse = b2MaxW( b2SubW( c->normalImpulse2, negImpulse ), b2ZeroW() ); b2FloatW impulse = b2SubW( newImpulse, c->normalImpulse2 ); c->normalImpulse2 = newImpulse; - c->maxNormalImpulse2 = b2MaxW( c->maxNormalImpulse2, newImpulse ); + c->totalNormalImpulse2 = b2AddW( c->totalNormalImpulse2, newImpulse ); totalNormalImpulse = b2AddW( totalNormalImpulse, newImpulse ); @@ -1882,7 +1898,7 @@ void b2SolveContactsTask( int startIndex, int endIndex, b2StepContext* context, b2FloatW deltaLambda = b2MulW( c->rollingMass, b2SubW( bA.w, bB.w )); b2FloatW lambda = c->rollingImpulse; b2FloatW maxLambda = b2MulW( c->rollingResistance, totalNormalImpulse ); - c->rollingImpulse = b2ClampSymW( b2AddW(lambda, deltaLambda), maxLambda ); + c->rollingImpulse = b2SymClampW( b2AddW(lambda, deltaLambda), maxLambda ); deltaLambda = b2SubW(c->rollingImpulse, lambda); bA.w = b2MulSubW( bA.w, c->invIA, deltaLambda ); @@ -1916,7 +1932,7 @@ void b2ApplyRestitutionTask( int startIndex, int endIndex, b2StepContext* contex { // Set effective mass to zero if restitution should not be applied b2FloatW mask1 = b2GreaterThanW( b2AddW( c->relativeVelocity1, threshold ), zero ); - b2FloatW mask2 = b2EqualsW( c->maxNormalImpulse1, zero ); + b2FloatW mask2 = b2EqualsW( c->totalNormalImpulse1, zero ); b2FloatW mask = b2OrW( mask1, mask2 ); b2FloatW mass = b2BlendW( c->normalMass1, zero, mask ); @@ -1954,7 +1970,7 @@ void b2ApplyRestitutionTask( int startIndex, int endIndex, b2StepContext* contex { // Set effective mass to zero if restitution should not be applied b2FloatW mask1 = b2GreaterThanW( b2AddW( c->relativeVelocity2, threshold ), zero ); - b2FloatW mask2 = b2EqualsW( c->maxNormalImpulse2, zero ); + b2FloatW mask2 = b2EqualsW( c->totalNormalImpulse2, zero ); b2FloatW mask = b2OrW( mask1, mask2 ); b2FloatW mass = b2BlendW( c->normalMass2, zero, mask ); @@ -2012,8 +2028,8 @@ void b2StoreImpulsesTask( int startIndex, int endIndex, b2StepContext* context ) const float* normalImpulse2 = (float*)&c->normalImpulse2; const float* tangentImpulse1 = (float*)&c->tangentImpulse1; const float* tangentImpulse2 = (float*)&c->tangentImpulse2; - const float* maxNormalImpulse1 = (float*)&c->maxNormalImpulse1; - const float* maxNormalImpulse2 = (float*)&c->maxNormalImpulse2; + const float* totalNormalImpulse1 = (float*)&c->totalNormalImpulse1; + const float* totalNormalImpulse2 = (float*)&c->totalNormalImpulse2; const float* normalVelocity1 = (float*)&c->relativeVelocity1; const float* normalVelocity2 = (float*)&c->relativeVelocity2; @@ -2026,12 +2042,12 @@ void b2StoreImpulsesTask( int startIndex, int endIndex, b2StepContext* context ) m->points[0].normalImpulse = normalImpulse1[laneIndex]; m->points[0].tangentImpulse = tangentImpulse1[laneIndex]; - m->points[0].maxNormalImpulse = maxNormalImpulse1[laneIndex]; + m->points[0].totalNormalImpulse = totalNormalImpulse1[laneIndex]; m->points[0].normalVelocity = normalVelocity1[laneIndex]; m->points[1].normalImpulse = normalImpulse2[laneIndex]; m->points[1].tangentImpulse = tangentImpulse2[laneIndex]; - m->points[1].maxNormalImpulse = maxNormalImpulse2[laneIndex]; + m->points[1].totalNormalImpulse = totalNormalImpulse2[laneIndex]; m->points[1].normalVelocity = normalVelocity2[laneIndex]; } } diff --git a/src/contact_solver.h b/src/contact_solver.h index 6faaeb9ff..61e46c74a 100644 --- a/src/contact_solver.h +++ b/src/contact_solver.h @@ -14,7 +14,7 @@ typedef struct b2ContactConstraintPoint float relativeVelocity; float normalImpulse; float tangentImpulse; - float maxNormalImpulse; + float totalNormalImpulse; float normalMass; float tangentMass; } b2ContactConstraintPoint; diff --git a/src/core.c b/src/core.c index 4e39eb5c4..7fcc90897 100644 --- a/src/core.c +++ b/src/core.c @@ -71,79 +71,6 @@ b2Version b2GetVersion( void ) return ( b2Version ){ 3, 1, 0 }; } -#if 0 -#if defined( _MSC_VER ) -#include -#endif - -void b2AtomicStoreInt( b2AtomicInt* a, int value ) -{ -#if defined( _MSC_VER ) - (void)_InterlockedExchange( (long*)&a->value, value ); -#elif defined( __GNUC__ ) || defined( __clang__ ) - __atomic_store_n( &a->value, value, __ATOMIC_SEQ_CST ); -#else -#error "Unsupported platform" -#endif -} - -int b2AtomicLoadInt( b2AtomicInt* a ) -{ -#if defined( _MSC_VER ) - return _InterlockedOr( (long*)&a->value, 0 ); -#elif defined( __GNUC__ ) || defined( __clang__ ) - return __atomic_load_n( &a->value, __ATOMIC_SEQ_CST ); -#else -#error "Unsupported platform" -#endif -} - -int b2AtomicFetchAddInt( b2AtomicInt* a, int increment ) -{ -#if defined( _MSC_VER ) - return _InterlockedExchangeAdd( (long*)&a->value, (long)increment ); -#elif defined( __GNUC__ ) || defined( __clang__ ) - return __atomic_fetch_add( &a->value, increment, __ATOMIC_SEQ_CST ); -#else -#error "Unsupported platform" -#endif -} - -bool b2AtomicCompareExchangeInt( b2AtomicInt* a, int expected, int desired ) -{ -#if defined( _MSC_VER ) - return _InterlockedCompareExchange( (long*)&a->value, (long)desired, (long)expected ) == expected; -#elif defined( __GNUC__ ) || defined( __clang__ ) - // The value written to expected is ignored - return __atomic_compare_exchange_n( &a->value, &expected, desired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST ); -#else -#error "Unsupported platform" -#endif -} - -void b2AtomicStoreU32( b2AtomicU32* a, uint32_t value ) -{ -#if defined( _MSC_VER ) - (void)_InterlockedExchange( (long*)&a->value, value ); -#elif defined( __GNUC__ ) || defined( __clang__ ) - __atomic_store_n( &a->value, value, __ATOMIC_SEQ_CST ); -#else -#error "Unsupported platform" -#endif -} - -uint32_t b2AtomicLoadU32( b2AtomicU32* a ) -{ -#if defined( _MSC_VER ) - return (uint32_t)_InterlockedOr( (long*)&a->value, 0 ); -#elif defined( __GNUC__ ) || defined( __clang__ ) - return __atomic_load_n( &a->value, __ATOMIC_SEQ_CST ); -#else -#error "Unsupported platform" -#endif -} -#endif - static b2AllocFcn* b2_allocFcn = NULL; static b2FreeFcn* b2_freeFcn = NULL; diff --git a/src/core.h b/src/core.h index cb7a5b4d8..9b2233ea8 100644 --- a/src/core.h +++ b/src/core.h @@ -122,16 +122,6 @@ #define B2_CHECK_DEF( DEF ) B2_ASSERT( DEF->internalValue == B2_SECRET_COOKIE ) -void* b2Alloc( int size ); -#define B2_ALLOC_STRUCT( type ) b2Alloc(sizeof(type)) -#define B2_ALLOC_ARRAY( count, type ) b2Alloc(count * sizeof(type)) - -void b2Free( void* mem, int size ); -#define B2_FREE_STRUCT( mem, type ) b2Free( mem, sizeof(type)); -#define B2_FREE_ARRAY( mem, count, type ) b2Free(mem, count * sizeof(type)) - -void* b2GrowAlloc( void* oldMem, int oldSize, int newSize ); - typedef struct b2AtomicInt { int value; @@ -142,12 +132,12 @@ typedef struct b2AtomicU32 uint32_t value; } b2AtomicU32; -#if 0 -void b2AtomicStoreInt( b2AtomicInt* a, int value ); -int b2AtomicLoadInt( b2AtomicInt* a ); -int b2AtomicFetchAddInt( b2AtomicInt* a, int increment ); -bool b2AtomicCompareExchangeInt( b2AtomicInt* obj, int expected, int desired ); +void* b2Alloc( int size ); +#define B2_ALLOC_STRUCT( type ) b2Alloc(sizeof(type)) +#define B2_ALLOC_ARRAY( count, type ) b2Alloc(count * sizeof(type)) -void b2AtomicStoreU32( b2AtomicU32* a, uint32_t value ); -uint32_t b2AtomicLoadU32( b2AtomicU32* a ); -#endif +void b2Free( void* mem, int size ); +#define B2_FREE_STRUCT( mem, type ) b2Free( mem, sizeof(type)); +#define B2_FREE_ARRAY( mem, count, type ) b2Free(mem, count * sizeof(type)) + +void* b2GrowAlloc( void* oldMem, int oldSize, int newSize ); diff --git a/src/distance.c b/src/distance.c index abdec84ed..5fa706e08 100644 --- a/src/distance.c +++ b/src/distance.c @@ -480,14 +480,16 @@ b2DistanceOutput b2ShapeDistance( b2SimplexCache* cache, const b2DistanceInput* // Get simplex vertices as an array. b2SimplexVertex* vertices[] = { &simplex.v1, &simplex.v2, &simplex.v3 }; - const int k_maxIters = 20; + const int maxIterations = 20; + + b2Vec2 normal = b2Vec2_zero; // These store the vertices of the last simplex so that we can check for duplicates and prevent cycling. int saveA[3], saveB[3]; // Main iteration loop. - int iter = 0; - while ( iter < k_maxIters ) + int iteration = 0; + while ( iteration < maxIterations ) { // Copy simplex so we can identify duplicates. int saveCount = simplex.count; @@ -528,6 +530,7 @@ b2DistanceOutput b2ShapeDistance( b2SimplexCache* cache, const b2DistanceInput* // Get search direction. b2Vec2 d = b2ComputeSimplexSearchDirection( &simplex ); + normal = b2Neg( d ); // Ensure the search direction is numerically fit. if ( b2Dot( d, d ) < FLT_EPSILON * FLT_EPSILON ) @@ -551,7 +554,7 @@ b2DistanceOutput b2ShapeDistance( b2SimplexCache* cache, const b2DistanceInput* vertex->w = b2Sub( vertex->wB, vertex->wA ); // Iteration count is equated to the number of support point calls. - ++iter; + ++iteration; // Check for duplicate support points. This is the main termination criteria. bool duplicate = false; @@ -582,8 +585,9 @@ b2DistanceOutput b2ShapeDistance( b2SimplexCache* cache, const b2DistanceInput* // Prepare output b2ComputeSimplexWitnessPoints( &output.pointA, &output.pointB, &simplex ); + output.normal = normal; output.distance = b2Distance( output.pointA, output.pointB ); - output.iterations = iter; + output.iterations = iteration; output.simplexCount = simplexIndex; // Cache the simplex @@ -592,27 +596,13 @@ b2DistanceOutput b2ShapeDistance( b2SimplexCache* cache, const b2DistanceInput* // Apply radii if requested if ( input->useRadii ) { - if ( output.distance < FLT_EPSILON ) - { - // Shapes are too close to safely compute normal - b2Vec2 p = ( b2Vec2 ){ 0.5f * ( output.pointA.x + output.pointB.x ), 0.5f * ( output.pointA.y + output.pointB.y ) }; - output.pointA = p; - output.pointB = p; - output.distance = 0.0f; - } - else - { - // Keep closest points on perimeter even if overlapped, this way - // the points move smoothly. - float rA = proxyA->radius; - float rB = proxyB->radius; - output.distance = b2MaxFloat( 0.0f, output.distance - rA - rB ); - b2Vec2 normal = b2Normalize( b2Sub( output.pointB, output.pointA ) ); - b2Vec2 offsetA = ( b2Vec2 ){ rA * normal.x, rA * normal.y }; - b2Vec2 offsetB = ( b2Vec2 ){ rB * normal.x, rB * normal.y }; - output.pointA = b2Add( output.pointA, offsetA ); - output.pointB = b2Sub( output.pointB, offsetB ); - } + float rA = input->proxyA.radius; + float rB = input->proxyB.radius; + output.distance = b2MaxFloat( 0.0f, output.distance - rA - rB ); + + // Keep closest points on perimeter even if overlapped, this way the points move smoothly. + output.pointA = b2MulAdd( output.pointA, rA, normal); + output.pointB = b2MulSub( output.pointB, rB, normal); } return output; @@ -1055,6 +1045,21 @@ b2TOIOutput b2TimeOfImpact( const b2TOIInput* input ) distanceInput.transformB = xfB; b2DistanceOutput distanceOutput = b2ShapeDistance( &cache, &distanceInput, NULL, 0 ); + // Progressive time of impact. This handles slender geometry well but introduces + // significant time loss. + //if (distanceIterations == 0) + //{ + // if ( distanceOutput.distance > totalRadius + B2_SPECULATIVE_DISTANCE ) + // { + // target = totalRadius + B2_SPECULATIVE_DISTANCE - tolerance; + // } + // else + // { + // target = distanceOutput.distance - 1.5f * tolerance; + // target = b2MaxFloat( target, 2.0f * tolerance ); + // } + //} + distanceIterations += 1; #if B2_SNOOP_TOI_COUNTERS b2_toiDistanceIterations += 1; diff --git a/src/distance_joint.c b/src/distance_joint.c index bdbb44bd7..016e8acc6 100644 --- a/src/distance_joint.c +++ b/src/distance_joint.c @@ -529,28 +529,28 @@ void b2DrawDistanceJoint( b2DebugDraw* draw, b2JointSim* base, b2Transform trans if ( joint->minLength > B2_LINEAR_SLOP ) { // draw->DrawPoint(pMin, 4.0f, c2, draw->context); - draw->DrawSegment( b2Sub( pMin, offset ), b2Add( pMin, offset ), b2_colorLightGreen, draw->context ); + draw->DrawSegmentFcn( b2Sub( pMin, offset ), b2Add( pMin, offset ), b2_colorLightGreen, draw->context ); } if ( joint->maxLength < B2_HUGE ) { // draw->DrawPoint(pMax, 4.0f, c3, draw->context); - draw->DrawSegment( b2Sub( pMax, offset ), b2Add( pMax, offset ), b2_colorRed, draw->context ); + draw->DrawSegmentFcn( b2Sub( pMax, offset ), b2Add( pMax, offset ), b2_colorRed, draw->context ); } if ( joint->minLength > B2_LINEAR_SLOP && joint->maxLength < B2_HUGE ) { - draw->DrawSegment( pMin, pMax, b2_colorGray, draw->context ); + draw->DrawSegmentFcn( pMin, pMax, b2_colorGray, draw->context ); } } - draw->DrawSegment( pA, pB, b2_colorWhite, draw->context ); - draw->DrawPoint( pA, 4.0f, b2_colorWhite, draw->context ); - draw->DrawPoint( pB, 4.0f, b2_colorWhite, draw->context ); + draw->DrawSegmentFcn( pA, pB, b2_colorWhite, draw->context ); + draw->DrawPointFcn( pA, 4.0f, b2_colorWhite, draw->context ); + draw->DrawPointFcn( pB, 4.0f, b2_colorWhite, draw->context ); if ( joint->hertz > 0.0f && joint->enableSpring ) { b2Vec2 pRest = b2MulAdd( pA, joint->length, axis ); - draw->DrawPoint( pRest, 4.0f, b2_colorBlue, draw->context ); + draw->DrawPointFcn( pRest, 4.0f, b2_colorBlue, draw->context ); } } diff --git a/src/id_pool.c b/src/id_pool.c index bca24faff..5bc1c77dc 100644 --- a/src/id_pool.c +++ b/src/id_pool.c @@ -60,12 +60,27 @@ void b2ValidateFreeId( b2IdPool* pool, int id ) B2_ASSERT( 0 ); } +void b2ValidateUsedId( b2IdPool* pool, int id ) +{ + int freeCount = pool->freeArray.count; + for ( int i = 0; i < freeCount; ++i ) + { + if ( pool->freeArray.data[i] == id ) + { + B2_ASSERT( 0 ); + } + } +} + #else void b2ValidateFreeId( b2IdPool* pool, int id ) { - B2_UNUSED( pool ); - B2_UNUSED( id ); + B2_UNUSED( pool, id ); } +void b2ValidateUsedId( b2IdPool* pool, int id ) +{ + B2_UNUSED( pool, id ); +} #endif diff --git a/src/id_pool.h b/src/id_pool.h index a2659400d..16a318886 100644 --- a/src/id_pool.h +++ b/src/id_pool.h @@ -17,6 +17,7 @@ void b2DestroyIdPool( b2IdPool* pool ); int b2AllocId( b2IdPool* pool ); void b2FreeId( b2IdPool* pool, int id ); void b2ValidateFreeId( b2IdPool* pool, int id ); +void b2ValidateUsedId( b2IdPool* pool, int id ); static inline int b2GetIdCount( b2IdPool* pool ) { diff --git a/src/island.c b/src/island.c index 3a3a87610..afbde0cbf 100644 --- a/src/island.c +++ b/src/island.c @@ -57,6 +57,11 @@ b2Island* b2CreateIsland( b2World* world, int setIndex ) void b2DestroyIsland( b2World* world, int islandId ) { + if (world->splitIslandId == islandId) + { + world->splitIslandId = B2_NULL_INDEX; + } + // assume island is empty b2Island* island = b2IslandArray_Get( &world->islands, islandId ); b2SolverSet* set = b2SolverSetArray_Get( &world->solverSets, island->setIndex ); @@ -614,7 +619,7 @@ void b2SplitIsland( b2World* world, int baseId ) int bodyCount = baseIsland->bodyCount; b2Body* bodies = world->bodies.data; - b2ArenaAllocator* alloc = &world->stackAllocator; + b2ArenaAllocator* alloc = &world->arena; // No lock is needed because I ensure the allocator is not used while this task is active. int* stack = b2AllocateArenaItem( alloc, bodyCount * sizeof( int ), "island stack" ); diff --git a/src/joint.c b/src/joint.c index c7b321624..19f8fbbfe 100644 --- a/src/joint.c +++ b/src/joint.c @@ -1218,17 +1218,17 @@ void b2DrawJoint( b2DebugDraw* draw, b2World* world, b2Joint* joint ) b2Vec2 target = jointSim->mouseJoint.targetA; b2HexColor c1 = b2_colorGreen; - draw->DrawPoint( target, 4.0f, c1, draw->context ); - draw->DrawPoint( pB, 4.0f, c1, draw->context ); + draw->DrawPointFcn( target, 4.0f, c1, draw->context ); + draw->DrawPointFcn( pB, 4.0f, c1, draw->context ); b2HexColor c2 = b2_colorLightGray; - draw->DrawSegment( target, pB, c2, draw->context ); + draw->DrawSegmentFcn( target, pB, c2, draw->context ); } break; case b2_nullJoint: { - draw->DrawSegment( pA, pB, b2_colorGold, draw->context ); + draw->DrawSegmentFcn( pA, pB, b2_colorGold, draw->context ); } break; @@ -1245,9 +1245,9 @@ void b2DrawJoint( b2DebugDraw* draw, b2World* world, b2Joint* joint ) break; default: - draw->DrawSegment( transformA.p, pA, color, draw->context ); - draw->DrawSegment( pA, pB, color, draw->context ); - draw->DrawSegment( transformB.p, pB, color, draw->context ); + draw->DrawSegmentFcn( transformA.p, pA, color, draw->context ); + draw->DrawSegmentFcn( pA, pB, color, draw->context ); + draw->DrawSegmentFcn( transformB.p, pB, color, draw->context ); } if ( draw->drawGraphColors ) @@ -1260,7 +1260,7 @@ void b2DrawJoint( b2DebugDraw* draw, b2World* world, b2Joint* joint ) if ( colorIndex != B2_NULL_INDEX ) { b2Vec2 p = b2Lerp( pA, pB, 0.5f ); - draw->DrawPoint( p, 5.0f, colors[colorIndex], draw->context ); + draw->DrawPointFcn( p, 5.0f, colors[colorIndex], draw->context ); } } } diff --git a/src/manifold.c b/src/manifold.c index 773a2d931..97f26ce12 100644 --- a/src/manifold.c +++ b/src/manifold.c @@ -393,7 +393,9 @@ b2Manifold b2CollideCapsules( const b2Capsule* capsuleA, b2Transform xfA, const } } - if ( separationA >= separationB ) + // biased to avoid feature flip-flop + // todo more testing? + if ( separationA + 0.1f * B2_LINEAR_SLOP >= separationB ) { manifold.normal = normalA; @@ -1386,14 +1388,19 @@ b2Manifold b2CollideChainSegmentAndPolygon( const b2ChainSegment* segmentA, b2Tr manifold = b2ClipSegments( b1, b2, p1, p2, normalB, radiusB, 0.0f, B2_MAKE_ID( ib1, 1 ), B2_MAKE_ID( ib2, 0 ) ); - manifold.normal = b2RotateVector( xfA.q, b2Neg( normalB ) ); - manifold.points[0].anchorA = b2RotateVector( xfA.q, manifold.points[0].anchorA ); - manifold.points[1].anchorA = b2RotateVector( xfA.q, manifold.points[1].anchorA ); - b2Vec2 pAB = b2Sub( xfA.p, xfB.p ); - manifold.points[0].anchorB = b2Add( manifold.points[0].anchorA, pAB ); - manifold.points[1].anchorB = b2Add( manifold.points[1].anchorA, pAB ); - manifold.points[0].point = b2Add( xfA.p, manifold.points[0].anchorA ); - manifold.points[1].point = b2Add( xfA.p, manifold.points[1].anchorA ); + + B2_ASSERT( manifold.pointCount == 0 || manifold.pointCount == 2 ); + if ( manifold.pointCount == 2 ) + { + manifold.normal = b2RotateVector( xfA.q, b2Neg( normalB ) ); + manifold.points[0].anchorA = b2RotateVector( xfA.q, manifold.points[0].anchorA ); + manifold.points[1].anchorA = b2RotateVector( xfA.q, manifold.points[1].anchorA ); + b2Vec2 pAB = b2Sub( xfA.p, xfB.p ); + manifold.points[0].anchorB = b2Add( manifold.points[0].anchorA, pAB ); + manifold.points[1].anchorB = b2Add( manifold.points[1].anchorA, pAB ); + manifold.points[0].point = b2Add( xfA.p, manifold.points[0].anchorA ); + manifold.points[1].point = b2Add( xfA.p, manifold.points[1].anchorA ); + } return manifold; } @@ -1530,14 +1537,21 @@ b2Manifold b2CollideChainSegmentAndPolygon( const b2ChainSegment* segmentA, b2Tr } manifold = b2ClipSegments( a1, a2, p1, p2, normals[ia1], radiusB, 0.0f, B2_MAKE_ID( ia1, 1 ), B2_MAKE_ID( ia2, 0 ) ); - manifold.normal = b2RotateVector( xfA.q, b2Neg( normals[ia1] ) ); - manifold.points[0].anchorA = b2RotateVector( xfA.q, manifold.points[0].anchorA ); - manifold.points[1].anchorA = b2RotateVector( xfA.q, manifold.points[1].anchorA ); - b2Vec2 pAB = b2Sub( xfA.p, xfB.p ); - manifold.points[0].anchorB = b2Add( manifold.points[0].anchorA, pAB ); - manifold.points[1].anchorB = b2Add( manifold.points[1].anchorA, pAB ); - manifold.points[0].point = b2Add( xfA.p, manifold.points[0].anchorA ); - manifold.points[1].point = b2Add( xfA.p, manifold.points[1].anchorA ); + + B2_ASSERT( manifold.pointCount == 0 || manifold.pointCount == 2 ); + if ( manifold.pointCount == 2 ) + { + + manifold.normal = b2RotateVector( xfA.q, b2Neg( normals[ia1] ) ); + manifold.points[0].anchorA = b2RotateVector( xfA.q, manifold.points[0].anchorA ); + manifold.points[1].anchorA = b2RotateVector( xfA.q, manifold.points[1].anchorA ); + b2Vec2 pAB = b2Sub( xfA.p, xfB.p ); + manifold.points[0].anchorB = b2Add( manifold.points[0].anchorA, pAB ); + manifold.points[1].anchorB = b2Add( manifold.points[1].anchorA, pAB ); + manifold.points[0].point = b2Add( xfA.p, manifold.points[0].anchorA ); + manifold.points[1].point = b2Add( xfA.p, manifold.points[1].anchorA ); + } + return manifold; } @@ -1586,14 +1600,20 @@ b2Manifold b2CollideChainSegmentAndPolygon( const b2ChainSegment* segmentA, b2Tr } manifold = b2ClipSegments( p1, p2, b1, b2, normal1, 0.0f, radiusB, B2_MAKE_ID( 0, ib2 ), B2_MAKE_ID( 1, ib1 ) ); - manifold.normal = b2RotateVector( xfA.q, manifold.normal ); - manifold.points[0].anchorA = b2RotateVector( xfA.q, manifold.points[0].anchorA ); - manifold.points[1].anchorA = b2RotateVector( xfA.q, manifold.points[1].anchorA ); - b2Vec2 pAB = b2Sub( xfA.p, xfB.p ); - manifold.points[0].anchorB = b2Add( manifold.points[0].anchorA, pAB ); - manifold.points[1].anchorB = b2Add( manifold.points[1].anchorA, pAB ); - manifold.points[0].point = b2Add( xfA.p, manifold.points[0].anchorA ); - manifold.points[1].point = b2Add( xfA.p, manifold.points[1].anchorA ); + + B2_ASSERT( manifold.pointCount == 0 || manifold.pointCount == 2 ); + if ( manifold.pointCount == 2 ) + { + // There may be no points c + manifold.normal = b2RotateVector( xfA.q, manifold.normal ); + manifold.points[0].anchorA = b2RotateVector( xfA.q, manifold.points[0].anchorA ); + manifold.points[1].anchorA = b2RotateVector( xfA.q, manifold.points[1].anchorA ); + b2Vec2 pAB = b2Sub( xfA.p, xfB.p ); + manifold.points[0].anchorB = b2Add( manifold.points[0].anchorA, pAB ); + manifold.points[1].anchorB = b2Add( manifold.points[1].anchorA, pAB ); + manifold.points[0].point = b2Add( xfA.p, manifold.points[0].anchorA ); + manifold.points[1].point = b2Add( xfA.p, manifold.points[1].anchorA ); + } return manifold; } diff --git a/src/prismatic_joint.c b/src/prismatic_joint.c index 8e604c40b..4043c166c 100644 --- a/src/prismatic_joint.c +++ b/src/prismatic_joint.c @@ -633,22 +633,22 @@ void b2DrawPrismaticJoint( b2DebugDraw* draw, b2JointSim* base, b2Transform tran b2HexColor c4 = b2_colorBlue; b2HexColor c5 = b2_colorDimGray; - draw->DrawSegment( pA, pB, c5, draw->context ); + draw->DrawSegmentFcn( pA, pB, c5, draw->context ); if ( joint->enableLimit ) { b2Vec2 lower = b2MulAdd( pA, joint->lowerTranslation, axis ); b2Vec2 upper = b2MulAdd( pA, joint->upperTranslation, axis ); b2Vec2 perp = b2LeftPerp( axis ); - draw->DrawSegment( lower, upper, c1, draw->context ); - draw->DrawSegment( b2MulSub( lower, 0.1f, perp ), b2MulAdd( lower, 0.1f, perp ), c2, draw->context ); - draw->DrawSegment( b2MulSub( upper, 0.1f, perp ), b2MulAdd( upper, 0.1f, perp ), c3, draw->context ); + draw->DrawSegmentFcn( lower, upper, c1, draw->context ); + draw->DrawSegmentFcn( b2MulSub( lower, 0.1f, perp ), b2MulAdd( lower, 0.1f, perp ), c2, draw->context ); + draw->DrawSegmentFcn( b2MulSub( upper, 0.1f, perp ), b2MulAdd( upper, 0.1f, perp ), c3, draw->context ); } else { - draw->DrawSegment( b2MulSub( pA, 1.0f, axis ), b2MulAdd( pA, 1.0f, axis ), c1, draw->context ); + draw->DrawSegmentFcn( b2MulSub( pA, 1.0f, axis ), b2MulAdd( pA, 1.0f, axis ), c1, draw->context ); } - draw->DrawPoint( pA, 5.0f, c1, draw->context ); - draw->DrawPoint( pB, 5.0f, c4, draw->context ); + draw->DrawPointFcn( pA, 5.0f, c1, draw->context ); + draw->DrawPointFcn( pB, 5.0f, c4, draw->context ); } diff --git a/src/revolute_joint.c b/src/revolute_joint.c index cfcb4b4ff..a8edeb0cf 100644 --- a/src/revolute_joint.c +++ b/src/revolute_joint.c @@ -183,23 +183,6 @@ float b2GetRevoluteJointTorque( b2World* world, b2JointSim* base ) // J = [0 0 -1 0 0 1] // K = invI1 + invI2 -// Body State -// The solver operates on the body state. The body state array does not hold static bodies. Static bodies are shared -// across worker threads. It would be okay to read their states, but writing to them would cause cache thrashing across -// workers, even if the values don't change. -// This causes some trouble when computing anchors. I rotate the anchors using the body rotation every sub-step. For static -// bodies the anchor doesn't rotate. Body A or B could be static and this can lead to lots of branching. This branching -// should be minimized. -// -// Solution 1: -// Use delta rotations. This means anchors need to be prepared in world space. The delta rotation for static bodies will be -// identity. Base separation and angles need to be computed. Manifolds will be behind a frame, but that is probably best if bodies -// move fast. -// -// Solution 2: -// Use full rotation. The anchors for static bodies will be in world space while the anchors for dynamic bodies will be in local -// space. Potentially confusing and bug prone. - void b2PrepareRevoluteJoint( b2JointSim* base, b2StepContext* context ) { B2_ASSERT( base->type == b2_revoluteJoint ); @@ -367,9 +350,9 @@ void b2SolveRevoluteJoint( b2JointSim* base, b2StepContext* context, bool useBia } float Cdot = wB - wA; - float impulse = -massScale * joint->axialMass * ( Cdot + bias ) - impulseScale * joint->lowerImpulse; float oldImpulse = joint->lowerImpulse; - joint->lowerImpulse = b2MaxFloat( joint->lowerImpulse + impulse, 0.0f ); + float impulse = -massScale * joint->axialMass * ( Cdot + bias ) - impulseScale * oldImpulse; + joint->lowerImpulse = b2MaxFloat( oldImpulse + impulse, 0.0f ); impulse = joint->lowerImpulse - oldImpulse; wA -= iA * impulse; @@ -398,9 +381,9 @@ void b2SolveRevoluteJoint( b2JointSim* base, b2StepContext* context, bool useBia // sign flipped on Cdot float Cdot = wA - wB; - float impulse = -massScale * joint->axialMass * ( Cdot + bias ) - impulseScale * joint->lowerImpulse; float oldImpulse = joint->upperImpulse; - joint->upperImpulse = b2MaxFloat( joint->upperImpulse + impulse, 0.0f ); + float impulse = -massScale * joint->axialMass * ( Cdot + bias ) - impulseScale * oldImpulse; + joint->upperImpulse = b2MaxFloat( oldImpulse + impulse, 0.0f ); impulse = joint->upperImpulse - oldImpulse; // sign flipped on applied impulse @@ -500,21 +483,21 @@ void b2DrawRevoluteJoint( b2DebugDraw* draw, b2JointSim* base, b2Transform trans const float L = drawSize; // draw->drawPoint(pA, 3.0f, b2_colorGray40, draw->context); // draw->drawPoint(pB, 3.0f, b2_colorLightBlue, draw->context); - draw->DrawCircle( pB, L, c1, draw->context ); + draw->DrawCircleFcn( pB, L, c1, draw->context ); float angle = b2RelativeAngle( transformB.q, transformA.q ); b2Rot rot = b2MakeRot( angle ); b2Vec2 r = { L * rot.c, L * rot.s }; b2Vec2 pC = b2Add( pB, r ); - draw->DrawSegment( pB, pC, c1, draw->context ); + draw->DrawSegmentFcn( pB, pC, c1, draw->context ); if ( draw->drawJointExtras ) { float jointAngle = b2UnwindAngle( angle - joint->referenceAngle ); char buffer[32]; snprintf( buffer, 32, " %.1f deg", 180.0f * jointAngle / B2_PI ); - draw->DrawString( pC, buffer, b2_colorWhite, draw->context ); + draw->DrawStringFcn( pC, buffer, b2_colorWhite, draw->context ); } float lowerAngle = joint->lowerAngle + joint->referenceAngle; @@ -528,18 +511,18 @@ void b2DrawRevoluteJoint( b2DebugDraw* draw, b2JointSim* base, b2Transform trans b2Rot rotHi = b2MakeRot( upperAngle ); b2Vec2 rhi = { L * rotHi.c, L * rotHi.s }; - draw->DrawSegment( pB, b2Add( pB, rlo ), c2, draw->context ); - draw->DrawSegment( pB, b2Add( pB, rhi ), c3, draw->context ); + draw->DrawSegmentFcn( pB, b2Add( pB, rlo ), c2, draw->context ); + draw->DrawSegmentFcn( pB, b2Add( pB, rhi ), c3, draw->context ); b2Rot rotRef = b2MakeRot( joint->referenceAngle ); b2Vec2 ref = ( b2Vec2 ){ L * rotRef.c, L * rotRef.s }; - draw->DrawSegment( pB, b2Add( pB, ref ), b2_colorBlue, draw->context ); + draw->DrawSegmentFcn( pB, b2Add( pB, ref ), b2_colorBlue, draw->context ); } b2HexColor color = b2_colorGold; - draw->DrawSegment( transformA.p, pA, color, draw->context ); - draw->DrawSegment( pA, pB, color, draw->context ); - draw->DrawSegment( transformB.p, pB, color, draw->context ); + draw->DrawSegmentFcn( transformA.p, pA, color, draw->context ); + draw->DrawSegmentFcn( pA, pB, color, draw->context ); + draw->DrawSegmentFcn( transformB.p, pB, color, draw->context ); // char buffer[32]; // sprintf(buffer, "%.1f", b2Length(joint->impulse)); diff --git a/src/sensor.c b/src/sensor.c index 8b66b6f6e..107790d5b 100644 --- a/src/sensor.c +++ b/src/sensor.c @@ -34,7 +34,7 @@ struct b2SensorQueryContext // - maintain an active list of overlaps for query // Assumption -// - sensors don't detect other sensors +// - sensors don't detect shapes on the same body // Algorithm // Query all sensors for overlaps @@ -60,8 +60,14 @@ static bool b2SensorQueryCallback( int proxyId, int shapeId, void* context ) b2World* world = queryContext->world; b2Shape* otherShape = b2ShapeArray_Get( &world->shapes, shapeId ); - // Sensors don't overlap with other sensors - if ( otherShape->sensorIndex != B2_NULL_INDEX ) + // Are sensor events enabled on the other shape? + if ( otherShape->enableSensorEvents == false ) + { + return true; + } + + // Skip shapes on the same body + if ( otherShape->bodyId == sensorShape->bodyId ) { return true; } @@ -146,7 +152,17 @@ static void b2SensorTask( int startIndex, int endIndex, uint32_t threadIndex, vo sensor->overlaps2 = temp; b2ShapeRefArray_Clear( &sensor->overlaps2 ); - b2Transform transform = b2GetBodyTransform( world, sensorShape->bodyId ); + b2Body* body = b2BodyArray_Get( &world->bodies, sensorShape->bodyId ); + if ( body->setIndex == b2_disabledSet || sensorShape->enableSensorEvents == false ) + { + if ( sensor->overlaps1.count != 0 ) + { + b2SetBit( &taskContext->eventBits, sensorIndex ); + } + continue; + } + + b2Transform transform = b2GetBodyTransformQuick( world, body ); struct b2SensorQueryContext queryContext = { .world = world, @@ -263,7 +279,10 @@ void b2OverlapSensors( b2World* world ) { // end b2ShapeId visitorId = { r1->shapeId + 1, world->worldId, r1->generation }; - b2SensorEndTouchEvent event = { sensorId, visitorId }; + b2SensorEndTouchEvent event = { + .sensorShapeId = sensorId, + .visitorShapeId = visitorId, + }; b2SensorEndTouchEventArray_Push( &world->sensorEndEvents[world->endEventArrayIndex], event ); index1 += 1; } diff --git a/src/shape.c b/src/shape.c index a3bad3f8a..48b47f555 100644 --- a/src/shape.c +++ b/src/shape.c @@ -116,6 +116,7 @@ static b2Shape* b2CreateShapeInternal( b2World* world, b2Body* body, b2Transform shape->userData = def->userData; shape->customColor = def->customColor; shape->enlargedAABB = false; + shape->enableSensorEvents = def->enableSensorEvents; shape->enableContactEvents = def->enableContactEvents; shape->enableHitEvents = def->enableHitEvents; shape->enablePreSolveEvents = def->enablePreSolveEvents; @@ -391,6 +392,7 @@ b2ChainId b2CreateChain( b2BodyId bodyId, const b2ChainDef* def ) b2ShapeDef shapeDef = b2DefaultShapeDef(); shapeDef.userData = def->userData; shapeDef.filter = def->filter; + shapeDef.enableSensorEvents = def->enableSensorEvents; shapeDef.enableContactEvents = false; shapeDef.enableHitEvents = false; @@ -1221,6 +1223,25 @@ void b2Shape_SetFilter( b2ShapeId shapeId, b2Filter filter ) // overlaps are updated the next time step } +void b2Shape_EnableSensorEvents( b2ShapeId shapeId, bool flag ) +{ + b2World* world = b2GetWorldLocked( shapeId.world0 ); + if ( world == NULL ) + { + return; + } + + b2Shape* shape = b2GetShape( world, shapeId ); + shape->enableSensorEvents = flag; +} + +bool b2Shape_AreSensorEventsEnabled( b2ShapeId shapeId ) +{ + b2World* world = b2GetWorld( shapeId.world0 ); + b2Shape* shape = b2GetShape( world, shapeId ); + return shape->enableSensorEvents; +} + void b2Shape_EnableContactEvents( b2ShapeId shapeId, bool flag ) { b2World* world = b2GetWorldLocked( shapeId.world0 ); diff --git a/src/shape.h b/src/shape.h index 6d986f1dc..600daf5f5 100644 --- a/src/shape.h +++ b/src/shape.h @@ -44,6 +44,7 @@ typedef struct b2Shape }; uint16_t generation; + bool enableSensorEvents; bool enableContactEvents; bool enableHitEvents; bool enablePreSolveEvents; diff --git a/src/solver.c b/src/solver.c index 587f1cc98..e44a88df3 100644 --- a/src/solver.c +++ b/src/solver.c @@ -346,7 +346,9 @@ static bool b2ContinuousQueryCallback( int proxyId, int shapeId, void* context ) { // fallback to TOI of a small circle around the fast shape centroid b2Vec2 centroid = b2GetShapeCentroid( fastShape ); - input.proxyB = b2MakeProxy( ¢roid, 1, B2_SPECULATIVE_DISTANCE ); + b2ShapeExtent extent = b2ComputeShapeExtent( fastShape, centroid ); + float radius = 0.25f * extent.minExtent; + input.proxyB = b2MakeProxy( ¢roid, 1, radius ); output = b2TimeOfImpact( &input ); if ( 0.0f < output.fraction && output.fraction < continuousContext->fraction ) { @@ -458,6 +460,10 @@ static void b2SolveContinuous( b2World* world, int bodySimIndex ) fastBodySim->rotation0 = q; fastBodySim->center0 = c; + // Update body move event + b2BodyMoveEvent* event = b2BodyMoveEventArray_Get( &world->bodyMoveEvents, bodySimIndex ); + event->transform = transform; + // Prepare AABBs for broad-phase. // Even though a body is fast, it may not move much. So the // AABB may not need enlargement. @@ -1206,7 +1212,7 @@ void b2Solve( b2World* world, b2StepContext* stepContext ) { // Prepare buffers for bullets b2AtomicStoreInt(&stepContext->bulletBodyCount, 0); - stepContext->bulletBodies = b2AllocateArenaItem( &world->stackAllocator, awakeBodyCount * sizeof( int ), "bullet bodies" ); + stepContext->bulletBodies = b2AllocateArenaItem( &world->arena, awakeBodyCount * sizeof( int ), "bullet bodies" ); b2TracyCZoneNC( prepare_stages, "Prepare Stages", b2_colorDarkOrange, true ); uint64_t prepareTicks = b2GetTicks(); @@ -1339,19 +1345,19 @@ void b2Solve( b2World* world, b2StepContext* stepContext ) // Gather contact pointers for easy parallel-for traversal. Some may be NULL due to SIMD remainders. b2ContactSim** contacts = b2AllocateArenaItem( - &world->stackAllocator, B2_SIMD_WIDTH * simdContactCount * sizeof( b2ContactSim* ), "contact pointers" ); + &world->arena, B2_SIMD_WIDTH * simdContactCount * sizeof( b2ContactSim* ), "contact pointers" ); // Gather joint pointers for easy parallel-for traversal. b2JointSim** joints = - b2AllocateArenaItem( &world->stackAllocator, awakeJointCount * sizeof( b2JointSim* ), "joint pointers" ); + b2AllocateArenaItem( &world->arena, awakeJointCount * sizeof( b2JointSim* ), "joint pointers" ); int simdConstraintSize = b2GetContactConstraintSIMDByteCount(); b2ContactConstraintSIMD* simdContactConstraints = - b2AllocateArenaItem( &world->stackAllocator, simdContactCount * simdConstraintSize, "contact constraint" ); + b2AllocateArenaItem( &world->arena, simdContactCount * simdConstraintSize, "contact constraint" ); int overflowContactCount = colors[B2_OVERFLOW_INDEX].contactSims.count; b2ContactConstraint* overflowContactConstraints = b2AllocateArenaItem( - &world->stackAllocator, overflowContactCount * sizeof( b2ContactConstraint ), "overflow contact constraint" ); + &world->arena, overflowContactCount * sizeof( b2ContactConstraint ), "overflow contact constraint" ); graph->colors[B2_OVERFLOW_INDEX].overflowConstraints = overflowContactConstraints; @@ -1443,15 +1449,15 @@ void b2Solve( b2World* world, b2StepContext* stepContext ) // b2_stageStoreImpulses stageCount += 1; - b2SolverStage* stages = b2AllocateArenaItem( &world->stackAllocator, stageCount * sizeof( b2SolverStage ), "stages" ); + b2SolverStage* stages = b2AllocateArenaItem( &world->arena, stageCount * sizeof( b2SolverStage ), "stages" ); b2SolverBlock* bodyBlocks = - b2AllocateArenaItem( &world->stackAllocator, bodyBlockCount * sizeof( b2SolverBlock ), "body blocks" ); + b2AllocateArenaItem( &world->arena, bodyBlockCount * sizeof( b2SolverBlock ), "body blocks" ); b2SolverBlock* contactBlocks = - b2AllocateArenaItem( &world->stackAllocator, contactBlockCount * sizeof( b2SolverBlock ), "contact blocks" ); + b2AllocateArenaItem( &world->arena, contactBlockCount * sizeof( b2SolverBlock ), "contact blocks" ); b2SolverBlock* jointBlocks = - b2AllocateArenaItem( &world->stackAllocator, jointBlockCount * sizeof( b2SolverBlock ), "joint blocks" ); + b2AllocateArenaItem( &world->arena, jointBlockCount * sizeof( b2SolverBlock ), "joint blocks" ); b2SolverBlock* graphBlocks = - b2AllocateArenaItem( &world->stackAllocator, graphBlockCount * sizeof( b2SolverBlock ), "graph blocks" ); + b2AllocateArenaItem( &world->arena, graphBlockCount * sizeof( b2SolverBlock ), "graph blocks" ); // Split an awake island. This modifies: // - stack allocator @@ -1718,15 +1724,15 @@ void b2Solve( b2World* world, b2StepContext* stepContext ) world->finishTaskFcn( finalizeBodiesTask, world->userTaskContext ); } - b2FreeArenaItem( &world->stackAllocator, graphBlocks ); - b2FreeArenaItem( &world->stackAllocator, jointBlocks ); - b2FreeArenaItem( &world->stackAllocator, contactBlocks ); - b2FreeArenaItem( &world->stackAllocator, bodyBlocks ); - b2FreeArenaItem( &world->stackAllocator, stages ); - b2FreeArenaItem( &world->stackAllocator, overflowContactConstraints ); - b2FreeArenaItem( &world->stackAllocator, simdContactConstraints ); - b2FreeArenaItem( &world->stackAllocator, joints ); - b2FreeArenaItem( &world->stackAllocator, contacts ); + b2FreeArenaItem( &world->arena, graphBlocks ); + b2FreeArenaItem( &world->arena, jointBlocks ); + b2FreeArenaItem( &world->arena, contactBlocks ); + b2FreeArenaItem( &world->arena, bodyBlocks ); + b2FreeArenaItem( &world->arena, stages ); + b2FreeArenaItem( &world->arena, overflowContactConstraints ); + b2FreeArenaItem( &world->arena, simdContactConstraints ); + b2FreeArenaItem( &world->arena, joints ); + b2FreeArenaItem( &world->arena, contacts ); world->profile.transforms = b2GetMilliseconds( transformTicks ); b2TracyCZoneEnd( update_transforms ); @@ -1766,8 +1772,8 @@ void b2Solve( b2World* world, b2StepContext* stepContext ) b2ManifoldPoint* mp = contactSim->manifold.points + k; float approachSpeed = -mp->normalVelocity; - // Need to check max impulse because the point may be speculative and not colliding - if ( approachSpeed > event.approachSpeed && mp->maxNormalImpulse > 0.0f ) + // Need to check total impulse because the point may be speculative and not colliding + if ( approachSpeed > event.approachSpeed && mp->totalNormalImpulse > 0.0f ) { event.approachSpeed = approachSpeed; event.point = mp->point; @@ -1790,7 +1796,7 @@ void b2Solve( b2World* world, b2StepContext* stepContext ) } } - world->profile.hitEvents = b2GetMillisecondsAndReset( &hitTicks ); + world->profile.hitEvents = b2GetMilliseconds( hitTicks ); b2TracyCZoneEnd( hit_events ); } @@ -1965,7 +1971,7 @@ void b2Solve( b2World* world, b2StepContext* stepContext ) } // Need to free this even if no bullets got processed. - b2FreeArenaItem( &world->stackAllocator, stepContext->bulletBodies ); + b2FreeArenaItem( &world->arena, stepContext->bulletBodies ); stepContext->bulletBodies = NULL; b2AtomicStoreInt(&stepContext->bulletBodyCount, 0); diff --git a/src/timer.c b/src/timer.c index 503ac0b36..3af27ce31 100644 --- a/src/timer.c +++ b/src/timer.c @@ -5,7 +5,7 @@ #include -#if defined( _WIN32 ) +#if defined( _MSC_VER ) #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN diff --git a/src/types.c b/src/types.c index 6e56d4ca5..0e3c4056a 100644 --- a/src/types.c +++ b/src/types.c @@ -13,7 +13,7 @@ b2WorldDef b2DefaultWorldDef( void ) def.gravity.y = -10.0f; def.hitEventThreshold = 1.0f * b2_lengthUnitsPerMeter; def.restitutionThreshold = 1.0f * b2_lengthUnitsPerMeter; - def.contactPushMaxSpeed = 3.0f * b2_lengthUnitsPerMeter; + def.maxContactPushSpeed = 3.0f * b2_lengthUnitsPerMeter; def.contactHertz = 30.0; def.contactDampingRatio = 10.0f; def.jointHertz = 60.0; @@ -137,14 +137,14 @@ b2DebugDraw b2DefaultDebugDraw( void ) b2DebugDraw draw = { 0 }; // These allow the user to skip some implementations and not hit null exceptions. - draw.DrawPolygon = b2EmptyDrawPolygon; - draw.DrawSolidPolygon = b2EmptyDrawSolidPolygon; - draw.DrawCircle = b2EmptyDrawCircle; - draw.DrawSolidCircle = b2EmptyDrawSolidCircle; - draw.DrawSolidCapsule = b2EmptyDrawSolidCapsule; - draw.DrawSegment = b2EmptyDrawSegment; - draw.DrawTransform = b2EmptyDrawTransform; - draw.DrawPoint = b2EmptyDrawPoint; - draw.DrawString = b2EmptyDrawString; + draw.DrawPolygonFcn = b2EmptyDrawPolygon; + draw.DrawSolidPolygonFcn = b2EmptyDrawSolidPolygon; + draw.DrawCircleFcn = b2EmptyDrawCircle; + draw.DrawSolidCircleFcn = b2EmptyDrawSolidCircle; + draw.DrawSolidCapsuleFcn = b2EmptyDrawSolidCapsule; + draw.DrawSegmentFcn = b2EmptyDrawSegment; + draw.DrawTransformFcn = b2EmptyDrawTransform; + draw.DrawPointFcn = b2EmptyDrawPoint; + draw.DrawStringFcn = b2EmptyDrawString; return draw; } diff --git a/src/wheel_joint.c b/src/wheel_joint.c index 9304cfc0c..2201533a9 100644 --- a/src/wheel_joint.c +++ b/src/wheel_joint.c @@ -528,22 +528,22 @@ void b2DrawWheelJoint( b2DebugDraw* draw, b2JointSim* base, b2Transform transfor b2HexColor c4 = b2_colorDimGray; b2HexColor c5 = b2_colorBlue; - draw->DrawSegment( pA, pB, c5, draw->context ); + draw->DrawSegmentFcn( pA, pB, c5, draw->context ); if ( joint->enableLimit ) { b2Vec2 lower = b2MulAdd( pA, joint->lowerTranslation, axis ); b2Vec2 upper = b2MulAdd( pA, joint->upperTranslation, axis ); b2Vec2 perp = b2LeftPerp( axis ); - draw->DrawSegment( lower, upper, c1, draw->context ); - draw->DrawSegment( b2MulSub( lower, 0.1f, perp ), b2MulAdd( lower, 0.1f, perp ), c2, draw->context ); - draw->DrawSegment( b2MulSub( upper, 0.1f, perp ), b2MulAdd( upper, 0.1f, perp ), c3, draw->context ); + draw->DrawSegmentFcn( lower, upper, c1, draw->context ); + draw->DrawSegmentFcn( b2MulSub( lower, 0.1f, perp ), b2MulAdd( lower, 0.1f, perp ), c2, draw->context ); + draw->DrawSegmentFcn( b2MulSub( upper, 0.1f, perp ), b2MulAdd( upper, 0.1f, perp ), c3, draw->context ); } else { - draw->DrawSegment( b2MulSub( pA, 1.0f, axis ), b2MulAdd( pA, 1.0f, axis ), c1, draw->context ); + draw->DrawSegmentFcn( b2MulSub( pA, 1.0f, axis ), b2MulAdd( pA, 1.0f, axis ), c1, draw->context ); } - draw->DrawPoint( pA, 5.0f, c1, draw->context ); - draw->DrawPoint( pB, 5.0f, c4, draw->context ); + draw->DrawPointFcn( pA, 5.0f, c1, draw->context ); + draw->DrawPointFcn( pB, 5.0f, c4, draw->context ); } diff --git a/src/world.c b/src/world.c index 7d8ad1e93..90d157015 100644 --- a/src/world.c +++ b/src/world.c @@ -32,6 +32,7 @@ #include _Static_assert( B2_MAX_WORLDS > 0, "must be 1 or more" ); +_Static_assert( B2_MAX_WORLDS < UINT16_MAX, "B3_MAX_WORLDS limit exceeded" ); b2World b2_worlds[B2_MAX_WORLDS]; B2_ARRAY_SOURCE( b2BodyMoveEvent, b2BodyMoveEvent ); @@ -128,7 +129,7 @@ b2WorldId b2CreateWorld( const b2WorldDef* def ) world->generation = generation; world->inUse = true; - world->stackAllocator = b2CreateArenaAllocator( 2048 ); + world->arena = b2CreateArenaAllocator( 2048 ); b2CreateBroadPhase( &world->broadPhase ); b2CreateGraph( &world->constraintGraph, 16 ); @@ -191,7 +192,7 @@ b2WorldId b2CreateWorld( const b2WorldDef* def ) world->hitEventThreshold = def->hitEventThreshold; world->restitutionThreshold = def->restitutionThreshold; world->maxLinearSpeed = def->maximumLinearSpeed; - world->contactMaxPushSpeed = def->contactPushMaxSpeed; + world->maxContactPushSpeed = def->maxContactPushSpeed; world->contactHertz = def->contactHertz; world->contactDampingRatio = def->contactDampingRatio; world->jointHertz = def->jointHertz; @@ -256,6 +257,7 @@ b2WorldId b2CreateWorld( const b2WorldDef* def ) world->debugBodySet = b2CreateBitSet( 256 ); world->debugJointSet = b2CreateBitSet( 256 ); world->debugContactSet = b2CreateBitSet( 256 ); + world->debugIslandSet = b2CreateBitSet( 256 ); // add one to worldId so that 0 represents a null b2WorldId return ( b2WorldId ){ (uint16_t)( worldId + 1 ), world->generation }; @@ -268,6 +270,7 @@ void b2DestroyWorld( b2WorldId worldId ) b2DestroyBitSet( &world->debugBodySet ); b2DestroyBitSet( &world->debugJointSet ); b2DestroyBitSet( &world->debugContactSet ); + b2DestroyBitSet( &world->debugIslandSet ); for ( int i = 0; i < world->workerCount; ++i ) { @@ -345,7 +348,7 @@ void b2DestroyWorld( b2WorldId worldId ) b2DestroyIdPool( &world->islandIdPool ); b2DestroyIdPool( &world->solverSetIdPool ); - b2DestroyArenaAllocator( &world->stackAllocator ); + b2DestroyArenaAllocator( &world->arena ); // Wipe world but preserve generation uint16_t generation = world->generation; @@ -368,9 +371,9 @@ static void b2CollideTask( int startIndex, int endIndex, uint32_t threadIndex, v B2_ASSERT( startIndex < endIndex ); - for ( int i = startIndex; i < endIndex; ++i ) + for ( int contactIndex = startIndex; contactIndex < endIndex; ++contactIndex ) { - b2ContactSim* contactSim = contactSims[i]; + b2ContactSim* contactSim = contactSims[contactIndex]; int contactId = contactSim->contactId; @@ -425,6 +428,19 @@ static void b2CollideTask( int startIndex, int endIndex, uint32_t threadIndex, v contactSim->simFlags |= b2_simStoppedTouching; b2SetBit( &taskContext->contactStateBitSet, contactId ); } + + // To make this work, the time of impact code needs to adjust the target + // distance based on the number of TOI events for a body. + // if (touching && bodySimB->isFast) + //{ + // b2Manifold* manifold = &contactSim->manifold; + // int pointCount = manifold->pointCount; + // for (int i = 0; i < pointCount; ++i) + // { + // // trick the solver into pushing the fast shapes apart + // manifold->points[i].separation -= 0.25f * B2_SPECULATIVE_DISTANCE; + // } + //} } } @@ -505,7 +521,7 @@ static void b2Collide( b2StepContext* context ) } b2ContactSim** contactSims = - b2AllocateArenaItem( &world->stackAllocator, contactCount * sizeof( b2ContactSim* ), "contacts" ); + b2AllocateArenaItem( &world->arena, contactCount * sizeof( b2ContactSim* ), "contacts" ); int contactIndex = 0; for ( int i = 0; i < B2_GRAPH_COLOR_COUNT; ++i ) @@ -549,7 +565,7 @@ static void b2Collide( b2StepContext* context ) world->finishTaskFcn( userCollideTask, world->userTaskContext ); } - b2FreeArenaItem( &world->stackAllocator, contactSims ); + b2FreeArenaItem( &world->arena, contactSims ); context->contacts = NULL; contactSims = NULL; @@ -684,6 +700,9 @@ static void b2Collide( b2StepContext* context ) void b2World_Step( b2WorldId worldId, float timeStep, int subStepCount ) { + B2_ASSERT( b2IsValidFloat( timeStep ) ); + B2_ASSERT( 0 < subStepCount ); + b2World* world = b2GetWorldFromId( worldId ); B2_ASSERT( world->locked == false ); if ( world->locked ) @@ -782,10 +801,10 @@ void b2World_Step( b2WorldId worldId, float timeStep, int subStepCount ) world->profile.step = b2GetMilliseconds( stepTicks ); - B2_ASSERT( b2GetArenaAllocation( &world->stackAllocator ) == 0 ); + B2_ASSERT( b2GetArenaAllocation( &world->arena ) == 0 ); // Ensure stack is large enough - b2GrowArena( &world->stackAllocator ); + b2GrowArena( &world->arena ); // Make sure all tasks that were started were also finished B2_ASSERT( world->activeTaskCount == 0 ); @@ -808,7 +827,7 @@ static void b2DrawShape( b2DebugDraw* draw, b2Shape* shape, b2Transform xf, b2He b2Capsule* capsule = &shape->capsule; b2Vec2 p1 = b2TransformPoint( xf, capsule->center1 ); b2Vec2 p2 = b2TransformPoint( xf, capsule->center2 ); - draw->DrawSolidCapsule( p1, p2, capsule->radius, color, draw->context ); + draw->DrawSolidCapsuleFcn( p1, p2, capsule->radius, color, draw->context ); } break; @@ -816,14 +835,14 @@ static void b2DrawShape( b2DebugDraw* draw, b2Shape* shape, b2Transform xf, b2He { b2Circle* circle = &shape->circle; xf.p = b2TransformPoint( xf, circle->center ); - draw->DrawSolidCircle( xf, circle->radius, color, draw->context ); + draw->DrawSolidCircleFcn( xf, circle->radius, color, draw->context ); } break; case b2_polygonShape: { b2Polygon* poly = &shape->polygon; - draw->DrawSolidPolygon( xf, poly->vertices, poly->count, poly->radius, color, draw->context ); + draw->DrawSolidPolygonFcn( xf, poly->vertices, poly->count, poly->radius, color, draw->context ); } break; @@ -832,7 +851,7 @@ static void b2DrawShape( b2DebugDraw* draw, b2Shape* shape, b2Transform xf, b2He b2Segment* segment = &shape->segment; b2Vec2 p1 = b2TransformPoint( xf, segment->point1 ); b2Vec2 p2 = b2TransformPoint( xf, segment->point2 ); - draw->DrawSegment( p1, p2, color, draw->context ); + draw->DrawSegmentFcn( p1, p2, color, draw->context ); } break; @@ -841,9 +860,9 @@ static void b2DrawShape( b2DebugDraw* draw, b2Shape* shape, b2Transform xf, b2He b2Segment* segment = &shape->chainSegment.segment; b2Vec2 p1 = b2TransformPoint( xf, segment->point1 ); b2Vec2 p2 = b2TransformPoint( xf, segment->point2 ); - draw->DrawSegment( p1, p2, color, draw->context ); - draw->DrawPoint( p2, 4.0f, color, draw->context ); - draw->DrawSegment( p1, b2Lerp( p1, p2, 0.1f ), b2_colorPaleGreen, draw->context ); + draw->DrawSegmentFcn( p1, p2, color, draw->context ); + draw->DrawPointFcn( p2, 4.0f, color, draw->context ); + draw->DrawSegmentFcn( p1, b2Lerp( p1, p2, 0.1f ), b2_colorPaleGreen, draw->context ); } break; @@ -927,7 +946,7 @@ static bool DrawQueryCallback( int proxyId, int shapeId, void* context ) b2DrawShape( draw, shape, bodySim->transform, color ); } - if ( draw->drawAABBs ) + if ( draw->drawBounds ) { b2AABB aabb = shape->fatAABB; @@ -936,7 +955,7 @@ static bool DrawQueryCallback( int proxyId, int shapeId, void* context ) { aabb.upperBound.x, aabb.upperBound.y }, { aabb.lowerBound.x, aabb.upperBound.y } }; - draw->DrawPolygon( vs, 4, b2_colorGold, draw->context ); + draw->DrawPolygonFcn( vs, 4, b2_colorGold, draw->context ); } return true; @@ -996,11 +1015,11 @@ static void b2DrawWithBounds( b2World* world, b2DebugDraw* draw ) b2BodySim* bodySim = b2GetBodySim( world, body ); b2Transform transform = { bodySim->center, bodySim->transform.q }; - draw->DrawTransform( transform, draw->context ); + draw->DrawTransformFcn( transform, draw->context ); b2Vec2 p = b2TransformPoint( transform, offset ); - draw->DrawString( p, body->name, b2_colorBlueViolet, draw->context ); + draw->DrawStringFcn( p, body->name, b2_colorBlueViolet, draw->context ); } if ( draw->drawMass && body->type == b2_dynamicBody ) @@ -1009,13 +1028,13 @@ static void b2DrawWithBounds( b2World* world, b2DebugDraw* draw ) b2BodySim* bodySim = b2GetBodySim( world, body ); b2Transform transform = { bodySim->center, bodySim->transform.q }; - draw->DrawTransform( transform, draw->context ); + draw->DrawTransformFcn( transform, draw->context ); b2Vec2 p = b2TransformPoint( transform, offset ); char buffer[32]; snprintf( buffer, 32, " %.2f", body->mass ); - draw->DrawString( p, buffer, b2_colorWhite, draw->context ); + draw->DrawStringFcn( p, buffer, b2_colorWhite, draw->context ); } if ( draw->drawJoints ) @@ -1078,38 +1097,44 @@ static void b2DrawWithBounds( b2World* world, b2DebugDraw* draw ) { // graph color float pointSize = contact->colorIndex == B2_OVERFLOW_INDEX ? 7.5f : 5.0f; - draw->DrawPoint( point->point, pointSize, graphColors[contact->colorIndex], draw->context ); + draw->DrawPointFcn( point->point, pointSize, graphColors[contact->colorIndex], draw->context ); // g_draw.DrawString(point->position, "%d", point->color); } else if ( point->separation > linearSlop ) { // Speculative - draw->DrawPoint( point->point, 5.0f, speculativeColor, draw->context ); + draw->DrawPointFcn( point->point, 5.0f, speculativeColor, draw->context ); } else if ( point->persisted == false ) { // Add - draw->DrawPoint( point->point, 10.0f, addColor, draw->context ); + draw->DrawPointFcn( point->point, 10.0f, addColor, draw->context ); } else if ( point->persisted == true ) { // Persist - draw->DrawPoint( point->point, 5.0f, persistColor, draw->context ); + draw->DrawPointFcn( point->point, 5.0f, persistColor, draw->context ); } if ( draw->drawContactNormals ) { b2Vec2 p1 = point->point; b2Vec2 p2 = b2MulAdd( p1, k_axisScale, normal ); - draw->DrawSegment( p1, p2, normalColor, draw->context ); + draw->DrawSegmentFcn( p1, p2, normalColor, draw->context ); } else if ( draw->drawContactImpulses ) { b2Vec2 p1 = point->point; b2Vec2 p2 = b2MulAdd( p1, k_impulseScale * point->normalImpulse, normal ); - draw->DrawSegment( p1, p2, impulseColor, draw->context ); + draw->DrawSegmentFcn( p1, p2, impulseColor, draw->context ); snprintf( buffer, B2_ARRAY_COUNT( buffer ), "%.1f", 1000.0f * point->normalImpulse ); - draw->DrawString( p1, buffer, b2_colorWhite, draw->context ); + draw->DrawStringFcn( p1, buffer, b2_colorWhite, draw->context ); + } + + if ( draw->drawContactFeatures ) + { + snprintf( buffer, B2_ARRAY_COUNT( buffer ), "%d", point->id ); + draw->DrawStringFcn( point->point, buffer, b2_colorOrange, draw->context ); } if ( draw->drawFrictionImpulses ) @@ -1117,9 +1142,9 @@ static void b2DrawWithBounds( b2World* world, b2DebugDraw* draw ) b2Vec2 tangent = b2RightPerp( normal ); b2Vec2 p1 = point->point; b2Vec2 p2 = b2MulAdd( p1, k_impulseScale * point->tangentImpulse, tangent ); - draw->DrawSegment( p1, p2, frictionColor, draw->context ); + draw->DrawSegmentFcn( p1, p2, frictionColor, draw->context ); snprintf( buffer, B2_ARRAY_COUNT( buffer ), "%.1f", 1000.0f * point->tangentImpulse ); - draw->DrawString( p1, buffer, b2_colorWhite, draw->context ); + draw->DrawStringFcn( p1, buffer, b2_colorWhite, draw->context ); } } @@ -1245,7 +1270,7 @@ void b2World_Draw( b2WorldId worldId, b2DebugDraw* draw ) } } - if ( draw->drawAABBs ) + if ( draw->drawBounds ) { b2HexColor color = b2_colorGold; @@ -1260,7 +1285,7 @@ void b2World_Draw( b2WorldId worldId, b2DebugDraw* draw ) char buffer[32]; snprintf( buffer, 32, "%d", bodySim->bodyId ); - draw->DrawString( bodySim->center, buffer, b2_colorWhite, draw->context ); + draw->DrawStringFcn( bodySim->center, buffer, b2_colorWhite, draw->context ); b2Body* body = b2BodyArray_Get( &world->bodies, bodySim->bodyId ); B2_ASSERT( body->setIndex == setIndex ); @@ -1276,7 +1301,7 @@ void b2World_Draw( b2WorldId worldId, b2DebugDraw* draw ) { aabb.upperBound.x, aabb.upperBound.y }, { aabb.lowerBound.x, aabb.upperBound.y } }; - draw->DrawPolygon( vs, 4, color, draw->context ); + draw->DrawPolygonFcn( vs, 4, color, draw->context ); shapeId = shape->nextShapeId; } @@ -1304,7 +1329,7 @@ void b2World_Draw( b2WorldId worldId, b2DebugDraw* draw ) b2Transform transform = b2GetBodyTransformQuick( world, body ); b2Vec2 p = b2TransformPoint( transform, offset ); - draw->DrawString( p, body->name, b2_colorBlueViolet, draw->context ); + draw->DrawStringFcn( p, body->name, b2_colorBlueViolet, draw->context ); } } @@ -1321,14 +1346,14 @@ void b2World_Draw( b2WorldId worldId, b2DebugDraw* draw ) b2BodySim* bodySim = set->bodySims.data + bodyIndex; b2Transform transform = { bodySim->center, bodySim->transform.q }; - draw->DrawTransform( transform, draw->context ); + draw->DrawTransformFcn( transform, draw->context ); b2Vec2 p = b2TransformPoint( transform, offset ); char buffer[32]; float mass = bodySim->invMass > 0.0f ? 1.0f / bodySim->invMass : 0.0f; snprintf( buffer, 32, " %.2f", mass ); - draw->DrawString( p, buffer, b2_colorWhite, draw->context ); + draw->DrawStringFcn( p, buffer, b2_colorWhite, draw->context ); } } } @@ -1370,38 +1395,44 @@ void b2World_Draw( b2WorldId worldId, b2DebugDraw* draw ) { // graph color float pointSize = colorIndex == B2_OVERFLOW_INDEX ? 7.5f : 5.0f; - draw->DrawPoint( point->point, pointSize, colors[colorIndex], draw->context ); + draw->DrawPointFcn( point->point, pointSize, colors[colorIndex], draw->context ); // g_draw.DrawString(point->position, "%d", point->color); } else if ( point->separation > linearSlop ) { // Speculative - draw->DrawPoint( point->point, 5.0f, speculativeColor, draw->context ); + draw->DrawPointFcn( point->point, 5.0f, speculativeColor, draw->context ); } else if ( point->persisted == false ) { // Add - draw->DrawPoint( point->point, 10.0f, addColor, draw->context ); + draw->DrawPointFcn( point->point, 10.0f, addColor, draw->context ); } else if ( point->persisted == true ) { // Persist - draw->DrawPoint( point->point, 5.0f, persistColor, draw->context ); + draw->DrawPointFcn( point->point, 5.0f, persistColor, draw->context ); } if ( draw->drawContactNormals ) { b2Vec2 p1 = point->point; b2Vec2 p2 = b2MulAdd( p1, k_axisScale, normal ); - draw->DrawSegment( p1, p2, normalColor, draw->context ); + draw->DrawSegmentFcn( p1, p2, normalColor, draw->context ); } else if ( draw->drawContactImpulses ) { b2Vec2 p1 = point->point; b2Vec2 p2 = b2MulAdd( p1, k_impulseScale * point->normalImpulse, normal ); - draw->DrawSegment( p1, p2, impulseColor, draw->context ); + draw->DrawSegmentFcn( p1, p2, impulseColor, draw->context ); snprintf( buffer, B2_ARRAY_COUNT( buffer ), "%.2f", 1000.0f * point->normalImpulse ); - draw->DrawString( p1, buffer, b2_colorWhite, draw->context ); + draw->DrawStringFcn( p1, buffer, b2_colorWhite, draw->context ); + } + + if ( draw->drawContactFeatures ) + { + snprintf( buffer, B2_ARRAY_COUNT( buffer ), "%d", point->id ); + draw->DrawStringFcn( point->point, buffer, b2_colorOrange, draw->context ); } if ( draw->drawFrictionImpulses ) @@ -1409,14 +1440,61 @@ void b2World_Draw( b2WorldId worldId, b2DebugDraw* draw ) b2Vec2 tangent = b2RightPerp( normal ); b2Vec2 p1 = point->point; b2Vec2 p2 = b2MulAdd( p1, k_impulseScale * point->tangentImpulse, tangent ); - draw->DrawSegment( p1, p2, frictionColor, draw->context ); - snprintf( buffer, B2_ARRAY_COUNT( buffer ), "%.2f", point->normalImpulse ); - draw->DrawString( p1, buffer, b2_colorWhite, draw->context ); + draw->DrawSegmentFcn( p1, p2, frictionColor, draw->context ); + snprintf( buffer, B2_ARRAY_COUNT( buffer ), "%.2f", point->tangentImpulse ); + draw->DrawStringFcn( p1, buffer, b2_colorWhite, draw->context ); } } } } } + + if ( draw->drawIslands ) + { + int count = world->islands.count; + for ( int i = 0; i < count; ++i ) + { + b2Island* island = world->islands.data + i; + if ( island->setIndex == B2_NULL_INDEX ) + { + continue; + } + + int shapeCount = 0; + b2AABB aabb = { + .lowerBound = { FLT_MAX, FLT_MAX }, + .upperBound = { -FLT_MAX, -FLT_MAX }, + }; + + island->bodyCount; + + int bodyId = island->headBody; + while (bodyId != B2_NULL_INDEX) + { + b2Body* body = b2BodyArray_Get( &world->bodies, bodyId ); + int shapeId = body->headShapeId; + while (shapeId != B2_NULL_INDEX) + { + b2Shape* shape = b2ShapeArray_Get( &world->shapes, shapeId ); + aabb = b2AABB_Union( aabb, shape->fatAABB ); + shapeCount += 1; + shapeId = shape->nextShapeId; + } + + bodyId = body->islandNext; + } + + if (shapeCount > 0) + { + b2Vec2 vs[4] = { { aabb.lowerBound.x, aabb.lowerBound.y }, + { aabb.upperBound.x, aabb.lowerBound.y }, + { aabb.upperBound.x, aabb.upperBound.y }, + { aabb.lowerBound.x, aabb.upperBound.y } }; + + draw->DrawPolygonFcn( vs, 4, b2_colorOrangeRed, draw->context ); + } + } + } } b2BodyEvents b2World_GetBodyEvents( b2WorldId worldId ) @@ -1764,7 +1842,7 @@ void b2World_SetContactTuning( b2WorldId worldId, float hertz, float dampingRati world->contactHertz = b2ClampFloat( hertz, 0.0f, FLT_MAX ); world->contactDampingRatio = b2ClampFloat( dampingRatio, 0.0f, FLT_MAX ); - world->contactMaxPushSpeed = b2ClampFloat( pushSpeed, 0.0f, FLT_MAX ); + world->maxContactPushSpeed = b2ClampFloat( pushSpeed, 0.0f, FLT_MAX ); } void b2World_SetJointTuning( b2WorldId worldId, float hertz, float dampingRatio ) @@ -1823,7 +1901,7 @@ b2Counters b2World_GetCounters( b2WorldId worldId ) b2DynamicTree* kinematicTree = world->broadPhase.trees + b2_kinematicBody; s.treeHeight = b2MaxInt( b2DynamicTree_GetHeight( dynamicTree ), b2DynamicTree_GetHeight( kinematicTree ) ); - s.stackUsed = b2GetMaxArenaAllocation( &world->stackAllocator ); + s.stackUsed = b2GetMaxArenaAllocation( &world->arena ); s.byteCount = b2GetByteCount(); s.taskCount = world->taskCount; @@ -1975,7 +2053,7 @@ void b2World_DumpMemoryStats( b2WorldId worldId ) fprintf( file, "\n" ); // stack allocator - fprintf( file, "stack allocator: %d\n\n", world->stackAllocator.capacity ); + fprintf( file, "stack allocator: %d\n\n", world->arena.capacity ); // chain shapes // todo @@ -2073,14 +2151,15 @@ static bool TreeOverlapCallback( int proxyId, int shapeId, void* context ) b2DistanceInput input; input.proxyA = worldContext->proxy; input.proxyB = b2MakeShapeDistanceProxy( shape ); - input.transformA = worldContext->transform; - input.transformB = transform; + input.transformA = b2Transform_identity; + input.transformB = b2InvMulTransforms( worldContext->transform, transform ); input.useRadii = true; b2SimplexCache cache = { 0 }; b2DistanceOutput output = b2ShapeDistance( &cache, &input, NULL, 0 ); - if ( output.distance > 0.0f ) + float tolerance = 0.1f * B2_LINEAR_SLOP; + if ( output.distance > tolerance ) { return true; } @@ -2488,6 +2567,110 @@ b2TreeStats b2World_CastPolygon( b2WorldId worldId, const b2Polygon* polygon, b2 return treeStats; } +static b2AABB b2ComputerShapeBounds( const b2ShapeProxy* shape, b2Transform xf ) +{ + B2_ASSERT( shape->count > 0 ); + b2Vec2 lower = b2TransformPoint( xf, shape->points[0] ); + b2Vec2 upper = lower; + + for ( int i = 1; i < shape->count; ++i ) + { + b2Vec2 v = b2TransformPoint( xf, shape->points[i] ); + lower = b2Min( lower, v ); + upper = b2Max( upper, v ); + } + + b2Vec2 r = { shape->radius, shape->radius }; + lower = b2Sub( lower, r ); + upper = b2Add( upper, r ); + + b2AABB aabb = { lower, upper }; + return aabb; +} + +typedef struct b2CharacterCallbackContext +{ + b2World* world; + b2QueryFilter filter; + b2ShapeProxy proxy; + b2Transform transform; + void* userContext; +} b2CharacterCallbackContext; + +static bool b2CharacterOverlapCallback( int proxyId, int shapeId, void* context ) +{ + B2_UNUSED( proxyId ); + + WorldOverlapContext* worldContext = context; + b2World* world = worldContext->world; + + b2Shape* shape = b2ShapeArray_Get( &world->shapes, shapeId ); + + b2Filter shapeFilter = shape->filter; + b2QueryFilter queryFilter = worldContext->filter; + + if ( ( shapeFilter.categoryBits & queryFilter.maskBits ) == 0 || ( shapeFilter.maskBits & queryFilter.categoryBits ) == 0 ) + { + return true; + } + + b2Body* body = b2BodyArray_Get( &world->bodies, shape->bodyId ); + b2Transform transform = b2GetBodyTransformQuick( world, body ); + + b2DistanceInput input; + input.proxyA = worldContext->proxy; + input.proxyB = b2MakeShapeDistanceProxy( shape ); + input.transformA = worldContext->transform; + input.transformB = transform; + input.useRadii = true; + + b2SimplexCache cache = { 0 }; + b2DistanceOutput output = b2ShapeDistance( &cache, &input, NULL, 0 ); + + if ( output.distance > 0.0f ) + { + return true; + } + + b2ShapeId id = { shape->id + 1, world->worldId, shape->generation }; + bool result = worldContext->fcn( id, worldContext->userContext ); + return result; +} + +b2Vec2 b2World_MoveCharacter( b2WorldId worldId, const b2ShapeProxy* shapeProxy, b2Transform originTransform, b2Vec2 translation, + b2QueryFilter filter ) +{ + B2_ASSERT( b2IsValidVec2( originTransform.p ) ); + B2_ASSERT( b2IsValidRotation( originTransform.q ) ); + B2_ASSERT( b2IsValidVec2( translation ) ); + + b2Vec2 position = originTransform.p; + + b2World* world = b2GetWorldFromId( worldId ); + B2_ASSERT( world->locked == false ); + if ( world->locked ) + { + return position; + } + + b2AABB aabb = b2ComputerShapeBounds( shapeProxy, originTransform ); + b2CharacterCallbackContext context = { + world, + filter, + *shapeProxy, + originTransform, + }; + + for ( int i = 0; i < b2_bodyTypeCount; ++i ) + { + b2TreeStats treeResult = + b2DynamicTree_Query( world->broadPhase.trees + i, aabb, filter.maskBits, b2CharacterOverlapCallback, &context ); + B2_UNUSED( treeResult ); + } + + return b2Add( originTransform.p, translation ); +} + #if 0 void b2World_ShiftOrigin(b2WorldId worldId, b2Vec2 newOrigin) @@ -2778,6 +2961,8 @@ void b2ValidateConnectivity( b2World* world ) continue; } + b2ValidateUsedId( &world->bodyIdPool, bodyIndex ); + B2_ASSERT( bodyIndex == body->id ); // Need to get the root island because islands are not merged until the next time step diff --git a/src/world.h b/src/world.h index 35c455778..8a38b2309 100644 --- a/src/world.h +++ b/src/world.h @@ -43,7 +43,7 @@ typedef struct b2TaskContext // The world also contains efficient memory management facilities. typedef struct b2World { - b2ArenaAllocator stackAllocator; + b2ArenaAllocator arena; b2BroadPhase broadPhase; b2ConstraintGraph constraintGraph; @@ -114,6 +114,7 @@ typedef struct b2World b2BitSet debugBodySet; b2BitSet debugJointSet; b2BitSet debugContactSet; + b2BitSet debugIslandSet; // Id that is incremented every time step uint64_t stepIndex; @@ -131,7 +132,7 @@ typedef struct b2World float hitEventThreshold; float restitutionThreshold; float maxLinearSpeed; - float contactMaxPushSpeed; + float maxContactPushSpeed; float contactHertz; float contactDampingRatio; float jointHertz; diff --git a/test/main.c b/test/main.c index 4ca58d7ca..8e7ae322e 100644 --- a/test/main.c +++ b/test/main.c @@ -3,7 +3,7 @@ #include "test_macros.h" -#if defined( _WIN32 ) +#if defined( _MSC_VER ) #include // int MyAllocHook(int allocType, void* userData, size_t size, int blockType, long requestNumber, const unsigned char* filename, @@ -30,7 +30,7 @@ extern int WorldTest( void ); int main( void ) { -#if defined( _WIN32 ) +#if defined( _MSC_VER ) // Enable memory-leak reports // How to break at the leaking allocation, in the watch window enter this variable @@ -63,7 +63,7 @@ int main( void ) printf( "======================================\n" ); printf( "All Box2D tests passed!\n" ); -#if defined( _WIN32 ) +#if defined( _MSC_VER ) if ( _CrtDumpMemoryLeaks() ) { return 1; diff --git a/test/test_determinism.c b/test/test_determinism.c index 43102d0e6..a397eebae 100644 --- a/test/test_determinism.c +++ b/test/test_determinism.c @@ -326,8 +326,8 @@ static int CrossPlatformTest(void) } ENSURE( stepCount < maxSteps ); - ENSURE( sleepStep == 263 ); - ENSURE( hash == 0x7de58fbe ); + ENSURE( sleepStep == 383 ); + ENSURE( hash == 0xfeb0cd4e ); free( bodies ); diff --git a/test/test_world.c b/test/test_world.c index 146bceda7..c9d563345 100644 --- a/test/test_world.c +++ b/test/test_world.c @@ -339,6 +339,7 @@ static int TestSensor( void ) b2BodyId wallId = b2CreateBody( worldId, &bodyDef ); b2Polygon box = b2MakeBox( 0.5f, 10.0f ); b2ShapeDef shapeDef = b2DefaultShapeDef(); + shapeDef.enableSensorEvents = true; b2CreatePolygonShape( wallId, &shapeDef, &box ); // Bullet fired towards the wall @@ -351,6 +352,7 @@ static int TestSensor( void ) b2BodyId bulletId = b2CreateBody( worldId, &bodyDef ); shapeDef = b2DefaultShapeDef(); shapeDef.isSensor = true; + shapeDef.enableSensorEvents = true; b2Circle circle = { { 0.0f, 0.0f }, 0.1f }; b2CreateCircleShape( bulletId, &shapeDef, &circle );