diff --git a/CMakeLists.txt b/CMakeLists.txt index b5f944ee..31c21b68 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ include(FetchContent) project(box2d VERSION 3.0.0 - DESCRIPTION "A 2D physics engine for games" + DESCRIPTION "A 2D physics engine" HOMEPAGE_URL "https://box2d.org" LANGUAGES C CXX ) diff --git a/benchmark/joint_grid.c b/benchmark/joint_grid.c index 638a0d2d..a36edd07 100644 --- a/benchmark/joint_grid.c +++ b/benchmark/joint_grid.c @@ -2,7 +2,6 @@ // SPDX-License-Identifier: MIT #include "box2d/box2d.h" -#include "box2d/geometry.h" #include "box2d/math_functions.h" #include diff --git a/benchmark/large_pyramid.c b/benchmark/large_pyramid.c index 4283f2b7..9b922735 100644 --- a/benchmark/large_pyramid.c +++ b/benchmark/large_pyramid.c @@ -2,7 +2,6 @@ // SPDX-License-Identifier: MIT #include "box2d/box2d.h" -#include "box2d/geometry.h" #include "box2d/math_functions.h" b2WorldId LargePyramid(b2WorldDef* worldDef) diff --git a/benchmark/main.c b/benchmark/main.c index ce18e5c8..8a6be501 100644 --- a/benchmark/main.c +++ b/benchmark/main.c @@ -7,7 +7,6 @@ #include "box2d/box2d.h" #include "box2d/math_functions.h" -#include "box2d/timer.h" #include #include diff --git a/benchmark/many_pyramids.c b/benchmark/many_pyramids.c index 47c60e4d..0ccfd28d 100644 --- a/benchmark/many_pyramids.c +++ b/benchmark/many_pyramids.c @@ -2,7 +2,6 @@ // SPDX-License-Identifier: MIT #include "box2d/box2d.h" -#include "box2d/geometry.h" #include "box2d/math_functions.h" static void CreatePyramid(b2WorldId worldId, int baseCount, float extent, float centerX, float baseY) diff --git a/benchmark/smash.c b/benchmark/smash.c index 838d89ad..02e9276f 100644 --- a/benchmark/smash.c +++ b/benchmark/smash.c @@ -2,7 +2,6 @@ // SPDX-License-Identifier: MIT #include "box2d/box2d.h" -#include "box2d/geometry.h" #include "box2d/math_functions.h" b2WorldId Smash(b2WorldDef* worldDef) diff --git a/benchmark/tumbler.c b/benchmark/tumbler.c index bf02be33..ea3baa14 100644 --- a/benchmark/tumbler.c +++ b/benchmark/tumbler.c @@ -2,7 +2,6 @@ // SPDX-License-Identifier: MIT #include "box2d/box2d.h" -#include "box2d/geometry.h" #include "box2d/math_functions.h" b2WorldId Tumbler(b2WorldDef* worldDef) diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index 8d4e8145..72412269 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -1,8 +1,12 @@ find_package(Doxygen REQUIRED dot) +set(DOXYGEN_PROJECT_NAME "Box2D") +set(DOXYGEN_GENERATE_HTML YES) +set(DOXYGEN_USE_MATHJAX YES) +set(DOXYGEN_MATHJAX_VERSION MathJax_3) +set(DOXYGEN_MATHJAX_FORMAT SVG) set(DOXYGEN_EXTRACT_ALL NO) set(DOXYGEN_FILE_PATTERNS *.h) -set(DOXYGEN_EXCLUDE_PATTERNS timer.h) set(DOXYGEN_ENABLE_PREPROCESSING YES) set(DOXYGEN_MACRO_EXPANSION YES) set(DOXYGEN_EXPAND_ONLY_PREDEF YES) @@ -17,29 +21,32 @@ set(DOXYGEN_IMAGE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/images") set(DOXYGEN_HTML_EXTRA_STYLESHEET "${CMAKE_CURRENT_SOURCE_DIR}/extra.css") set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "${CMAKE_CURRENT_SOURCE_DIR}/overview.md") set(DOXYGEN_PROJECT_LOGO "${CMAKE_CURRENT_SOURCE_DIR}/images/logo.svg") -set(DOXYGEN_LAYOUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/DoxygenLayout.xml") +set(DOXYGEN_LAYOUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/layout.xml") set(DOXYGEN_INLINE_SIMPLE_STRUCTS YES) set(DOXYGEN_TYPEDEF_HIDES_STRUCT YES) -set(DOXYGEN_DISABLE_INDEX YES) +# set(DOXYGEN_DISABLE_INDEX YES) set(DOXYGEN_GENERATE_TREEVIEW YES) set(DOXYGEN_FULL_SIDEBAR NO) +# force dark mode to work with extra.css +set(DOXYGEN_HTML_COLORSTYLE DARK) + # this tells doxygen to label structs as structs intead of classes set(DOXYGEN_OPTIMIZE_OUTPUT_FOR_C YES) - set(DOXYGEN_WARN_IF_INCOMPLETE_DOC NO) doxygen_add_docs(doc "${CMAKE_SOURCE_DIR}/include/box2d" "overview.md" "hello.md" - "testbed.md" - "common.md" + "samples.md" + "foundation.md" "collision.md" - "dynamics.md" + "simulation.md" "loose_ends.md" - "references.md" - "FAQ.md" + "reading.md" + "faq.md" + "migration.md" ALL COMMENT "Generate HTML documentation") diff --git a/docs/FAQ.md b/docs/FAQ.md index 27fd0b43..12b2680e 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -1,74 +1,67 @@ -# FAQ (NOT UPDATED for Box2D version 3.0) +# FAQ ## What is Box2D? +Box2D is a feature rich 2D rigid body physics engine, written in C11 by Erin Catto. It has been used in many games and in many +game engines. -Box2D is a feature rich 2D rigid body physics engine, written in C++ by Erin Catto. It has been used in many games, including Crayon Physics Deluxe, winner of the 2008 Independant Game Festival Grand Prize. - -Box2D uses the [MIT license](https://en.wikipedia.org/wiki/MIT_License) license and can be used free of charge. +Box2D uses the [MIT license](https://en.wikipedia.org/wiki/MIT_License) license and can be used free of charge. Credit +should be included if possible. Support is [appreciated](https://github.com/sponsors/erincatto). You may use the +Box2D [logo](https://box2d.org/images/logo.svg). ## What platforms does Box2D support? +Box2D is developed using C11. Ports and bindings are likely available for most languages and platforms. -Box2D is developed on Windows using Visual C++. Ports are also available for Flash, Java, C#, Python. - -Erin Catto maintains the C++ version, but provides no support for other languages. Other languages are supported by the community and possibly by the authors of those ports. +Erin Catto maintains the C11 version, but provides no support for other languages. Other languages are supported +by the community and possibly by the authors of those ports. ## Who makes it? - -Erin Catto is the driving force behind Box2D, with various others supporting the ports. Box2D is an open source project, and accepts community feedback. +Erin Catto is the creator and sole contributor of the C11 version of Box2D, with various others supporting the ports. Box2D is an open source project, and accepts community feedback. ## How do I get help? +You should read the documentation and the rest of this FAQ first. Also, you should study the examples included in the source distribution. Then you can visit the [Discord](https://discord.gg/aM4mRKxW) to ask any remaining questions. -You should read the documentation and the rest of this FAQ first. Also, you should study the examples included in the source distribution. Then you can visit the [subreddit](https://www.reddit.com/r/box2d/) to ask any remaining questions. - -Please to not PM or email Erin Catto for support. It is best to ask questions in the forum so that everyone can benefit from the discussion. +Please to not message or email Erin Catto for support. It is best to ask questions on the Discord server so that everyone can benefit from the discussion. ## Documentation -### Why isn't feature foo documented? - -If you grab the latest code from the git master branch you will likely find features that are not documented in the manual. New features are added to the manual after they are mature and a new point release is imminent. However, all major features added to Box2D are accompanied by example code in the testbed to test the feature and show the intended usage. +### Why isn't a feature documented? +If you grab the latest code from the git master branch you will likely find features that are not documented in the manual. New features are added to the manual after they are mature and a new point release is imminent. However, all major features added to Box2D are accompanied by example code in the samples application to test the feature and show the intended usage. ## Prerequisites ### Programming - -You should have a working knowledge of C++ before you use Box2D. You should understand classes, inheritance, and pointers. There are plenty of resources on the web for learning C++. You should also understand your development environment: compilation, linking, and debugging. +You should have a working knowledge of C before you use Box2D. You should understand functions, structures, and pointers. There are plenty of resources on the web for learning C. You should also understand your development environment: compilation, linking, and debugging. ### Math and Physics - You should have a basic knowledge of rigid bodies, force, torque, and impulses. If you come across a math or physics concept you don't understand, please read about it on Wikipedia. Visit this [page](http://box2d.org/publications/) if you want a deeper knowledge of the algorithms used in Box2D. ## API ### What units does Box2D use? - -Box2D is tuned for meters-kilograms-seconds (MKS). Your moving objects should be between 0.1 - 10 meters. Do not use pixels as units! You will get a jittery simulation. +Box2D is tuned for meters-kilograms-seconds (MKS). This is recommend as the units for your game. However, you may use +different units if you are careful. ### How do I convert pixels to meters? - Suppose you have a sprite for a character that is 100x100 pixels. You decide to use a scaling factor that is 0.01. This will make the character physics box 1m x 1m. So go make a physics box that is 1x1. Now suppose the character starts out at pixel coordinate (345,679). So position the physics box at (3.45,6.79). Now simulate the physics world. Suppose the character physics box moves to (2.31,4.98), so move your character sprite to pixel coordinates (231,498). -Now the only tricky part is choosing a scaling factor. This really depends on your game. You should try to get your moving objects in the range 0.1 - 10 meters, with 1 meter being the sweet spot. -### Why don't you use this awesome C++ feature? +Now the only tricky part is choosing a scaling factor. This really depends on your game. You should try to get your moving objects in the range 0.1 - 10 meters, with 1 meter being the sweet spot. -Box2D is designed to be portable, so I try to keep the C++ usage simple. Also, I don't use the STL (except sort) or other libraries to keep the dependencies low. I keep template usage low and don't use name spaces. Remember, just because a C++ feature exists, that doesn't mean you need to use it. +This [repo](https://github.com/erincatto/box2d-raylib) shows how to convert meters to pixels. -The many ports of Box2D to other languages platforms shows that this strategy has been successful. +### Why don't you use this awesome language? +Box2D is designed to be portable and easy to wrap with other languages, so I decided to use C11. I used C11 to get support for atomics. ### Can I use Box2D in a DLL? - -Box2D was not designed to be used in a DLL. You may have to change how static data is used to make this work. +Yes. See the CMake option `BUILD_SHARED_LIBS`. ### Is Box2D thread-safe? - -No. Box2D will likely never be thread-safe. Box2D has a large API and trying to make such an API thread-safe would have a large performance and complexity impact. +No. Box2D will likely never be thread-safe. Box2D has a large API and trying to make such an API thread-safe would have a large performance and complexity impact. However, you can call read only functions from multiple threads. For example, all the +[spatial query](#spatial) functions are read only. ## Build Issues ### Why doesn't my code compile and/or link? - There are many reasons why a build can go bad. Here are a few that have come up: - * Using old Box2D headers with new code * Not linking the Box2D library with your application * Using old project files that don't include some new source files @@ -76,71 +69,50 @@ There are many reasons why a build can go bad. Here are a few that have come up: ## Rendering ### What are Box2D's rendering capabilities? - Box2D is only a physics engine. How you draw stuff is up to you. -### But the Testbed draws stuff - -Visualization is very important for debugging collision and physics. I wrote the test bed to help me test Box2D and give you examples of how to use Box2D. The TestBed is not part of the Box2D library. +### But the samples application draws stuff +Visualization is very important for debugging collision and physics. I wrote the samples application to help me test Box2D and give you examples of how to use Box2D. The samples are not part of the Box2D library. ### How do I draw shapes? - -Drawing shapes is not supported and shape internal data is likely to change. Instead you should implement the `b2DebugDraw` interface. +Implement the `b2DebugDraw` interface and call `b2World_Draw()`. ## Accuracy - Box2D uses approximate methods for a few reasons. - * Performance * Some differential equations don't have known solutions * Some constraints cannot be determined uniquely What this means is that constraints are not perfectly rigid and sometimes you will see some bounce even when the restitution is zero. -Box2D uses Gauss-Seidel to approximately solve constraints. -Box2D also uses Semi-implicit Euler to approximately solve the differential equations. -Box2D also does not have exact collision. Polygons are covered with a thin skin (around 0.5cm thick) to avoid numerical problems. This can sometimes lead to unexpected contact normals. Also, some shapes may begin to overlap and then be pushed apart by the solver. +Box2D uses [Gauss-Seidel](https://en.wikipedia.org/wiki/Gauss%E2%80%93Seidel_method) to approximately solve constraints. +Box2D also uses [Semi-implicit Euler](https://en.wikipedia.org/wiki/Semi-implicit_Euler_method) to approximately solve the differential equations. +Box2D also does not have exact collision. There is no continuous collision between dynamic shapes. Slow moving shapes may have small overlap for a few time steps. In extreme stacking scenarios, shapes may have sustained overlap. ## Making Games ### Worms Clones - Making a worms clone requires arbitrarily destructible terrain. This is beyond the scope of Box2D, so you will have to figure out how to do this on your own. ### Tile Based Environment - -Using many boxes for your terrain may not work well because box-like characters can get snagged on internal corners. A future update to Box2D should allow for smooth motion over edge chains. In general you should avoid using a rectangular character because collision tolerances will still lead to undesirable snagging. +Using many boxes for your terrain may not work well because box-like characters can get snagged on internal corners. Box2D proves chain shapes for smooth collision, see `b2ChainDef`. In general you should avoid using a rectangular character because collision tolerances will still lead to undesirable snagging. Box2D provides capsules and rounded polygons that may work better for characters. ### Asteroid Type Coordinate Systems - Box2D does not have any support for coordinate frame wrapping. You would likely need to customize Box2D for this purpose. You may need to use a different broad-phase for this to work. ## Determinism ### Is Box2D deterministic? - For the same input, and same binary, Box2D will reproduce any simulation. Box2D does not use any random numbers nor base any computation on random events (such as timers, etc). -However, people often want more stringent determinism. People often want to know if Box2D can produce identical results on different binaries and on different platforms. The answer is no. The reason for this answer has to do with how floating point math is implemented in many compilers and processors. I recommend reading this article if you are curious: http://www.yosefk.com/blog/consistency-how-to-defeat-the-purpose-of-ieee-floating-point.html +Box2D is also deterministic under multithreading. A simulation using two threads will give the same result as eight threads. -### But I really want determinism +However, people often want more stringent determinism. People often want to know if Box2D can produce identical results on different binaries and on different platforms. Currently this is not provided, but the situation may improve in a future update. +### But I really want determinism This naturally leads to the question of fixed-point math. Box2D does not support fixed-point math. In the past Box2D was ported to the NDS in fixed-point and apparently it worked okay. Fixed-point math is slower and more tedious to develop, so I have chosen not to use fixed-point for the development of Box2D. -## Why is the restitution/friction mixing inaccurate? - -A physically correct restitution value must be measured in experiments. But as soon as you change the geometry from the experiment then the value is wrong. Next, adding simultaneous collision makes the answer worse. We've been down this road before. - -So the question of accuracy has been answered: failure. - -The only remaining question is how do we make it convenient. On this opinions may vary. - -`b2Settings` is just that. Settings you can adjust if you know what you are doing. - -## What are the biggest mistakes made by new users? - -* Using pixels for length instead of meters. -* Expecting Box2D to give pixel perfect results. -* Using b2Polygon to create concave polygons. -* Testing their code in release mode. -* Not learning C++ before using Box2D. -* Not reading this FAQ. :) +## What are the common mistakes made by new users? +* Using pixels for length instead of meters +* Expecting Box2D to give pixel perfect results +* Testing their code in release mode +* Not learning C before using Box2D diff --git a/docs/collision.md b/docs/collision.md index 67c9df99..debd31a4 100644 --- a/docs/collision.md +++ b/docs/collision.md @@ -1,58 +1,70 @@ -# Collision Module (NOT UPDATED for Box2D version 3.0) -The Collision module contains shapes and functions that operate on them. -The module also contains a dynamic tree and broad-phase to acceleration -collision processing of large systems. - -The collision module is designed to be usable outside of the dynamic -system. For example, you can use the dynamic tree for other aspects of -your game besides physics. - -However, the main purpose of Box2D is to provide a rigid body physics -engine, so the using the collision module by itself may feel limited for -some applications. Likewise, I will not make a strong effort to document -it or polish the APIs. - -## Shapes -Shapes describe collision geometry and may be used independently of +# Collision +Box2D provides geometric types and functions. These include: +- raw geometry: circles, capsules, segments, and convex polygons +- convex hull and related helper functions +- mass and bounding box computation +- local ray and shape casts +- contact manifolds +- shape distance +- generic shape cast +- time of impact +- dynamic bounding volume tree + +The collision interface is designed to be usable outside of rigid body simulation. +For example, you can use the dynamic tree for other aspects of your game besides physics. + +However, the main purpose of Box2D is to be a rigid body physics +engine. So the collision interface only contains features that are also useful in +the physics simulation. + +## Shape Primitives +Shape primitives describe collision geometry and may be used independently of physics simulation. At a minimum, you should understand how to create -shapes that can be later attached to rigid bodies. - -Box2D shapes implement the b2Shape base class. The base class defines -functions to: -- Test a point for overlap with the shape. -- Perform a ray cast against the shape. -- Compute the shape's AABB. -- Compute the mass properties of the shape. - -In addition, each shape has a type member and a radius. The radius even -applies to polygons, as discussed below. - -Keep in mind that a shape does not know about bodies and stand apart -from the dynamics system. Shapes are stored in a compact form that is -optimized for size and performance. As such, shapes are not easily moved -around. You have to manually set the shape vertex positions to move a -shape. However, when a shape is attached to a body using a fixture, the -shapes move rigidly with the host body. In summary: -- When a shape is **not** attached to a body, you can view it's vertices as being expressed in world-space. -- When a shape is attached to a body, you can view it's vertices as being expressed in local coordinates. - -### Circle Shapes -Circle shapes have a position and radius. Circles are solid. You cannot -make a hollow circle using the circle shape. - -```cpp -b2CircleShape circle; -circle.m_p.Set(2.0f, 3.0f); -circle.m_radius = 0.5f; +primitives that can be later attached to rigid bodies. + +Box2D shape primitives support several operations: +- Test a point for overlap with the primitive +- Perform a ray cast against the primitive +- Compute the primitive's AABB +- Compute the mass properties of the primitive + +### Circles +Circles have a center and radius. Circles are solid. + +![Circle](images/circle.svg) + +```c +b2Circle circle; +circle.center = (b2Vec2){2.0f, 3.0f}; +circle.radius = 0.5f; +``` + +You can also initialize a circle and other structures inline. This is an equivalent circle: + +```c +b2Circle circle = {{2.0f, 3.0f}, 0.5f}; +``` + +### Capsules +Capsules have two center points and a radius. The center points are the centers of two +semicircles that are connected by a rectangle. + +![Capsule](images/capsule.svg) + +```c +b2Capsule capsule; +capsule.center1 = (b2Vec2){1.0f, 1.0f}; +capsule.center1 = (b2Vec2){2.0f, 3.0f}; +capsule.radius = 0.25f; ``` -### Polygon Shapes -Polygon shapes are solid convex polygons. A polygon is convex when all +### Polygons +Box2D polygons are solid convex polygons. A polygon is convex when all line segments connecting two points in the interior do not cross any edge of the polygon. Polygons are solid and never hollow. A polygon must have 3 or more vertices. -![Convex and Concave Polygons](images/convex_concave.gif) +![Convex and Concave Polygons](images/convex_concave.svg) Polygons vertices are stored with a counter clockwise winding (CCW). We must be careful because the notion of CCW is with respect to a @@ -66,286 +78,175 @@ The polygon members are public, but you should use initialization functions to create a polygon. The initialization functions create normal vectors and perform validation. -You can create a polygon shape by passing in a vertex array. The maximal -size of the array is controlled by `b2_maxPolygonVertices` which has a -default value of 8. This is sufficient to describe most convex polygons. - -The `b2PolygonShape::Set` function automatically computes the convex hull -and establishes the proper winding order. This function is fast when the -number of vertices is low. If you increase `b2_maxPolygonVertices`, then -the convex hull computation might become slow. Also note that the convex -hull function may eliminate and/or re-order the points you provide. -Vertices that are closer than `b2_linearSlop` may be merged. - -```cpp -// This defines a triangle in CCW order. -b2Vec2 vertices[3]; -vertices[0].Set(0.0f, 0.0f); -vertices[1].Set(1.0f, 0.0f); -vertices[2].Set(0.0f, 1.0f); - -int32 count = 3; -b2PolygonShape polygon; -polygon.Set(vertices, count); +Polygons in Box2D have a maximum of 8 vertices, as controlled by #b2_maxPolygonVertices. +If you have more complex shapes, I recommend to use multiple polygons. + +There are a few ways to create polygons. You can attempt to create them manually, +but this is not recommended. Instead there are several functions provided to create them. + +For example if you need a square or box you can use these functions: + +```c +b2Polygon square = b2MakeSquare(0.5f); +b2Polygon box = b2MakeBox(0.5f, 1.0f); ``` -The polygon shape has some convenience functions to create boxes. +The values provided to these functions are *extents*, which are half-widths or half-heights. +This corresponds with circles and capsules using radii instead of diameters. + +Box2D also supports rounded polygons. These are convex polygons with a thick rounded skin. -```cpp -void SetAsBox(float hx, float hy); -void SetAsBox(float hx, float hy, const b2Vec2& center, float angle); +```c +b2Polygon roundedBox = b2MakeRoundedBox(0.5f, 1.0f, 0.25f); ``` -Polygons inherit a radius from b2Shape. The radius creates a skin around -the polygon. The skin is used in stacking scenarios to keep polygons -slightly separated. This allows continuous collision to work against the -core polygon. +If you want a box that is not centered on the body origin, you can use an offset box. -![Polygon Skin](images/skinned_polygon.svg) +```c +b2Vec2 center = {1.0f, 0.0f}; +float angle = b2_pi / 4.0f; +b2Polygon offsetBox = b2MakeOffsetBox(0.5f, 1.0f, center, angle); +``` -The polygon skin helps prevent tunneling by keeping the polygons -separated. This results in small gaps between the shapes. Your visual -representation can be larger than the polygon to hide any gaps. +If you want a more general convex polygon, you can compute the hull using `b2ComputeHull()`. Then you can +create a polygon from the hull. You can make this rounded or not. -![Skin Collision](images/skin_collision.svg) +```c +b2Vec2 points[] = {{-1.0f, 0.0f}, {1.0f, 0.0f}, {0.0f, 1.0f}}; +b2Hull hull = b2ComputeHull(points, 3); +float radius = 0.1f; +b2Polygon roundedTriangle = b2MakePolygon(&hull, radius); +``` -Not that polygon skin is only provided to help with continuous collision. -The purpose is not to simulate rounded polygons. +If you have an automatic process for generating convex polygons, you may feed a degenerate set of points to `b2ComputeHull()`. You should check that the hull was created successfully before creating the polygon or you will get an assertion. -### Edge Shapes -Edge shapes are line segments. These are provided to assist in making a -free-form static environment for your game. A major limitation of edge -shapes is that they can collide with circles and polygons but not with -themselves. The collision algorithms used by Box2D require that at least -one of two colliding shapes have volume. Edge shapes have no volume, so -edge-edge collision is not possible. +```c +b2Hull questionableHull = b2ComputeHull(randomPoints, 8); +if (questionableHull.count == 0) +{ + // handle failure +} +``` + +Degenerate points may be coincident and/or collinear. For the hull to be viable, the enclosed area must be sufficiently positive. -```cpp -// This an edge shape. -b2Vec2 v1(0.0f, 0.0f); -b2Vec2 v2(1.0f, 0.0f); +### Segments +Segments are line segments. Segment +shapes can collide with circles, capsules, and polygons but not with other line segments. +The collision algorithms used by Box2D require that at least +one of two colliding shapes has sufficiently positive area. Segment shapes have no area, so +segment-segment collision is not possible. -b2EdgeShape edge; -edge.SetTwoSided(v1, v2); +```c +b2Segment segment1; +segment1.point1 = (b2Vec2){0.0f, 0.0f}; +segment2.point2 = (b2Vec2){1.0f, 0.0f}; + +// equivalent +b2Segment segment2 = {{0.0f, 0.0f}, {1.0f, 0.0f}}; ``` +### Ghost Collisions In many cases a game environment is constructed by connecting several -edge shapes end-to-end. This can give rise to an unexpected artifact -when a polygon slides along the chain of edges. In the figure below we -see a box colliding with an internal vertex. These *ghost* collisions +segment shapes end-to-end. This can give rise to an unexpected artifact +when a polygon slides along the chain of segments. In the figure below there is + a box colliding with an internal vertex. These *ghost* collisions are caused when the polygon collides with an internal vertex generating an internal collision normal. -![Ghost Collision](images/ghost_collision.svg) +![Ghost Collision](images/ghost_collision.svg){html: width=30%} If edge1 did not exist this collision would seem fine. With edge1 present, the internal collision seems like a bug. But normally when Box2D collides two shapes, it views them in isolation. -Fortunately, the edge shape provides a mechanism for eliminating ghost +`b2SmoothSegment` provides a mechanism for eliminating ghost collisions by storing the adjacent *ghost* vertices. Box2D uses these ghost vertices to prevent internal collisions. -![Ghost Vertices](images/ghost_vertices.svg) +![Ghost Vertices](images/ghost_vertices.svg){html: width=30%} The Box2D algorithm for dealing with ghost collisions only supports one-sided collision. The front face is to the right when looking from the first -vertex towards the second vertex. This matches the CCW winding order +vertex towards the second vertex. This matches the counter-clockwise winding order used by polygons. -```cpp -// This is an edge shape with ghost vertices. -b2Vec2 v0(1.7f, 0.0f); -b2Vec2 v1(1.0f, 0.25f); -b2Vec2 v2(0.0f, 0.0f); -b2Vec2 v3(-1.7f, 0.4f); +### Smooth segment +Smooth segments use a concept called *ghost vertices* that Box2D can use to eliminate ghost +collisions. -b2EdgeShape edge; -edge.SetOneSided(v0, v1, v2, v3); +```c +b2SmoothSegment smoothSegment = {0}; +smoothSegment.ghost1 = (b2Vec2){1.7f, 0.0f}; +smoothSegment.segment = (b2Segment){{1.0f, 0.25f}, {0.0f, 0.0f}}; +smoothSegment.ghost2 = (b2Vec2){-1.7f, 0.4f}; ``` -In general stitching edges together this way is a bit wasteful and -tedious. This brings us to chain shapes. - -### Chain Shapes +These ghost vertices must align with vertices of neighboring smooth segments, making them +tedious and error-prone to setup. -The chain shape provides an efficient way to connect many edges together -to construct your static game worlds. Chain shapes automatically -eliminate ghost collisions and provide one-sided collision. The collision is -one-sided to eliminate ghost collisions. - -If you don't care about ghost collisions, you can just create a bunch of -two-sided edge shapes. The efficiency is similar. - -The simplest way to use chain shapes is to create loops. Simply provide an -array of vertices. - -```cpp -b2Vec2 vs[4]; -vs[0].Set(1.7f, 0.0f); -vs[1].Set(1.0f, 0.25f); -vs[2].Set(0.0f, 0.0f); -vs[3].Set(-1.7f, 0.4f); - -b2ChainShape chain; -chain.CreateLoop(vs, 4); -``` - -The edge normal depends on the winding order. A counter-clockwise winding order orients the normal outwards and a clockwise winding order orients the normal inwards. - -![Chain Shape Outwards Loop](images/chain_loop_outwards.svg) - -![Chain Shape Inwards Loop](images/chain_loop_inwards.svg) - -You may have a scrolling game world and would like to connect several chains together. -You can connect chains together using ghost vertices, like we did with b2EdgeShape. - -![Chain Shape](images/chain_shape.svg) - -```cpp -b2ChainShape::CreateChain(const b2Vec2* vertices, int32 count, - const b2Vec2& prevVertex, const b2Vec2& nextVertex); -``` - -Self-intersection of chain shapes is not supported. It might work, it -might not. The code that prevents ghost collisions assumes there are no -self-intersections of the chain. Also, very close vertices can cause -problems. Make sure all your edges are longer than b2_linearSlop (5mm). - -![Self Intersection is Bad](images/self_intersect.svg) - -Each edge in the chain is treated as a child shape and can be accessed -by index. When a chain shape is connected to a body, each edge gets its -own bounding box in the broad-phase collision tree. - -```cpp -// Visit each child edge. -for (int32 i = 0; i < chain.GetChildCount(); ++i) -{ - b2EdgeShape edge; - chain.GetChildEdge(&edge, i); - - ... -} -``` +Smooth segments are not created directly. Instead, you can create chains of smooth +segments. See `b2ChainDef` and `b2CreateChain()`. ## Geometric Queries -You can perform a couple geometric queries on a single shape. +You can perform a geometric queries on a single shape. ### Shape Point Test You can test a point for overlap with a shape. You provide a transform for the shape and a world point. -```cpp -b2Transform transform; -transform.SetIdentity(); -b2Vec2 point(5.0f, 2.0f); - -bool hit = shape->TestPoint(transform, point); +```c +b2Vec2 point = {5.0f, 2.0f}; +bool hit = b2PointInCapsule(point, &myCapsule); ``` -Edge and chain shapes always return false, even if the chain is a loop. +See also `b2PointInCircle()` and `b2PointInPolygon()`. -### Shape Ray Cast -You can cast a ray at a shape to get the point of first intersection and normal vector. A child index is included for chain shapes because the ray cast will only check a single edge at a time. +### Ray Cast +You can cast a ray at a shape to get the point of first intersection and normal vector. > **Caution**: -> No hit will register if the ray starts inside a convex shape like a circle or polygon. This is consistent with Box2D treating convex shapes as solid. -> - -```cpp -b2Transfrom transform; -transform.SetIdentity(); +> No hit will register if the ray starts inside a convex shape like a circle or polygon. This is +> consistent with Box2D treating convex shapes as solid. +```c b2RayCastInput input; -input.p1.Set(0.0f, 0.0f); -input.p2.Set(1.0f, 0.0f); +input.origin = (b2Vec2){0.0f, 0.0f}; +input.translation = (b2Vec2){1.0f, 0.0f}; input.maxFraction = 1.0f; -int32 childIndex = 0; -b2RayCastOutput output; -bool hit = shape->RayCast(&output, input, transform, childIndex); - -if (hit) +b2CastOutput output = b2RayCastPolygon(&input, &myPolygon); +if (output.hit == true) { - b2Vec2 hitPoint = input.p1 + output.fraction * (input.p2 -- input.p1); - ... + // do something } ``` -## Pairwise Functions -The Collision module contains functions that take a pair of shapes and compute some results. These include: -- Overlap -- Contact manifolds -- Distance -- Time of impact - -### Overlap -You can test two shapes for overlap using this function: - -```cpp -b2Transform xfA = ..., xfB = ...; -bool overlap = b2TestOverlap(shapeA, indexA, shapeB, indexB, xfA, xfB); -``` - -Again you must provide child indices to for the case of chain shapes. - -### Contact Manifolds -Box2D has functions to compute contact points for overlapping shapes. If -we consider circle-circle or circle-polygon, we can only get one contact -point and normal. In the case of polygon-polygon we can get two points. -These points share the same normal vector so Box2D groups them into a -manifold structure. The contact solver takes advantage of this to -improve stacking stability. - -![Contact Manifold](images/manifolds.svg) +### Shape Cast +You can also cast a shape at another shape. This uses an abstract way of describing the moving shape. It is represented as a point cloud with a radius. This implies a convex shape even if the input data is not convex. The internal algorithm (GJK) will essentially only use the convex portion. -Normally you don't need to compute contact manifolds directly, however -you will likely use the results produced in the simulation. - -The b2Manifold structure holds a normal vector and up to two contact -points. The normal and points are held in local coordinates. As a -convenience for the contact solver, each point stores the normal and -tangential (friction) impulses. - -The data stored in b2Manifold is optimized for internal use. If you need -this data, it is usually best to use the b2WorldManifold structure to -generate the world coordinates of the contact normal and points. You -need to provide a b2Manifold and the shape transforms and radii. - -```cpp -b2WorldManifold worldManifold; -worldManifold.Initialize(&manifold, transformA, shapeA.m_radius, -transformB, shapeB.m_radius); +```c +b2ShapeCastInput input; +input.points[0] = (b2Vec2){1.0f, 0.0f}; +input.points[1] = (b2Vec2){2.0f, -3.0f}; +input.radius = 0.2f; +input.translation = (b2Vec2){1.0f, 0.0f}; +input.maxFraction = 1.0f; -for (int32 i = 0; i < manifold.pointCount; ++i) +b2CastOutput output = b2ShapeCastPolygon(&input, &myPolygon); +if (output.hit == true) { - b2Vec2 point = worldManifold.points[i]; - ... + // do something } ``` -Notice that the world manifold uses the point count from the original -manifold. - -During simulation shapes may move and the manifolds may change. Points -may be added or removed. You can detect this using b2GetPointStates. - -```cpp -b2PointState state1[2], state2[2]; -b2GetPointStates(state1, state2, &manifold1, &manifold2); - -if (state1[0] == b2_removeState) -{ - // process event -} -``` +Even more generic, you can use `b2ShapeCast()` to linearly cast one point cloud at another point cloud. All shape cast functions use this internally. ### Distance -The `b2Distance` function can be used to compute the distance between two +`b2ShapeDistance()` function can be used to compute the distance between two shapes. The distance function needs both shapes to be converted into a -b2DistanceProxy. There is also some caching used to warm start the -distance function for repeated calls. +`b2DistanceProxy` (which are point clouds with radii). There is also some caching used to warm start the +distance function for repeated calls. This can improve performance when the shapes move by small amounts. ![Distance Function](images/distance.svg) @@ -353,44 +254,57 @@ distance function for repeated calls. If two shapes are moving fast, they may *tunnel* through each other in a single time step. -![Tunneling](images/tunneling2.svg) +![Tunneling](images/tunneling2.svg){html: width=30%} -The `b2TimeOfImpact` function is used to determine the time when two -moving shapes collide. This is called the *time of impact* (TOI). The -main purpose of `b2TimeOfImpact` is for tunnel prevention. In particular, -it is designed to prevent moving objects from tunneling outside of -static level geometry. +The `b2TimeOfImpact()` function is used to determine the time when two moving shapes collide. +This is called the *time of impact* (TOI). The main purpose of `b2TimeOfImpact()` is for +tunnel prevention. Box2D uses this internally to prevent moving objects from tunneling through +static shapes. -This function accounts for rotation and translation of both shapes, -however if the rotations are large enough, then the function may miss a -collision. However the function will still report a non-overlapped time -and will capture all translational collisions. +The `b2TimeOfImpact()` identities an initial separating axis and +ensures the shapes do not cross on that axis. This process is repeated +as shapes are moved closer together, until they touch or pass by each other. -The time of impact function identities an initial separating axis and -ensures the shapes do not cross on that axis. This might miss collisions -that are clear at the final positions. While this approach may miss some -collisions, it is very fast and adequate for tunnel prevention. +The TOI function might miss collisions that are clear at the final positions. +Nevertheless, it is very fast and adequate for tunnel prevention. -![Captured Collision](images/captured_toi.svg) +![Captured Collision](images/captured_toi.svg){html: width=30%} -![Missed Collision](images/missed_toi.svg) +![Missed Collision](images/missed_toi.svg){html: width=30%} It is difficult to put a restriction on the rotation magnitude. There may be cases where collisions are missed for small rotations. Normally, these missed rotational collisions should not harm game play. They tend to be glancing collisions. -The function requires two shapes (converted to b2DistanceProxy) and two -b2Sweep structures. The sweep structure defines the initial and final +The function requires two shapes (converted to `b2DistanceProxy`) and two +`b2Sweep` structures. The sweep structure defines the initial and final transforms of the shapes. You can use fixed rotations to perform a *shape cast*. In this case, the time of impact function will not miss any collisions. +### Contact Manifolds +Box2D has functions to compute contact points for overlapping shapes. If +we consider circle-circle or circle-polygon, we can only get one contact +point and normal. In the case of polygon-polygon we can get two points. +These points share the same normal vector so Box2D groups them into a +manifold structure. The contact solver takes advantage of this to +improve stacking stability. + +![Contact Manifold](images/manifolds.svg) + +Normally you don't need to compute contact manifolds directly, however +you will likely use the results produced in the simulation. + +The `b2Manifold` structure holds a normal vector and up to two contact +points. The contact points store the normal and tangential (friction) impulses +computed in the rigid body simulation. + ## Dynamic Tree -The b2DynamicTree class is used by Box2D to organize large numbers of -shapes efficiently. The class does not know about shapes. Instead it -operates on axis-aligned bounding boxes (AABBs) with user data pointers. +`b2DynamicTree` is used by Box2D to organize large numbers of +shapes efficiently. The object does not know directly about shapes. Instead it +operates on axis-aligned bounding boxes (`b2AABB`) with user data integers. The dynamic tree is a hierarchical AABB tree. Each internal node in the tree has two children. A leaf node is a single user AABB. The tree uses @@ -409,25 +323,11 @@ A region query uses the tree to find all leaf AABBs that overlap a query AABB. This is faster than a brute force approach because many shapes can be skipped. -![Raycast](images/raycast.svg) +![Ray-cast](images/raycast.svg){html: width=30%} -![Overlap Test](images/overlap_test.svg) +![Overlap Test](images/overlap_test.svg){html: width=30%} Normally you will not use the dynamic tree directly. Rather you will go -through the b2World class for ray casts and region queries. If you plan +through the `b2World` functions for ray casts and region queries. If you plan to instantiate your own dynamic tree, you can learn how to use it by -looking at how Box2D uses it. - -## Broad-phase -Collision processing in a physics step can be divided into narrow-phase -and broad-phase. In the narrow-phase we compute contact points between -pairs of shapes. Imagine we have N shapes. Using brute force, we would -need to perform the narrow-phase for N*N/2 pairs. - -The b2BroadPhase class reduces this load by using a dynamic tree for -pair management. This greatly reduces the number of narrow-phase calls. - -Normally you do not interact with the broad-phase directly. Instead, -Box2D creates and manages a broad-phase internally. Also, b2BroadPhase -is designed with Box2D's simulation loop in mind, so it is likely not -suited for other use cases. +looking at how Box2D uses it. Also see the `DynamicTree` sample. diff --git a/docs/common.md b/docs/common.md deleted file mode 100644 index a859485c..00000000 --- a/docs/common.md +++ /dev/null @@ -1,66 +0,0 @@ -# Common Module (NOT UPDATED for Box2D version 3.0) -The Common module contains settings, memory management, and vector math. - -## Settings -The header b2Settings.h contains: -- Types such as int32 and float -- Constants -- Allocation wrappers -- The version number - -### Types -Box2D defines various types such as int8, etc. to make it easy -to determine the size of structures. - -### Constants -Box2D defines several constants. These are all documented in -b2Settings.h. Normally you do not need to adjust these constants. - -Box2D uses floating point math for collision and simulation. Due to -round-off error some numerical tolerances are defined. Some tolerances -are absolute and some are relative. Absolute tolerances use MKS units. - -### Allocation wrappers -The settings file defines b2Alloc and b2Free for large allocations. You -may forward these calls to your own memory management system. - -### Version -The b2Version structure holds the current version so you can query this -at run-time. - -## Memory Management -A large number of the decisions about the design of Box2D were based on -the need for quick and efficient use of memory. In this section I will -discuss how and why Box2D allocates memory. - -Box2D tends to allocate a large number of small objects (around 50-300 -bytes). Using the system heap through malloc or new for small objects is -inefficient and can cause fragmentation. Many of these small objects may -have a short life span, such as contacts, but can persist for several -time steps. So we need an allocator that can efficiently provide heap -memory for these objects. - -Box2D's solution is to use a small object allocator (SOA) called -b2BlockAllocator. The SOA keeps a number of growable pools of varying -sizes. When a request is made for memory, the SOA returns a block of -memory that best fits the requested size. When a block is freed, it is -returned to the pool. Both of these operations are fast and cause little -heap traffic. - -Since Box2D uses a SOA, you should never new or malloc a body, fixture, -or joint. However, you do have to allocate a b2World on your own. The -b2World class provides factories for you to create bodies, fixtures, and -joints. This allows Box2D to use the SOA and hide the gory details from -you. Never, call delete or free on a body, fixture, or joint. - -While executing a time step, Box2D needs some temporary workspace -memory. For this, it uses a stack allocator called b2StackAllocator to -avoid per-step heap allocations. You don't need to interact with the -stack allocator, but it's good to know it's there. - -## Math -Box2D includes a simple small vector and matrix module. This has been -designed to suit the internal needs of Box2D and the API. All the -members are exposed, so you may use them freely in your application. - -The math library is kept simple to make Box2D easy to port and maintain. diff --git a/docs/dynamics.md b/docs/dynamics.md deleted file mode 100644 index bc04b164..00000000 --- a/docs/dynamics.md +++ /dev/null @@ -1,1673 +0,0 @@ -# Dynamics Module (NOT UPDATED for Box2D version 3.0) -The Dynamics module is the most complex part of Box2D and is the part -you likely interact with the most. The Dynamics module sits on top of -the Common and Collision modules, so you should be somewhat familiar -with those by now. - -The Dynamics module contains: -- fixture class -- rigid body class -- contact class -- joint classes -- world class -- listener classes - -There are many dependencies between these classes so it is difficult to -describe one class without referring to another. In the following, you -may see some references to classes that have not been described yet. -Therefore, you may want to quickly skim this chapter before reading it -closely. - -The dynamics module is covered in the following chapters. - -## Bodies -Bodies have position and velocity. You can apply forces, torques, and -impulses to bodies. Bodies can be static, kinematic, or dynamic. Here -are the body type definitions: - -### Body types - -#### b2_staticBody -A static body does not move under simulation and behaves as if it has -infinite mass. Internally, Box2D stores zero for the mass and the -inverse mass. Static bodies can be moved manually by the user. A static -body has zero velocity. Static bodies do not collide with other static -or kinematic bodies. - -#### b2_kinematicBody -A kinematic body moves under simulation according to its velocity. -Kinematic bodies do not respond to forces. They can be moved manually by -the user, but normally a kinematic body is moved by setting its -velocity. A kinematic body behaves as if it has infinite mass, however, -Box2D stores zero for the mass and the inverse mass. Kinematic bodies do -not collide with other kinematic or static bodies. - -#### b2_dynamicBody -A dynamic body is fully simulated. They can be moved manually by the -user, but normally they move according to forces. A dynamic body can -collide with all body types. A dynamic body always has finite, non-zero -mass. If you try to set the mass of a dynamic body to zero, it will -automatically acquire a mass of one kilogram and it won't rotate. - -Bodies are the backbone for fixtures (shapes). Bodies carry fixtures and -move them around in the world. Bodies are always rigid bodies in Box2D. -That means that two fixtures attached to the same rigid body never move -relative to each other and fixtures attached to the same body don't -collide. - -Fixtures have collision geometry and density. Normally, bodies acquire -their mass properties from the fixtures. However, you can override the -mass properties after a body is constructed. - -You usually keep pointers to all the bodies you create. This way you can -query the body positions to update the positions of your graphical -entities. You should also keep body pointers so you can destroy them -when you are done with them. - -### Body Definition -Before a body is created you must create a body definition (b2BodyDef). -The body definition holds the data needed to create and initialize a -body. - -Box2D copies the data out of the body definition; it does not keep a -pointer to the body definition. This means you can recycle a body -definition to create multiple bodies. - -Let's go over some of the key members of the body definition. - -### Body Type -As discussed at the beginning of this chapter, there are three different -body types: static, kinematic, and dynamic. You should establish the -body type at creation because changing the body type later is expensive. - -```cpp -b2BodyDef bodyDef; -bodyDef.type = b2_dynamicBody; -``` - -Setting the body type is mandatory. - -### Position and Angle -The body definition gives you the chance to initialize the position of -the body on creation. This has far better performance than creating the -body at the world origin and then moving the body. - -> **Caution**: -> Do not create a body at the origin and then move it. If you create -> several bodies at the origin, then performance will suffer. - -A body has two main points of interest. The first point is the body's -origin. Fixtures and joints are attached relative to the body's origin. -The second point of interest is the center of mass. The center of mass -is determined from mass distribution of the attached shapes or is -explicitly set with b2MassData. Much of Box2D's internal computations -use the center of mass position. For example b2Body stores the linear -velocity for the center of mass. - -When you are building the body definition, you may not know where the -center of mass is located. Therefore you specify the position of the -body's origin. You may also specify the body's angle in radians, which -is not affected by the position of the center of mass. If you later -change the mass properties of the body, then the center of mass may move -on the body, but the origin position does not change and the attached -shapes and joints do not move. - -```cpp -b2BodyDef bodyDef; -bodyDef.position.Set(0.0f, 2.0f); // the body's origin position. -bodyDef.angle = 0.25f * b2_pi; // the body's angle in radians. -``` - -A rigid body is also a frame of reference. You can define fixtures and -joints in that frame. Those fixtures and joint anchors never move in the -local frame of the body. - -### Damping -Damping is used to reduce the world velocity of bodies. Damping is -different than friction because friction only occurs with contact. -Damping is not a replacement for friction and the two effects should be -used together. - -Damping parameters should be between 0 and infinity, with 0 meaning no -damping, and infinity meaning full damping. Normally you will use a -damping value between 0 and 0.1. I generally do not use linear damping -because it makes bodies look like they are floating. - -```cpp -b2BodyDef bodyDef; -bodyDef.linearDamping = 0.0f; -bodyDef.angularDamping = 0.01f; -``` - -Damping is approximated for stability and performance. At small damping -values the damping effect is mostly independent of the time step. At -larger damping values, the damping effect will vary with the time step. -This is not an issue if you use a fixed time step (recommended). - -### Gravity Scale -You can use the gravity scale to adjust the gravity on a single body. Be -careful though, increased gravity can decrease stability. - -```cpp -// Set the gravity scale to zero so this body will float -b2BodyDef bodyDef; -bodyDef.gravityScale = 0.0f; -``` - -### Sleep Parameters -What does sleep mean? Well it is expensive to simulate bodies, so the -less we have to simulate the better. When a body comes to rest we would -like to stop simulating it. - -When Box2D determines that a body (or group of bodies) has come to rest, -the body enters a sleep state which has very little CPU overhead. If a -body is awake and collides with a sleeping body, then the sleeping body -wakes up. Bodies will also wake up if a joint or contact attached to -them is destroyed. You can also wake a body manually. - -The body definition lets you specify whether a body can sleep and -whether a body is created sleeping. - -```cpp -b2BodyDef bodyDef; -bodyDef.allowSleep = true; -bodyDef.awake = true; -``` - -### Fixed Rotation -You may want a rigid body, such as a character, to have a fixed -rotation. Such a body should not rotate, even under load. You can use -the fixed rotation setting to achieve this: - -```cpp -b2BodyDef bodyDef; -bodyDef.fixedRotation = true; -``` - -The fixed rotation flag causes the rotational inertia and its inverse to -be set to zero. - -### Bullets -Game simulation usually generates a sequence of images that are played -at some frame rate. This is called discrete simulation. In discrete -simulation, rigid bodies can move by a large amount in one time step. If -a physics engine doesn't account for the large motion, you may see some -objects incorrectly pass through each other. This effect is called -tunneling. - -By default, Box2D uses continuous collision detection (CCD) to prevent -dynamic bodies from tunneling through static bodies. This is done by -sweeping shapes from their old position to their new positions. The -engine looks for new collisions during the sweep and computes the time -of impact (TOI) for these collisions. Bodies are moved to their first -TOI and then the solver performs a sub-step to complete the full time -step. There may be additional TOI events within a sub-step. - -Normally CCD is not used between dynamic bodies. This is done to keep -performance reasonable. In some game scenarios you need dynamic bodies -to use CCD. For example, you may want to shoot a high speed bullet at a -stack of dynamic bricks. Without CCD, the bullet might tunnel through -the bricks. - -Fast moving objects in Box2D can be labeled as bullets. Bullets will -perform CCD with both static and dynamic bodies. You should decide what -bodies should be bullets based on your game design. If you decide a body -should be treated as a bullet, use the following setting. - -```cpp -b2BodyDef bodyDef; -bodyDef.bullet = true; -``` - -The bullet flag only affects dynamic bodies. - -### Activation -You may wish a body to be created but not participate in collision or -dynamics. This state is similar to sleeping except the body will not be -woken by other bodies and the body's fixtures will not be placed in the -broad-phase. This means the body will not participate in collisions, ray -casts, etc. - -You can create a body in an inactive state and later re-activate it. - -```cpp -b2BodyDef bodyDef; -bodyDef.active = true; -``` - -Joints may be connected to inactive bodies. These joints will not be -simulated. You should be careful when you activate a body that its -joints are not distorted. - -Note that activating a body is almost as expensive as creating the body -from scratch. So you should not use activation for streaming worlds. Use -creation/destruction for streaming worlds to save memory. - -### User Data -User data is a void pointer. This gives you a hook to link your -application objects to bodies. You should be consistent to use the same -object type for all body user data. - -```cpp -b2BodyDef bodyDef; -bodyDef.userData.pointer = reinterpret_cast(&myActor); -``` - -### Body Factory -Bodies are created and destroyed using a body factory provided by the -world class. This lets the world create the body with an efficient -allocator and add the body to the world data structure. - -```cpp -b2World* myWorld; -b2Body* dynamicBody = myWorld->CreateBody(&bodyDef); - -// ... do stuff ... - -myWorld->DestroyBody(dynamicBody); -dynamicBody = nullptr; -``` - -> **Caution**: -> You should never use new or malloc to create a body. The world won't -> know about the body and the body won't be properly initialized. - -Box2D does not keep a reference to the body definition or any of the -data it holds (except user data pointers). So you can create temporary -body definitions and reuse the same body definitions. - -Box2D allows you to avoid destroying bodies by deleting your b2World -object, which does all the cleanup work for you. However, you should be -mindful to nullify body pointers that you keep in your game engine. - -When you destroy a body, the attached fixtures and joints are -automatically destroyed. This has important implications for how you -manage shape and joint pointers. - -### Using a Body -After creating a body, there are many operations you can perform on the -body. These include setting mass properties, accessing position and -velocity, applying forces, and transforming points and vectors. - -### Mass Data -A body has mass (scalar), center of mass (2-vector), and rotational -inertia (scalar). For static bodies, the mass and rotational inertia are -set to zero. When a body has fixed rotation, its rotational inertia is -zero. - -Normally the mass properties of a body are established automatically -when fixtures are added to the body. You can also adjust the mass of a -body at run-time. This is usually done when you have special game -scenarios that require altering the mass. - -```cpp -void b2Body::SetMassData(const b2MassData* data); -``` - -After setting a body's mass directly, you may wish to revert to the -natural mass dictated by the fixtures. You can do this with: - -```cpp -void b2Body::ResetMassData(); -``` - -The body's mass data is available through the following functions: - -```cpp -float b2Body::GetMass() const; -float b2Body::GetInertia() const; -const b2Vec2& b2Body::GetLocalCenter() const; -void b2Body::GetMassData(b2MassData* data) const; -``` - -### State Information -There are many aspects to the body's state. You can access this state -data efficiently through the following functions: - -```cpp -void b2Body::SetType(b2BodyType type); -b2BodyType b2Body::GetType(); -void b2Body::SetBullet(bool flag); -bool b2Body::IsBullet() const; -void b2Body::SetSleepingAllowed(bool flag); -bool b2Body::IsSleepingAllowed() const; -void b2Body::SetAwake(bool flag); -bool b2Body::IsAwake() const; -void b2Body::SetEnabled(bool flag); -bool b2Body::IsEnabled() const; -void b2Body::SetFixedRotation(bool flag); -bool b2Body::IsFixedRotation() const; -``` - -### Position and Velocity -You can access the position and rotation of a body. This is common when -rendering your associated game actor. You can also set the position, -although this is less common since you will normally use Box2D to -simulate movement. - -```cpp -bool b2Body::SetTransform(const b2Vec2& position, float angle); -const b2Transform& b2Body::GetTransform() const; -const b2Vec2& b2Body::GetPosition() const; -float b2Body::GetAngle() const; -``` - -You can access the center of mass position in local and world -coordinates. Much of the internal simulation in Box2D uses the center of -mass. However, you should normally not need to access it. Instead you -will usually work with the body transform. For example, you may have a -body that is square. The body origin might be a corner of the square, -while the center of mass is located at the center of the square. - -```cpp -const b2Vec2& b2Body::GetWorldCenter() const; -const b2Vec2& b2Body::GetLocalCenter() const; -``` - -You can access the linear and angular velocity. The linear velocity is -for the center of mass. Therefore, the linear velocity may change if the -mass properties change. - -### Forces and Impulses -You can apply forces, torques, and impulses to a body. When you apply a -force or an impulse, you provide a world point where the load is -applied. This often results in a torque about the center of mass. - -```cpp -void b2Body::ApplyForce(const b2Vec2& force, const b2Vec2& point); -void b2Body::ApplyTorque(float torque); -void b2Body::ApplyLinearImpulse(const b2Vec2& impulse, const b2Vec2& point); -void b2Body::ApplyAngularImpulse(float impulse); -``` - -Applying a force, torque, or impulse wakes the body. Sometimes this is -undesirable. For example, you may be applying a steady force and want to -allow the body to sleep to improve performance. In this case you can use -the following code. - -```cpp -if (myBody->IsAwake() == true) -{ - myBody->ApplyForce(myForce, myPoint); -} -``` - -### Coordinate Transformations -The body class has some utility functions to help you transform points -and vectors between local and world space. If you don't understand -these concepts, please read \"Essential Mathematics for Games and -Interactive Applications\" by Jim Van Verth and Lars Bishop. These -functions are efficient (when inlined). - -```cpp -b2Vec2 b2Body::GetWorldPoint(const b2Vec2& localPoint); -b2Vec2 b2Body::GetWorldVector(const b2Vec2& localVector); -b2Vec2 b2Body::GetLocalPoint(const b2Vec2& worldPoint); -b2Vec2 b2Body::GetLocalVector(const b2Vec2& worldVector); -``` - -### Acessing Fixtures, Joints, and Contacts -You can iterate over a body's fixtures. This is mainly useful if you -need to access the fixture's user data. - -```cpp -for (b2Fixture* f = body->GetFixtureList(); f; f = f->GetNext()) -{ - MyFixtureData* data = (MyFixtureData*)f->GetUserData(); - // do something with data ... -} -``` - -You can similarly iterate over the body's joint list. - -The body also provides a list of associated contacts. You can use this -to get information about the current contacts. Be careful, because the -contact list may not contain all the contacts that existed during the -previous time step. - -## Fixtures -Recall that shapes don't know about bodies and may be used independently -of the physics simulation. Therefore Box2D provides the b2Fixture class -to attach shapes to bodies. A body may have zero or more fixtures. A -body with multiple fixtures is sometimes called a *compound body.* - -Fixtures hold the following: -- a single shape -- broad-phase proxies -- density, friction, and restitution -- collision filtering flags -- back pointer to the parent body -- user data -- sensor flag - -These are described in the following sections. - -### Fixture Creation -Fixtures are created by initializing a fixture definition and then -passing the definition to the parent body. - -```cpp -b2Body* myBody; -b2FixtureDef fixtureDef; -fixtureDef.shape = &myShape; -fixtureDef.density = 1.0f; -b2Fixture* myFixture = myBody->CreateFixture(&fixtureDef); -``` - -This creates the fixture and attaches it to the body. You do not need to -store the fixture pointer since the fixture will automatically be -destroyed when the parent body is destroyed. You can create multiple -fixtures on a single body. - -You can destroy a fixture on the parent body. You may do this to model a -breakable object. Otherwise you can just leave the fixture alone and let -the body destruction take care of destroying the attached fixtures. - -```cpp -myBody->DestroyFixture(myFixture); -``` - -Material properties such as density, friction, and restitution are associted with shapes instead of bodies. Since you can attach multiple shapes to a body, this allows for more possible setups. For example, you can make a car that is heavier in the back. - -### Density -The fixture density is used to compute the mass properties of the parent -body. The density can be zero or positive. You should generally use -similar densities for all your fixtures. This will improve stacking -stability. - -The mass of a body is not adjusted when you set the density. You must -call ResetMassData for this to occur. - -```cpp -b2Fixture* fixture; -fixture->SetDensity(5.0f); -b2Body* -body->ResetMassData(); -``` - -### Friction -Friction is used to make objects slide along each other realistically. -Box2D supports static and dynamic friction, but uses the same parameter -for both. Friction is simulated accurately in Box2D and the friction -strength is proportional to the normal force (this is called Coulomb -friction). The friction parameter is usually set between 0 and 1, but -can be any non-negative value. A friction value of 0 turns off friction -and a value of 1 makes the friction strong. When the friction force is -computed between two shapes, Box2D must combine the friction parameters -of the two parent fixtures. This is done with the geometric mean: - -```cpp -b2Fixture* fixtureA; -b2Fixture* fixtureB; -float friction; -friction = sqrtf(fixtureA->friction * fixtureB->friction); -``` - -So if one fixture has zero friction then the contact will have zero -friction. - -You can override the default mixed friction using -`b2Contact::SetFriction`. This is usually done in the `b2ContactListener` -callback. - -### Restitution -Restitution is used to make objects bounce. The restitution value is -usually set to be between 0 and 1. Consider dropping a ball on a table. -A value of zero means the ball won't bounce. This is called an -inelastic collision. A value of one means the ball's velocity will be -exactly reflected. This is called a perfectly elastic collision. -Restitution is combined using the following formula. - -```cpp -b2Fixture* fixtureA; -b2Fixture* fixtureB; -float restitution; -restitution = b2Max(fixtureA->restitution, fixtureB->restitution); -``` - -Restitution is combined this way so that you can have a bouncy super -ball without having a bouncy floor. - -You can override the default mixed restitution using -`b2Contact::SetRestitution`. This is usually done in the b2ContactListener -callback. - -When a shape develops multiple contacts, restitution is simulated -approximately. This is because Box2D uses an iterative solver. Box2D -also uses inelastic collisions when the collision velocity is small. -This is done to prevent jitter. See `b2_velocityThreshold`. - -### Filtering -Collision filtering allows you to prevent collision between fixtures. -For example, say you make a character that rides a bicycle. You want the -bicycle to collide with the terrain and the character to collide with -the terrain, but you don't want the character to collide with the -bicycle (because they must overlap). Box2D supports such collision -filtering using categories and groups. - -Box2D supports 16 collision categories. For each fixture you can specify -which category it belongs to. You also specify what other categories -this fixture can collide with. For example, you could specify in a -multiplayer game that all players don't collide with each other and -monsters don't collide with each other, but players and monsters should -collide. This is done with masking bits. For example: - -```cpp -b2FixtureDef playerFixtureDef, monsterFixtureDef; -playerFixtureDef.filter.categoryBits = 0x0002; -monsterFixtureDef.filter.categoryBits = 0x0004; -playerFixtureDef.filter.maskBits = 0x0004; -monsterFixtureDef.filter.maskBits = 0x0002; -``` - -Here is the rule for a collision to occur: - -```cpp -uint16 catA = fixtureA.filter.categoryBits; -uint16 maskA = fixtureA.filter.maskBits; -uint16 catB = fixtureB.filter.categoryBits; -uint16 maskB = fixtureB.filter.maskBits; - -if ((catA & maskB) != 0 && (catB & maskA) != 0) -{ - // fixtures can collide -} -``` - -Collision groups let you specify an integral group index. You can have -all fixtures with the same group index always collide (positive index) -or never collide (negative index). Group indices are usually used for -things that are somehow related, like the parts of a bicycle. In the -following example, fixture1 and fixture2 always collide, but fixture3 -and fixture4 never collide. - -```cpp -fixture1Def.filter.groupIndex = 2; -fixture2Def.filter.groupIndex = 2; -fixture3Def.filter.groupIndex = -8; -fixture4Def.filter.groupIndex = -8; -``` - -Collisions between fixtures of different group indices are filtered -according the category and mask bits. In other words, group filtering -has higher precedence than category filtering. - -Note that additional collision filtering occurs in Box2D. Here is a -list: -- A fixture on a static body can only collide with a dynamic body. -- A fixture on a kinematic body can only collide with a dynamic body. -- Fixtures on the same body never collide with each other. -- You can optionally enable/disable collision between fixtures on bodies connected by a joint. - -Sometimes you might need to change collision filtering after a fixture -has already been created. You can get and set the b2Filter structure on -an existing fixture using b2Fixture::GetFilterData and -b2Fixture::SetFilterData. Note that changing the filter data will not -add or remove contacts until the next time step (see the World class). - -### Sensors -Sometimes game logic needs to know when two fixtures overlap yet there -should be no collision response. This is done by using sensors. A sensor -is a fixture that detects collision but does not produce a response. - -You can flag any fixture as being a sensor. Sensors may be static, -kinematic, or dynamic. Remember that you may have multiple fixtures per -body and you can have any mix of sensors and solid fixtures. Also, -sensors only form contacts when at least one body is dynamic, so you -will not get a contact for kinematic versus kinematic, kinematic versus -static, or static versus static. - -Sensors do not generate contact points. There are two ways to get the -state of a sensor: -1. `b2Contact::IsTouching` -2. `b2ContactListener::BeginContact` and `b2ContactListener::EndContact` - -## Joints -Joints are used to constrain bodies to the world or to each other. -Typical examples in games include ragdolls, teeters, and pulleys. Joints -can be combined in many different ways to create interesting motions. - -Some joints provide limits so you can control the range of motion. Some -joint provide motors which can be used to drive the joint at a -prescribed speed until a prescribed force/torque is exceeded. - -Joint motors can be used in many ways. You can use motors to control -position by specifying a joint velocity that is proportional to the -difference between the actual and desired position. You can also use -motors to simulate joint friction: set the joint velocity to zero and -provide a small, but significant maximum motor force/torque. Then the -motor will attempt to keep the joint from moving until the load becomes -too strong. - -### Joint Definition -Each joint type has a definition that derives from b2JointDef. All -joints are connected between two different bodies. One body may be static. -Joints between static and/or kinematic bodies are allowed, but have no -effect and use some processing time. - -You can specify user data for any joint type and you can provide a flag -to prevent the attached bodies from colliding with each other. This is -actually the default behavior and you must set the collideConnected -Boolean to allow collision between to connected bodies. - -Many joint definitions require that you provide some geometric data. -Often a joint will be defined by anchor points. These are points fixed -in the attached bodies. Box2D requires these points to be specified in -local coordinates. This way the joint can be specified even when the -current body transforms violate the joint constraint \-\-- a common -occurrence when a game is saved and reloaded. Additionally, some joint -definitions need to know the default relative angle between the bodies. -This is necessary to constrain rotation correctly. - -Initializing the geometric data can be tedious, so many joints have -initialization functions that use the current body transforms to remove -much of the work. However, these initialization functions should usually -only be used for prototyping. Production code should define the geometry -directly. This will make joint behavior more robust. - -The rest of the joint definition data depends on the joint type. We -cover these now. - -### Joint Factory -Joints are created and destroyed using the world factory methods. This -brings up an old issue: - -> **Caution**: -> Don't try to create a joint on the stack or on the heap using new or -> malloc. You must create and destroy bodies and joints using the create -> and destroy methods of the b2World class. - -Here's an example of the lifetime of a revolute joint: - -```cpp -b2World* myWorld; -b2RevoluteJointDef jointDef; -jointDef.bodyA = myBodyA; -jointDef.bodyB = myBodyB; -jointDef.anchorPoint = myBodyA->GetCenterPosition(); - -b2RevoluteJoint* joint = (b2RevoluteJoint*)myWorld->CreateJoint(&jointDef); - -// ... do stuff ... - -myWorld->DestroyJoint(joint); -joint = nullptr; -``` - -It is always good to nullify your pointer after they are destroyed. This -will make the program crash in a controlled manner if you try to reuse -the pointer. - -The lifetime of a joint is not simple. Heed this warning well: - -> **Caution**: -> Joints are destroyed when an attached body is destroyed. - -This precaution is not always necessary. You may organize your game -engine so that joints are always destroyed before the attached bodies. -In this case you don't need to implement the listener class. See the -section on Implicit Destruction for details. - -### Using Joints -Many simulations create the joints and don't access them again until -they are destroyed. However, there is a lot of useful data contained in -joints that you can use to create a rich simulation. - -First of all, you can get the bodies, anchor points, and user data from -a joint. - -```cpp -b2Body* b2Joint::GetBodyA(); -b2Body* b2Joint::GetBodyB(); -b2Vec2 b2Joint::GetAnchorA(); -b2Vec2 b2Joint::GetAnchorB(); -void* b2Joint::GetUserData(); -``` - -All joints have a reaction force and torque. This the reaction force -applied to body 2 at the anchor point. You can use reaction forces to -break joints or trigger other game events. These functions may do some -computations, so don't call them if you don't need the result. - -```cpp -b2Vec2 b2Joint::GetReactionForce(); -float b2Joint::GetReactionTorque(); -``` - -### Distance Joint -One of the simplest joint is a distance joint which says that the -distance between two points on two bodies must be constant. When you -specify a distance joint the two bodies should already be in place. Then -you specify the two anchor points in world coordinates. The first anchor -point is connected to body 1, and the second anchor point is connected -to body 2. These points imply the length of the distance constraint. - -![Distance Joint](images/distance_joint.gif) - -Here is an example of a distance joint definition. In this case we -decide to allow the bodies to collide. - -```cpp -b2DistanceJointDef jointDef; -jointDef.Initialize(myBodyA, myBodyB, worldAnchorOnBodyA, -worldAnchorOnBodyB); -jointDef.collideConnected = true; -``` - -The distance joint can also be made soft, like a spring-damper -connection. See the Web example in the testbed to see how this behaves. - -Softness is achieved by tuning two constants in the definition: -stiffness and damping. It can be non-intuitive setting these values directly -since they have units in terms on Newtons. Box2D provides and API to compute -these values in terms of frequency and damping ratio. -```cpp -void b2LinearStiffness(float& stiffness, float& damping, - float frequencyHertz, float dampingRatio, - const b2Body* bodyA, const b2Body* bodyB); -``` - -Think of the frequency as the frequency of a harmonic oscillator (like a -guitar string). The frequency is specified in Hertz. Typically the frequency -should be less than a half the frequency of the time step. So if you are using -a 60Hz time step, the frequency of the distance joint should be less than 30Hz. -The reason is related to the Nyquist frequency. - -The damping ratio is non-dimensional and is typically between 0 and 1, -but can be larger. At 1, the damping is critical (all oscillations -should vanish). - -```cpp -float frequencyHz = 4.0f; -float dampingRatio = 0.5f; -b2LinearStiffness(jointDef.stiffness, jointDef.damping, frequencyHz, dampingRatio, jointDef.bodyA, jointDef.bodyB); -``` - -It is also possible to define a minimum and maximum length for the distance joint. -See `b2DistanceJointDef` for details. - -### Revolute Joint -A revolute joint forces two bodies to share a common anchor point, often -called a hinge point. The revolute joint has a single degree of freedom: -the relative rotation of the two bodies. This is called the joint angle. - -![Revolute Joint](images/revolute_joint.gif) - -To specify a revolute you need to provide two bodies and a single anchor -point in world space. The initialization function assumes that the -bodies are already in the correct position. - -In this example, two bodies are connected by a revolute joint at the -first body's center of mass. - -```cpp -b2RevoluteJointDef jointDef; -jointDef.Initialize(myBodyA, myBodyB, myBodyA->GetWorldCenter()); -``` - -The revolute joint angle is positive when bodyB rotates CCW about the -angle point. Like all angles in Box2D, the revolute angle is measured in -radians. By convention the revolute joint angle is zero when the joint -is created using Initialize(), regardless of the current rotation of the -two bodies. - -In some cases you might wish to control the joint angle. For this, the -revolute joint can optionally simulate a joint limit and/or a motor. - -A joint limit forces the joint angle to remain between a lower and upper -bound. The limit will apply as much torque as needed to make this -happen. The limit range should include zero, otherwise the joint will -lurch when the simulation begins. - -A joint motor allows you to specify the joint speed (the time derivative -of the angle). The speed can be negative or positive. A motor can have -infinite force, but this is usually not desirable. Recall the eternal -question: - -> *What happens when an irresistible force meets an immovable object?* - -I can tell you it's not pretty. So you can provide a maximum torque for -the joint motor. The joint motor will maintain the specified speed -unless the required torque exceeds the specified maximum. When the -maximum torque is exceeded, the joint will slow down and can even -reverse. - -You can use a joint motor to simulate joint friction. Just set the joint -speed to zero, and set the maximum torque to some small, but significant -value. The motor will try to prevent the joint from rotating, but will -yield to a significant load. - -Here's a revision of the revolute joint definition above; this time the -joint has a limit and a motor enabled. The motor is setup to simulate -joint friction. - -```cpp -b2RevoluteJointDef jointDef; -jointDef.Initialize(bodyA, bodyB, myBodyA->GetWorldCenter()); -jointDef.lowerAngle = -0.5f * b2_pi; // -90 degrees -jointDef.upperAngle = 0.25f * b2_pi; // 45 degrees -jointDef.enableLimit = true; -jointDef.maxMotorTorque = 10.0f; -jointDef.motorSpeed = 0.0f; -jointDef.enableMotor = true; -``` -You can access a revolute joint's angle, speed, and motor torque. - -```cpp -float b2RevoluteJoint::GetJointAngle() const; -float b2RevoluteJoint::GetJointSpeed() const; -float b2RevoluteJoint::GetMotorTorque() const; -``` - -You also update the motor parameters each step. - -```cpp -void b2RevoluteJoint::SetMotorSpeed(float speed); -void b2RevoluteJoint::SetMaxMotorTorque(float torque); -``` - -Joint motors have some interesting abilities. You can update the joint -speed every time step so you can make the joint move back-and-forth like -a sine-wave or according to whatever function you want. - -```cpp -// ... Game Loop Begin ... - -myJoint->SetMotorSpeed(cosf(0.5f * time)); - -// ... Game Loop End ... -``` - -You can also use joint motors to track a desired joint angle. For example: - -```cpp -// ... Game Loop Begin ... - -float angleError = myJoint->GetJointAngle() - angleTarget; -float gain = 0.1f; -myJoint->SetMotorSpeed(-gain * angleError); - -// ... Game Loop End ... -``` - -Generally your gain parameter should not be too large. Otherwise your -joint may become unstable. - -### Prismatic Joint -A prismatic joint allows for relative translation of two bodies along a -specified axis. A prismatic joint prevents relative rotation. Therefore, -a prismatic joint has a single degree of freedom. - -![Prismatic Joint](images/prismatic_joint.gif) - -The prismatic joint definition is similar to the revolute joint -description; just substitute translation for angle and force for torque. -Using this analogy provides an example prismatic joint definition with a -joint limit and a friction motor: - -```cpp -b2PrismaticJointDef jointDef; -b2Vec2 worldAxis(1.0f, 0.0f); -jointDef.Initialize(myBodyA, myBodyB, myBodyA->GetWorldCenter(), worldAxis); -jointDef.lowerTranslation = -5.0f; -jointDef.upperTranslation = 2.5f; -jointDef.enableLimit = true; -jointDef.maxMotorForce = 1.0f; -jointDef.motorSpeed = 0.0f; -jointDef.enableMotor = true; -``` - -The revolute joint has an implicit axis coming out of the screen. The -prismatic joint needs an explicit axis parallel to the screen. This axis -is fixed in the two bodies and follows their motion. - -Like the revolute joint, the prismatic joint translation is zero when -the joint is created using Initialize(). So be sure zero is between your -lower and upper translation limits. - -Using a prismatic joint is similar to using a revolute joint. Here are -the relevant member functions: - -```cpp -float PrismaticJoint::GetJointTranslation() const; -float PrismaticJoint::GetJointSpeed() const; -float PrismaticJoint::GetMotorForce() const; -void PrismaticJoint::SetMotorSpeed(float speed); -void PrismaticJoint::SetMotorForce(float force); -``` - -### Pulley Joint -A pulley is used to create an idealized pulley. The pulley connects two -bodies to ground and to each other. As one body goes up, the other goes -down. The total length of the pulley rope is conserved according to the -initial configuration. - -``` -length1 + length2 == constant -``` - -You can supply a ratio that simulates a block and tackle. This causes -one side of the pulley to extend faster than the other. At the same time -the constraint force is smaller on one side than the other. You can use -this to create mechanical leverage. - -``` -length1 + ratio * length2 == constant -``` - -For example, if the ratio is 2, then length1 will vary at twice the rate -of length2. Also the force in the rope attached to body1 will have half -the constraint force as the rope attached to body2. - -![Pulley Joint](images/pulley_joint.gif) - -Pulleys can be troublesome when one side is fully extended. The rope on -the other side will have zero length. At this point the constraint -equations become singular (bad). You should configure collision shapes -to prevent this. - -Here is an example pulley definition: - -```cpp -b2Vec2 anchor1 = myBody1->GetWorldCenter(); -b2Vec2 anchor2 = myBody2->GetWorldCenter(); - -b2Vec2 groundAnchor1(p1.x, p1.y + 10.0f); -b2Vec2 groundAnchor2(p2.x, p2.y + 12.0f); - -float ratio = 1.0f; - -b2PulleyJointDef jointDef; -jointDef.Initialize(myBody1, myBody2, groundAnchor1, groundAnchor2, anchor1, anchor2, ratio); -``` - -Pulley joints provide the current lengths. - -```cpp -float PulleyJoint::GetLengthA() const; -float PulleyJoint::GetLengthB() const; -``` - -### Gear Joint -If you want to create a sophisticated mechanical contraption you might -want to use gears. In principle you can create gears in Box2D by using -compound shapes to model gear teeth. This is not very efficient and -might be tedious to author. You also have to be careful to line up the -gears so the teeth mesh smoothly. Box2D has a simpler method of creating -gears: the gear joint. - -![Gear Joint](images/gear_joint.gif) - -The gear joint can only connect revolute and/or prismatic joints. - -Like the pulley ratio, you can specify a gear ratio. However, in this -case the gear ratio can be negative. Also keep in mind that when one -joint is a revolute joint (angular) and the other joint is prismatic -(translation), and then the gear ratio will have units of length or one -over length. - -``` -coordinate1 + ratio * coordinate2 == constant -``` - -Here is an example gear joint. The bodies myBodyA and myBodyB are any -bodies from the two joints, as long as they are not the same bodies. - -```cpp -b2GearJointDef jointDef; -jointDef.bodyA = myBodyA; -jointDef.bodyB = myBodyB; -jointDef.joint1 = myRevoluteJoint; -jointDef.joint2 = myPrismaticJoint; -jointDef.ratio = 2.0f * b2_pi / myLength; -``` - -Note that the gear joint depends on two other joints. This creates a -fragile situation. What happens if those joints are deleted? - -> **Caution**: -> Always delete gear joints before the revolute/prismatic joints on the -> gears. Otherwise your code will crash in a bad way due to the orphaned -> joint pointers in the gear joint. You should also delete the gear joint -> before you delete any of the bodies involved. - -### Mouse Joint -The mouse joint is used in the testbed to manipulate bodies with the -mouse. It attempts to drive a point on a body towards the current -position of the cursor. There is no restriction on rotation. - -The mouse joint definition has a target point, maximum force, frequency, -and damping ratio. The target point initially coincides with the body's -anchor point. The maximum force is used to prevent violent reactions -when multiple dynamic bodies interact. You can make this as large as you -like. The frequency and damping ratio are used to create a spring/damper -effect similar to the distance joint. - -Many users have tried to adapt the mouse joint for game play. Users -often want to achieve precise positioning and instantaneous response. -The mouse joint doesn't work very well in that context. You may wish to -consider using kinematic bodies instead. - -### Wheel Joint -The wheel joint restricts a point on bodyB to a line on bodyA. The wheel -joint also provides a suspension spring. See b2WheelJoint.h and Car.h -for details. - -![Wheel Joint](images/wheel_joint.svg) - -### Weld Joint -The weld joint attempts to constrain all relative motion between two -bodies. See the Cantilever.h in the testbed to see how the weld joint -behaves. - -It is tempting to use the weld joint to define breakable structures. -However, the Box2D solver is iterative so the joints are a bit soft. So -chains of bodies connected by weld joints will flex. - -Instead it is better to create breakable bodies starting with a single -body with multiple fixtures. When the body breaks, you can destroy a -fixture and recreate it on a new body. See the Breakable example in the -testbed. - -### Friction Joint -The friction joint is used for top-down friction. The joint provides 2D -translational friction and angular friction. See b2FrictionJoint.h and -apply_force.cpp for details. - -### Motor Joint -A motor joint lets you control the motion of a body by specifying target -position and rotation offsets. You can set the maximum motor force and -torque that will be applied to reach the target position and rotation. -If the body is blocked, it will stop and the contact forces will be -proportional the maximum motor force and torque. See b2MotorJoint and -motor_joint.cpp for details. - -### Wheel Joint -The wheel joint is designed specifically for vehicles. It provides a translation -and rotation. The translation has a spring and damper to simulate the vehicle -suspension. The rotation allows the wheel to rotate. You can specify an rotational -motor to drive the wheel and to apply braking. See b2WheelJoint, wheel_joint.cpp, -and car.cpp for details. - -## Contacts -Contacts are objects created by Box2D to manage collision between two -fixtures. If the fixture has children, such as a chain shape, then a -contact exists for each relevant child. There are different kinds of -contacts, derived from b2Contact, for managing contact between different -kinds of fixtures. For example there is a contact class for managing -polygon-polygon collision and another contact class for managing -circle-circle collision. - -Here is some terminology associated with contacts. - -##### contact point -A contact point is a point where two shapes touch. Box2D approximates -contact with a small number of points. - -##### contact normal -A contact normal is a unit vector that points from one shape to another. -By convention, the normal points from fixtureA to fixtureB. - -##### contact separation -Separation is the opposite of penetration. Separation is negative when -shapes overlap. It is possible that future versions of Box2D will create -contact points with positive separation, so you may want to check the -sign when contact points are reported. - -##### contact manifold -Contact between two convex polygons may generate up to 2 contact points. -Both of these points use the same normal, so they are grouped into a -contact manifold, which is an approximation of a continuous region of -contact. - -##### normal impulse -The normal force is the force applied at a contact point to prevent the -shapes from penetrating. For convenience, Box2D works with impulses. The -normal impulse is just the normal force multiplied by the time step. - -##### tangent impulse -The tangent force is generated at a contact point to simulate friction. -For convenience, this is stored as an impulse. - -##### contact ids -Box2D tries to re-use the contact force results from a time step as the -initial guess for the next time step. Box2D uses contact ids to match -contact points across time steps. The ids contain geometric features -indices that help to distinguish one contact point from another. - -Contacts are created when two fixture's AABBs overlap. Sometimes -collision filtering will prevent the creation of contacts. Contacts are -destroyed with the AABBs cease to overlap. - -So you might gather that there may be contacts created for fixtures that -are not touching (just their AABBs). Well, this is correct. It's a -\"chicken or egg\" problem. We don't know if we need a contact object -until one is created to analyze the collision. We could delete the -contact right away if the shapes are not touching, or we can just wait -until the AABBs stop overlapping. Box2D takes the latter approach -because it lets the system cache information to improve performance. - -### Contact Class -As mentioned before, the contact class is created and destroyed by -Box2D. Contact objects are not created by the user. However, you are -able to access the contact class and interact with it. - -You can access the raw contact manifold: - -```cpp -b2Manifold* b2Contact::GetManifold(); -const b2Manifold* b2Contact::GetManifold() const; -``` - -You can potentially modify the manifold, but this is generally not -supported and is for advanced usage. - -There is a helper function to get the `b2WorldManifold`: - -```cpp -void b2Contact::GetWorldManifold(b2WorldManifold* worldManifold) const; -``` - -This uses the current positions of the bodies to compute world positions -of the contact points. - -Sensors do not create manifolds, so for them use: - -```cpp -bool touching = sensorContact->IsTouching(); -``` - -This function also works for non-sensors. - -You can get the fixtures from a contact. From those you can get the -bodies. - -```cpp -b2Fixture* fixtureA = myContact->GetFixtureA(); -b2Body* bodyA = fixtureA->GetBody(); -MyActor* actorA = (MyActor*)bodyA->GetUserData().pointer; -``` - -You can disable a contact. This only works inside the -b2ContactListener::PreSolve event, discussed below. - -### Accessing Contacts -You can get access to contacts in several ways. You can access the -contacts directly on the world and body structures. You can also -implement a contact listener. - -You can iterate over all contacts in the world: - -```cpp -for (b2Contact* c = myWorld->GetContactList(); c; c = c->GetNext()) -{ - // process c -} -``` - -You can also iterate over all the contacts on a body. These are stored -in a graph using a contact edge structure. - -```cpp -for (b2ContactEdge* ce = myBody->GetContactList(); ce; ce = ce->next) -{ - b2Contact* c = ce->contact; - // process c -} -``` - -You can also access contacts using the contact listener that is -described below. - -> **Caution**: -> Accessing contacts off b2World and b2Body may miss some transient -> contacts that occur in the middle of the time step. Use -> b2ContactListener to get the most accurate results. - -### Contact Listener -You can receive contact data by implementing b2ContactListener. The -contact listener supports several events: begin, end, pre-solve, and -post-solve. - -```cpp -class MyContactListener : public b2ContactListener -{ -public: - -void BeginContact(b2Contact* contact) -{ /* handle begin event */ } - -void EndContact(b2Contact* contact) -{ /* handle end event */ } - -void PreSolve(b2Contact* contact, const b2Manifold* oldManifold) -{ /* handle pre-solve event */ } - -void PostSolve(b2Contact* contact, const b2ContactImpulse* impulse) -{ /* handle post-solve event */ } -}; -``` - -> **Caution**: -> Do not keep a reference to the pointers sent to b2ContactListener. -> Instead make a deep copy of the contact point data into your own buffer. -> The example below shows one way of doing this. - -At run-time you can create an instance of the listener and register it -with b2World::SetContactListener. Be sure your listener remains in scope -while the world object exists. - -#### Begin Contact Event -This is called when two fixtures begin to overlap. This is called for -sensors and non-sensors. This event can only occur inside the time step. - -#### End Contact Event -This is called when two fixtures cease to overlap. This is called for -sensors and non-sensors. This may be called when a body is destroyed, so -this event can occur outside the time step. - -#### Pre-Solve Event -This is called after collision detection, but before collision -resolution. This gives you a chance to disable the contact based on the -current configuration. For example, you can implement a one-sided -platform using this callback and calling b2Contact::SetEnabled(false). -The contact will be re-enabled each time through collision processing, -so you will need to disable the contact every time-step. The pre-solve -event may be fired multiple times per time step per contact due to -continuous collision detection. - -```cpp -void PreSolve(b2Contact* contact, const b2Manifold* oldManifold) -{ - b2WorldManifold worldManifold; - contact->GetWorldManifold(&worldManifold); - if (worldManifold.normal.y < -0.5f) - { - contact->SetEnabled(false); - } -} -``` - -The pre-solve event is also a good place to determine the point state -and the approach velocity of collisions. - -```cpp -void PreSolve(b2Contact* contact, const b2Manifold* oldManifold) -{ - b2WorldManifold worldManifold; - contact->GetWorldManifold(&worldManifold); - - b2PointState state1[2], state2[2]; - b2GetPointStates(state1, state2, oldManifold, contact->GetManifold()); - - if (state2[0] == b2_addState) - { - const b2Body* bodyA = contact->GetFixtureA()->GetBody(); - const b2Body* bodyB = contact->GetFixtureB()->GetBody(); - b2Vec2 point = worldManifold.points[0]; - b2Vec2 vA = bodyA->GetLinearVelocityFromWorldPoint(point); - b2Vec2 vB = bodyB->GetLinearVelocityFromWorldPoint(point); - - float approachVelocity = b2Dot(vB -- vA, worldManifold.normal); - - if (approachVelocity > 1.0f) - { - MyPlayCollisionSound(); - } - } -} -``` - -#### Post-Solve Event -The post solve event is where you can gather collision impulse results. -If you don't care about the impulses, you should probably just implement -the pre-solve event. - -It is tempting to implement game logic that alters the physics world -inside a contact callback. For example, you may have a collision that -applies damage and try to destroy the associated actor and its rigid -body. However, Box2D does not allow you to alter the physics world -inside a callback because you might destroy objects that Box2D is -currently processing, leading to orphaned pointers. - -The recommended practice for processing contact points is to buffer all -contact data that you care about and process it after the time step. You -should always process the contact points immediately after the time -step; otherwise some other client code might alter the physics world, -invalidating the contact buffer. When you process the contact buffer you -can alter the physics world, but you still need to be careful that you -don't orphan pointers stored in the contact point buffer. The testbed -has example contact point processing that is safe from orphaned -pointers. - -This code from the CollisionProcessing test shows how to handle orphaned -bodies when processing the contact buffer. Here is an excerpt. Be sure -to read the comments in the listing. This code assumes that all contact -points have been buffered in the b2ContactPoint array m_points. - -```cpp -// We are going to destroy some bodies according to contact -// points. We must buffer the bodies that should be destroyed -// because they may belong to multiple contact points. -const int32 k_maxNuke = 6; -b2Body* nuke[k_maxNuke]; -int32 nukeCount = 0; - -// Traverse the contact buffer. Destroy bodies that -// are touching heavier bodies. -for (int32 i = 0; i < m_pointCount; ++i) -{ - ContactPoint* point = m_points + i; - b2Body* bodyA = point->fixtureA->GetBody(); - b2Body* bodyB = point->FixtureB->GetBody(); - float massA = bodyA->GetMass(); - float massB = bodyB->GetMass(); - - if (massA > 0.0f && massB > 0.0f) - { - if (massB > massA) - { - nuke[nukeCount++] = bodyA; - } - else - { - nuke[nukeCount++] = bodyB; - } - - if (nukeCount == k_maxNuke) - { - break; - } - } -} - -// Sort the nuke array to group duplicates. -std::sort(nuke, nuke + nukeCount); - -// Destroy the bodies, skipping duplicates. -int32 i = 0; -while (i < nukeCount) -{ - b2Body* b = nuke[i++]; - while (i < nukeCount && nuke[i] == b) - { - ++i; - } - - m_world->DestroyBody(b); -} -``` - -### Contact Filtering -Often in a game you don't want all objects to collide. For example, you -may want to create a door that only certain characters can pass through. -This is called contact filtering, because some interactions are filtered -out. - -Box2D allows you to achieve custom contact filtering by implementing a -b2ContactFilter class. This class requires you to implement a -ShouldCollide function that receives two b2Shape pointers. Your function -returns true if the shapes should collide. - -The default implementation of ShouldCollide uses the b2FilterData -defined in Chapter 6, Fixtures. - -```cpp -bool b2ContactFilter::ShouldCollide(b2Fixture* fixtureA, b2Fixture* fixtureB) -{ - const b2Filter& filterA = fixtureA->GetFilterData(); - const b2Filter& filterB = fixtureB->GetFilterData(); - - if (filterA.groupIndex == filterB.groupIndex && filterA.groupIndex != 0) - { - return filterA.groupIndex > 0; - } - - bool collideA = (filterA.maskBits & filterB.categoryBits) != 0; - bool collideB = (filterA.categoryBits & filterB.maskBits) != 0 - bool collide = collideA && collideB; - return collide; -} -``` - -At run-time you can create an instance of your contact filter and -register it with b2World::SetContactFilter. Make sure your filter stays -in scope while the world exists. - -```cpp -MyContactFilter filter; -world->SetContactFilter(&filter); -// filter remains in scope ... -``` - -## World -The `b2World` class contains the bodies and joints. It manages all aspects -of the simulation and allows for asynchronous queries (like AABB queries -and ray-casts). Much of your interactions with Box2D will be with a -b2World object. - -### Creating and Destroying a World -Creating a world is fairly simple. You just need to provide a gravity -vector and a Boolean indicating if bodies can sleep. Usually you will -create and destroy a world using new and delete. - -```cpp -b2World* myWorld = new b2World(gravity, doSleep); - -// ... do stuff ... - -delete myWorld; -``` - -### Using a World -The world class contains factories for creating and destroying bodies -and joints. These factories are discussed later in the sections on -bodies and joints. There are some other interactions with b2World that I -will cover now. - -### Simulation -The world class is used to drive the simulation. You specify a time step -and a velocity and position iteration count. For example: - -```cpp -float timeStep = 1.0f / 60.f; -int32 velocityIterations = 10; -int32 positionIterations = 8; -myWorld->Step(timeStep, velocityIterations, positionIterations); -``` - -After the time step you can examine your bodies and joints for -information. Most likely you will grab the position off the bodies so -that you can update your actors and render them. You can perform the -time step anywhere in your game loop, but you should be aware of the -order of things. For example, you must create bodies before the time -step if you want to get collision results for the new bodies in that -frame. - -As I discussed above in the HelloWorld tutorial, you should use a fixed -time step. By using a larger time step you can improve performance in -low frame rate scenarios. But generally you should use a time step no -larger than 1/30 seconds. A time step of 1/60 seconds will usually -deliver a high quality simulation. - -The iteration count controls how many times the constraint solver sweeps -over all the contacts and joints in the world. More iteration always -yields a better simulation. But don't trade a small time step for a -large iteration count. 60Hz and 10 iterations is far better than 30Hz -and 20 iterations. - -After stepping, you should clear any forces you have applied to your -bodies. This is done with the command `b2World::ClearForces`. This lets -you take multiple sub-steps with the same force field. - -```cpp -myWorld->ClearForces(); -``` - -### Exploring the World -The world is a container for bodies, contacts, and joints. You can grab -the body, contact, and joint lists off the world and iterate over them. -For example, this code wakes up all the bodies in the world: - -```cpp -for (b2Body* b = myWorld->GetBodyList(); b; b = b->GetNext()) -{ - b->SetAwake(true); -} -``` - -Unfortunately real programs can be more complicated. For example, the -following code is broken: - -```cpp -for (b2Body* b = myWorld->GetBodyList(); b; b = b->GetNext()) -{ - GameActor* myActor = (GameActor*)b->GetUserData().pointer; - if (myActor->IsDead()) - { - myWorld->DestroyBody(b); // ERROR: now GetNext returns garbage. - } -} -``` - -Everything goes ok until a body is destroyed. Once a body is destroyed, -its next pointer becomes invalid. So the call to `b2Body::GetNext()` will -return garbage. The solution to this is to copy the next pointer before -destroying the body. - -```cpp -b2Body* node = myWorld->GetBodyList(); -while (node) -{ - b2Body* b = node; - node = node->GetNext(); - - GameActor* myActor = (GameActor*)b->GetUserData().pointer; - if (myActor->IsDead()) - { - myWorld->DestroyBody(b); - } -} -``` - -This safely destroys the current body. However, you may want to call a -game function that may destroy multiple bodies. In this case you need to -be very careful. The solution is application specific, but for -convenience I'll show one method of solving the problem. - -```cpp -b2Body* node = myWorld->GetBodyList(); -while (node) -{ - b2Body* b = node; - node = node->GetNext(); - - GameActor* myActor = (GameActor*)b->GetUserData().pointer; - if (myActor->IsDead()) - { - bool otherBodiesDestroyed = GameCrazyBodyDestroyer(b); - if (otherBodiesDestroyed) - { - node = myWorld->GetBodyList(); - } - } -} -``` - -Obviously to make this work, GameCrazyBodyDestroyer must be honest about -what it has destroyed. - -### AABB Queries -Sometimes you want to determine all the shapes in a region. The b2World -class has a fast log(N) method for this using the broad-phase data -structure. You provide an AABB in world coordinates and an -implementation of b2QueryCallback. The world calls your class with each -fixture whose AABB overlaps the query AABB. Return true to continue the -query, otherwise return false. For example, the following code finds all -the fixtures that potentially intersect a specified AABB and wakes up -all of the associated bodies. - -```cpp -class MyQueryCallback : public b2QueryCallback -{ -public: - bool ReportFixture(b2Fixture* fixture) - { - b2Body* body = fixture->GetBody(); - body->SetAwake(true); - - // Return true to continue the query. - return true; - } -}; - -// Elsewhere ... -MyQueryCallback callback; -b2AABB aabb; - -aabb.lowerBound.Set(-1.0f, -1.0f); -aabb.upperBound.Set(1.0f, 1.0f); -myWorld->Query(&callback, aabb); -``` - -You cannot make any assumptions about the order of the callbacks. - -### Ray Casts -You can use ray casts to do line-of-sight checks, fire guns, etc. You -perform a ray cast by implementing a callback class and providing the -start and end points. The world class calls your class with each fixture -hit by the ray. Your callback is provided with the fixture, the point of -intersection, the unit normal vector, and the fractional distance along -the ray. You cannot make any assumptions about the order of the -callbacks. - -You control the continuation of the ray cast by returning a fraction. -Returning a fraction of zero indicates the ray cast should be -terminated. A fraction of one indicates the ray cast should continue as -if no hit occurred. If you return the fraction from the argument list, -the ray will be clipped to the current intersection point. So you can -ray cast any shape, ray cast all shapes, or ray cast the closest shape -by returning the appropriate fraction. - -You may also return of fraction of -1 to filter the fixture. Then the -ray cast will proceed as if the fixture does not exist. - -Here is an example: - -```cpp -// This class captures the closest hit shape. -class MyRayCastCallback : public b2RayCastCallback -{ -public: - MyRayCastCallback() - { - m_fixture = NULL; - } - - float ReportFixture(b2Fixture* fixture, const b2Vec2& point, - const b2Vec2& normal, float fraction) - { - m_fixture = fixture; - m_point = point; - m_normal = normal; - m_fraction = fraction; - return fraction; - } - - b2Fixture* m_fixture; - b2Vec2 m_point; - b2Vec2 m_normal; - float m_fraction; -}; - -// Elsewhere ... -MyRayCastCallback callback; -b2Vec2 point1(-1.0f, 0.0f); -b2Vec2 point2(3.0f, 1.0f); -myWorld->RayCast(&callback, point1, point2); -``` - -> **Caution**: -> Due to round-off errors, ray casts can sneak through small cracks -> between polygons in your static environment. If this is not acceptable -> in your application, trying slightly overlapping your polygons. diff --git a/docs/extra.css b/docs/extra.css index 13656aa3..b5eba55a 100644 --- a/docs/extra.css +++ b/docs/extra.css @@ -16,6 +16,7 @@ body { #titlearea { border-bottom: 1px solid #32363d; + background-color: #292a2d; } div.contents { @@ -26,8 +27,6 @@ div.contents { .image { background-color: #CCCCCC; border: 10px solid #CCCCCC; - margin: 0px; - padding: 0px; } /* this kind of works with doxygen markdown */ @@ -343,7 +342,11 @@ a.highlighted { } .directory tr.even { - background: #32363d; + background: #36383d; +} + +.directory tr.odd { + background: #292a2d; } #MSearchSelectWindow { diff --git a/docs/foundation.md b/docs/foundation.md new file mode 100644 index 00000000..8aca2a3f --- /dev/null +++ b/docs/foundation.md @@ -0,0 +1,58 @@ +# Foundations +Box2D provides minimal base functionality for allocation hooks and vector math. The C interface +allows most runtime data and types to be defined internally in the `src` folder. + +## Assertions +Box2D will assert on bad input. This includes things like sending in NaN or infinity for values. It will assert if +you use negative values for things that should only be positive, such as density. + +Box2D will also assert if an internal bug is detected. For this reason, it is advisable to build Box2D from source. +The Box2D library compiles in about a second on my computer. + +You may wish to capture assertions in your application. In this case you can use `b2SetAssertFcn()`. This allows you +to override the debugger break and/or perform your own error handling. + +## Allocation +Box2D uses memory efficiently and minimizes per frame allocations by pooling memory. The engine quickly adapts to the +simulation size. After the first step or two of simulation, there should be no further per frame allocations. + +As bodies, shapes, and joints are created and destroyed, their memory will be recycled. Internally all this data is stored in contiguous arrays. When an object is destroyed, the array element will be marked as empty. And when an object is created it will use empty slots in the array using an efficient free list. + +Once the internal memory pools are initially filled, the only allocations should be for sleeping islands since their data is copied out of the main simulation. Generally, these allocations should be infrequent. + +You can provide a custom allocator using `b2SetAllocator()` and you can get the number of bytes allocated using `b2GetByteCount()`. + +## Version +The b2Version structure holds the current version so you can query this +at run-time using `b2GetVersion()`. + +```c +b2Version version = b2GetVersion(); +printf("Box2D version %d.%d.%d\n", version.major, version.minor, version.patch); +``` + +## Vector Math +Box2D includes a small vector math library including types `b2Vec2`, `b2Rot`, `b2Transform`, and `b2AABB`. This has been +designed to suit the internal needs of Box2D and the interface. All the +members are exposed, so you may use them freely in your application. + +The math library is kept simple to make Box2D easy to port and maintain. + +## Multithreading {#multi} +Box2D has been highly optimized for multithreading. Multithreading is not required and by default Box2D will run single-threaded. If performance is important for your application, you should consider using the multithreading interface. + +Box2D multithreading has been designed to work with your application's task system. Box2D does +not create threads. The Samples application shows how to do this using the open source tasks system [enkiTS](https://github.com/dougbinks/enkiTS). + +Multithreading is established for each Box2D world you create and must be hooked up to +the world definition. See `b2TaskCallback()`, `b2EnqueueTaskCallback()`, and `b2FinishTaskCallback()` for more details. Also see `b2WorldDef::workerCount`, `b2WorldDef::enqueueTask`, and `b2WorldDef::finishTask`. + +The multithreading design for Box2D is focused on [data parallelism](https://en.wikipedia.org/wiki/Data_parallelism). The idea is to use multiple cores to complete the world simulation as fast as possible. Box2D multithreading is not designed for [task parallelism](https://en.wikipedia.org/wiki/Task_parallelism). Often in games you may have a render thread and an audio thread that do work in isolation from the main thread. Those are examples of task parallelism. + +So when you design your game loop, you should let Box2D *go wide* and use multiple cores to finish its work quickly, without other threads trying to interact with the Box2D world. + +> **Caution**: +> While Box2D is designed for multithreading, its interface is *not* thread-safe. Modifying +> the Box2D world during simulation or from multiple threads will result in a [race condition](https://en.wikipedia.org/wiki/Race_condition). + +It *is safe* to do ray-casts, shape-casts, and overlap tests from multiple threads outside of `b2World_Step()`. Generally, any read-only operation is safe to do multithreaded outside of `b2World_Step()`. This can be very useful if you have multithreaded game logic. diff --git a/docs/hello.md b/docs/hello.md index b8427a31..41b571a0 100644 --- a/docs/hello.md +++ b/docs/hello.md @@ -1,5 +1,5 @@ -# Hello Box2D (NOT UPDATED for Box2D version 3.0) -In the distribution of Box2D is a Hello World project. The program +# Hello Box2D {#hello} +In the distribution of Box2D is a Hello World unit test written in C. The test creates a large ground box and a small dynamic box. This code does not contain any graphics. All you will see is text output in the console of the box's position over time. @@ -7,61 +7,74 @@ the box's position over time. This is a good example of how to get up and running with Box2D. ## Creating a World -Every Box2D program begins with the creation of a b2World object. -b2World is the physics hub that manages memory, objects, and simulation. -You can allocate the physics world on the stack, heap, or data section. +Every Box2D program begins with the creation of a world object. +The world is the physics hub that manages memory, objects, and simulation. +The world is represented by an opaque handle called `b2WorldId`. -It is easy to create a Box2D world. First, we define the gravity vector. +It is easy to create a Box2D world. First, I create the world definition: -```cpp -b2Vec2 gravity(0.0f, -10.0f); +```c +b2WorldDef worldDef = b2DefaultWorldDef(); ``` -Now we create the world object. Note that we are creating the world on -the stack, so the world must remain in scope. +The world definition is a temporary object that you can create on the stack. The function +`b2DefaultWorldDef()` populates the world definition with default values. This is necessary because C does not have constructors and zero initialization is not appropriate for `b2WorldDef`. -```cpp -b2World world(gravity); +Now I configure the world gravity vector. Note that Box2D has no concept of *up* and you may point gravity in any direction you like. Box2D example code uses the positive y-axis as the up direction. + +```c +worldDef.gravity = (b2Vec2){0.0f, -10.0f}; ``` +Now I create the world object. + +```c +b2WorldId worldId = b2CreateWorld(&worldDef); +``` + +The world creation copies all the data it needs out of the world definition, so the world +definition is no longer needed. + So now we have our physics world, let's start adding some stuff to it. ## Creating a Ground Box Bodies are built using the following steps: 1. Define a body with position, damping, etc. -2. Use the world object to create the body. -3. Define fixtures with a shape, friction, density, etc. -4. Create fixtures on the body. +2. Use the world id to create the body. +3. Define shapes with friction, density, etc. +4. Create shapes on the body. -For step 1 we create the ground body. For this we need a body -definition. With the body definition we specify the initial position of +For step 1 I create the ground body. For this I need a body +definition. With the body definition I specify the initial position of the ground body. -```cpp -b2BodyDef groundBodyDef; -groundBodyDef.position.Set(0.0f, -10.0f); +```c +b2BodyDef groundBodyDef = b2DefaultBodyDef(); +groundBodyDef.position = (b2Vec2){0.0f, -10.0f}; ``` -For step 2 the body definition is passed to the world object to create -the ground body. The world object does not keep a reference to the body -definition. Bodies are static by default. Static bodies don't collide -with other static bodies and are immovable. +For step 2 the body definition and the world id are used to create +the ground body. Again, the definition is fully copied and may leave scope after +the body is created. Bodies are static by default. Static bodies don't collide +with other static bodies and are immovable by the simulation. -```cpp -b2Body* groundBody = world.CreateBody(&groundBodyDef); +```c +b2BodyId groundId = b2CreateBody(worldId, &groundBodyDef); ``` -For step 3 we create a ground polygon. We use the SetAsBox shortcut to +Notice that `worldId` is passed by value. Ids are small structures that should +be passed by value. + +For step 3 I create a ground polygon. I use the `b2MakeBox()` helper function to form the ground polygon into a box shape, with the box centered on the origin of the parent body. -```cpp -b2PolygonShape groundBox; -groundBox.SetAsBox(50.0f, 10.0f); +```c +b2Polygon groundBox = b2MakeBox(50.0f, 10.0f); ``` -The SetAsBox function takes the **half**-**width** and -**half**-**height** (extents). So in this case the ground box is 100 +The `b2MakeBox()` function takes the **half-width** and +**half-height** (extents). So in this case the ground box is 100 units wide (x-axis) and 20 units tall (y-axis). Box2D is tuned for meters, kilograms, and seconds. So you can consider the extents to be in meters. Box2D generally works best when objects are the size of typical @@ -69,92 +82,88 @@ real world objects. For example, a barrel is about 1 meter tall. Due to the limitations of floating point arithmetic, using Box2D to model the movement of glaciers or dust particles is not a good idea. -We finish the ground body in step 4 by creating the shape fixture. For -this step we have a shortcut. We do not have a need to alter the default -fixture material properties, so we can pass the shape directly to the -body without creating a fixture definition. Later we will see how to use -a fixture definition for customized material properties. The second -parameter is the shape density in kilograms per meter squared. A static -body has zero mass by definition, so the density is not used in this -case. +I'll finish the ground body in step 4 by creating the shape. For this step +I need to create a shape definition which works fine with the default value. -```cpp -groundBody->CreateFixture(&groundBox, 0.0f); +```c +b2ShapeDef groundShapeDef = b2DefaultShapeDef(); +b2CreatePolygonShape(groundId, &groundShapeDef, &groundBox); ``` -Box2D does not keep a reference to the shape. It clones the data into a -new b2Shape object. +Box2D does not keep a reference to the shape data. It copies the data into the internal +data structures. -Note that every fixture must have a parent body, even fixtures that are -static. However, you can attach all static fixtures to a single static -body. +Note that every shape must have a parent body, even shapes that are +static. You may attach multiple shapes to a single parent body. -When you attach a shape to a body using a fixture, the shape's +When you attach a shape, the shape's coordinates become local to the body. So when the body moves, so does -the shape. A fixture's world transform is inherited from the parent -body. A fixture does not have a transform independent of the body. So we +the shape. A shape's world transform is inherited from the parent +body. A shape does not have a transform independent of the body. So we don't move a shape around on the body. Moving or modifying a shape that -is on a body is not supported. The reason is simple: a body with +is on a body is possible with certain functions, but it should not be part +of normal simulation. The reason is simple: a body with morphing shapes is not a rigid body, but Box2D is a rigid body engine. -Many of the assumptions made in Box2D are based on the rigid body model. -If this is violated many things will break +Many of the algorithms in Box2D are based on the rigid body model. +If this is violated you may get unexpected behavior. ## Creating a Dynamic Body -So now we have a ground body. We can use the same technique to create a -dynamic body. The main difference, besides dimensions, is that we must +I can use the same technique to create a +dynamic body. The main difference, besides dimensions, is that I must establish the dynamic body's mass properties. -First we create the body using CreateBody. By default bodies are static, -so we should set the b2BodyType at construction time to make the body -dynamic. +First I create the body using CreateBody. By default bodies are static, +so I should set the `b2BodyType` at creation time to make the body +dynamic. I should also use the body definition to put the body at the +intended position for simulation. Creating a body then moving it afterwards is +very inefficient and may cause lag spikes, especially if many bodies are created at +the origin. -```cpp -b2BodyDef bodyDef; +```c +b2BodyDef bodyDef = b2DefaultBodyDef(); bodyDef.type = b2_dynamicBody; bodyDef.position.Set(0.0f, 4.0f); -b2Body* body = world.CreateBody(&bodyDef); +b2BodyId bodyId = b2CreateBody(worldId, &bodyDef); ``` > **Caution**: -> You must set the body type to b2_dynamicBody if you want the body to -> move in response to forces. +> You must set the body type to `b2_dynamicBody` if you want the body to +> move in response to forces (such as gravity). -Next we create and attach a polygon shape using a fixture definition. -First we create a box shape: +Next I create and attach a polygon shape using a shape definition. +First I create another box shape: -```cpp -b2PolygonShape dynamicBox; -dynamicBox.SetAsBox(1.0f, 1.0f); +```c +b2Polygon dynamicBox = b2MakeBox(1.0f, 1.0f); ``` -Next we create a fixture definition using the box. Notice that we set -density to 1. The default density is zero. Also, the friction on the -shape is set to 0.3. +Next I create a shape definition for the box. Notice that I set +density to 1. The default density is 1, so this is unnecessary. Also, +the friction on the shape is set to 0.3. -```cpp -b2FixtureDef fixtureDef; -fixtureDef.shape = &dynamicBox; -fixtureDef.density = 1.0f; -fixtureDef.friction = 0.3f; +```c +b2ShapeDef shapeDef = b2DefaultShapeDef(); +shapeDef.density = 1.0f; +shapeDef.friction = 0.3f; ``` > **Caution**: -> A dynamic body should have at least one fixture with a non-zero density. +> A dynamic body should have at least one shape with a non-zero density. > Otherwise you will get strange behavior. -Using the fixture definition we can now create the fixture. This -automatically updates the mass of the body. You can add as many fixtures +Using the shape definition I can now create the shape. This +automatically updates the mass of the body. You can add as many shapes as you like to a body. Each one contributes to the total mass. -```cpp -body->CreateFixture(&fixtureDef); +```c +b2CreatePolygonShape(bodyId, &shapeDef, &dynamicBox); ``` That's it for initialization. We are now ready to begin simulating. ## Simulating the World -So we have initialized the ground box and a dynamic box. Now we are -ready to set Newton loose to do his thing. We just have a couple more +I have initialized the ground box and a dynamic box. Now we are +ready to set Newton loose to do his thing. I just have a couple more issues to consider. Box2D uses a computational algorithm called an integrator. Integrators @@ -163,64 +172,61 @@ along with the traditional game loop where we essentially have a flip book of movement on the screen. So we need to pick a time step for Box2D. Generally physics engines for games like a time step at least as fast as 60Hz or 1/60 seconds. You can get away with larger time steps, -but you will have to be more careful about setting up the definitions -for your world. We also don't like the time step to change much. A +but you will have to be more careful about setting up your simulation. +It is also not good for the time step to vary from frame to frame. A variable time step produces variable results, which makes it difficult -to debug. So don't tie the time step to your frame rate (unless you -really, really have to). Without further ado, here is the time step. +to debug. So don't tie the time step to your frame rate. Without further ado, +here is the time step. -```cpp +```c float timeStep = 1.0f / 60.0f; ``` In addition to the integrator, Box2D also uses a larger bit of code called a constraint solver. The constraint solver solves all the constraints in the simulation, one at a time. A single constraint can be -solved perfectly. However, when we solve one constraint, we slightly -disrupt other constraints. To get a good solution, we need to iterate +solved perfectly. However, when Box2D solves one constraint, it slightly +disrupts other constraints. To get a good solution, Box2D needs to iterate over all constraints a number of times. -There are two phases in the constraint solver: a velocity phase and a -position phase. In the velocity phase the solver computes the impulses -necessary for the bodies to move correctly. In the position phase the -solver adjusts the positions of the bodies to reduce overlap and joint -detachment. Each phase has its own iteration count. In addition, the -position phase may exit iterations early if the errors are small. +Box2D uses sub-stepping as a means of constraint iteration. It lets the +simulation move forward in time by small amounts and each constraint +gets a chance to react to the changes. -The suggested iteration count for Box2D is 8 for velocity and 3 for -position. You can tune this number to your liking, just keep in mind -that this has a trade-off between performance and accuracy. Using fewer -iterations increases performance but accuracy suffers. Likewise, using -more iterations decreases performance but improves the quality of your -simulation. For this simple example, we don't need much iteration. Here -are our chosen iteration counts. +The suggested sub-step count for Box2D is 4. You can tune this number +to your liking, just keep in mind that this has a trade-off between +performance and accuracy. Using fewer sub-steps increases performance +but accuracy suffers. Likewise, using +more sub-steps decreases performance but improves the quality of your +simulation. For this example, I will use 4 sub-steps. -```cpp -int32 velocityIterations = 6; -int32 positionIterations = 2; +```c +int subStepCount = 4; ``` -Note that the time step and the iteration count are completely -unrelated. An iteration is not a sub-step. One solver iteration is a -single pass over all the constraints within a time step. You can have -multiple passes over the constraints within a single time step. +Note that the time step and the sub-step count are related. As the time-step +decreases, the size of the sub-steps also decreases. For example, at 60Hz +time step and 4 sub-steps, the sub-steps operate at 240Hz. With 8 sub-steps +the sub-step is 480Hz! We are now ready to begin the simulation loop. In your game the simulation loop can be merged with your game loop. In each pass through -your game loop you call b2World::Step. Just one call is usually enough, -depending on your frame rate and your physics time step. +your game loop you call `b2World_Step()`. Just one call is usually enough, +depending on your frame rate and your physics time step. I recommend this article +[Fix Your Timestep!](https://gafferongames.com/post/fix_your_timestep/) to run +your game simulation at a fixed rate. -The Hello World program was designed to be simple, so it has no +The Hello World test was designed to be simple, so it has no graphical output. The code prints out the position and rotation of the -dynamic body. Here is the simulation loop that simulates 60 time steps -for a total of 1 second of simulated time. +dynamic body. Here is the simulation loop that simulates 90 time steps +for a total of 1.5 seconds of simulated time. -```cpp -for (int32 i = 0; i < 60; ++i) +```c +for (int i = 0; i < 90; ++i) { - world.Step(timeStep, velocityIterations, positionIterations); - b2Vec2 position = body->GetPosition(); - float angle = body->GetAngle(); + b2World_Step(worldId, timeStep, subStepCount); + b2Vec2 position = b2Body_GetPosition(bodyId); + float angle = b2Body_GetAngle(bodyId); printf("%4.2f %4.2f %4.2f\n", position.x, position.y, angle); } ``` @@ -239,8 +245,10 @@ output should look like this: ``` ## Cleanup -When a world leaves scope or is deleted by calling delete on a pointer, -all the memory reserved for bodies, fixtures, and joints is freed. This -is done to improve performance and make your life easier. However, you -will need to nullify any body, fixture, or joint pointers you have -because they will become invalid. +When you are done with the simulation, you should destroy the world. + +```c +b2DestroyWorld(worldId); +``` + +This efficiently destroys all bodies, shapes, and joints in the simulation. diff --git a/docs/images/body_origin.gif b/docs/images/body_origin.gif deleted file mode 100644 index cef17076..00000000 Binary files a/docs/images/body_origin.gif and /dev/null differ diff --git a/docs/images/capsule.svg b/docs/images/capsule.svg new file mode 100644 index 00000000..97d65c9a --- /dev/null +++ b/docs/images/capsule.svg @@ -0,0 +1,243 @@ + + + + + + + + + + + + + + + + + + x + y + radius + + + + + + + + + + + + + + + + + + + center2 + center1 + + diff --git a/docs/images/center_of_mass.svg b/docs/images/center_of_mass.svg new file mode 100644 index 00000000..00afe13e --- /dev/null +++ b/docs/images/center_of_mass.svg @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + body origin + + x + y + + + + + + + + + + + + + centerofmass + + diff --git a/docs/images/circle.svg b/docs/images/circle.svg new file mode 100644 index 00000000..7ce3000c --- /dev/null +++ b/docs/images/circle.svg @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + + + + + + x + y + radius + center + + diff --git a/docs/images/convex_concave.gif b/docs/images/convex_concave.gif deleted file mode 100644 index feefdf04..00000000 Binary files a/docs/images/convex_concave.gif and /dev/null differ diff --git a/docs/images/convex_concave.svg b/docs/images/convex_concave.svg new file mode 100644 index 00000000..2bc19413 --- /dev/null +++ b/docs/images/convex_concave.svg @@ -0,0 +1,94 @@ + + + + + + + + + + convex + + + concave + + diff --git a/docs/images/distance_joint.gif b/docs/images/distance_joint.gif deleted file mode 100644 index 3fd44a47..00000000 Binary files a/docs/images/distance_joint.gif and /dev/null differ diff --git a/docs/images/distance_joint.svg b/docs/images/distance_joint.svg new file mode 100644 index 00000000..a7191183 --- /dev/null +++ b/docs/images/distance_joint.svg @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + anchorB + bodyB + bodyA + anchorA + length + + diff --git a/docs/images/gear_joint.gif b/docs/images/gear_joint.gif deleted file mode 100644 index 9737d7dc..00000000 Binary files a/docs/images/gear_joint.gif and /dev/null differ diff --git a/docs/images/modules.svg b/docs/images/modules.svg deleted file mode 100644 index 08200be4..00000000 --- a/docs/images/modules.svg +++ /dev/null @@ -1,222 +0,0 @@ - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - Common - Collision - - Dynamics - - - - - diff --git a/docs/images/prismatic_joint.gif b/docs/images/prismatic_joint.gif deleted file mode 100644 index ac2037f6..00000000 Binary files a/docs/images/prismatic_joint.gif and /dev/null differ diff --git a/docs/images/prismatic_joint.svg b/docs/images/prismatic_joint.svg new file mode 100644 index 00000000..58a7c8cb --- /dev/null +++ b/docs/images/prismatic_joint.svg @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + + + + + + + + + + bodyA + anchor + + translation + bodyB + + + diff --git a/docs/images/pulley_joint.gif b/docs/images/pulley_joint.gif deleted file mode 100644 index ad8d4fbf..00000000 Binary files a/docs/images/pulley_joint.gif and /dev/null differ diff --git a/docs/images/revolute_joint.gif b/docs/images/revolute_joint.gif deleted file mode 100644 index 39b74e28..00000000 Binary files a/docs/images/revolute_joint.gif and /dev/null differ diff --git a/docs/images/revolute_joint.svg b/docs/images/revolute_joint.svg new file mode 100644 index 00000000..9b3fb9ec --- /dev/null +++ b/docs/images/revolute_joint.svg @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + bodyB + bodyA + anchor + + + + angle + + diff --git a/docs/images/samples.png b/docs/images/samples.png new file mode 100644 index 00000000..a5c01db8 Binary files /dev/null and b/docs/images/samples.png differ diff --git a/docs/images/skinned_polygon.svg b/docs/images/skinned_polygon.svg deleted file mode 100644 index d93934c0..00000000 --- a/docs/images/skinned_polygon.svg +++ /dev/null @@ -1,181 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - diff --git a/docs/images/winding.svg b/docs/images/winding.svg index e543df03..bc938dd9 100644 --- a/docs/images/winding.svg +++ b/docs/images/winding.svg @@ -2,22 +2,22 @@ + inkscape:export-ydpi="90" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> @@ -86,7 +86,7 @@ @@ -106,17 +106,17 @@ + fit-margin-bottom="0" + inkscape:showpageshadow="2" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" /> @@ -149,7 +152,6 @@ image/svg+xml - @@ -157,13 +159,13 @@ inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1" - transform="translate(-245.94154,-381.59189)"> + transform="translate(-255.77258,-380.63095)"> x @@ -172,7 +174,7 @@ xml:space="preserve" x="482.47318" y="507.86407" - style="font-style:normal;font-weight:normal;font-size:13.80000019px;line-height:0%;font-family:Calibri;text-align:start;text-anchor:start;fill:#000000" + style="font-style:normal;font-weight:normal;font-size:13.8px;line-height:0%;font-family:Calibri;text-align:start;text-anchor:start;fill:#000000" inkscape:export-filename="D:\Development\Box2D\Box2D\Documentation\images\winding.png" inkscape:export-xdpi="90" inkscape:export-ydpi="90"> @@ -181,7 +183,7 @@ xml:space="preserve" x="387.67319" y="387.19345" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12px;line-height:0%;font-family:Calibri;-inkscape-font-specification:Calibri;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12px;line-height:0%;font-family:'Hack Nerd Font';-inkscape-font-specification:'Hack Nerd Font, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000" inkscape:export-filename="D:\Development\Box2D\Box2D\Documentation\images\winding.png" inkscape:export-xdpi="90" inkscape:export-ydpi="90">y @@ -190,16 +192,16 @@ xml:space="preserve" x="396.32318" y="383.19345" - style="font-style:normal;font-weight:normal;font-size:13.80000019px;line-height:0%;font-family:Calibri;text-align:start;text-anchor:start;fill:#000000" + style="font-style:normal;font-weight:normal;font-size:13.8px;line-height:0%;font-family:Calibri;text-align:start;text-anchor:start;fill:#000000" inkscape:export-filename="D:\Development\Box2D\Box2D\Documentation\images\winding.png" inkscape:export-xdpi="90" inkscape:export-ydpi="90"> CCW winding @@ -208,13 +210,13 @@ xml:space="preserve" x="294.62317" y="397.59586" - style="font-style:normal;font-weight:normal;font-size:13.80000019px;line-height:0%;font-family:Calibri;text-align:start;text-anchor:start;fill:#000000" + style="font-style:normal;font-weight:normal;font-size:13.8px;line-height:0%;font-family:Calibri;text-align:start;text-anchor:start;fill:#000000" inkscape:export-filename="D:\Development\Box2D\Box2D\Documentation\images\winding.png" inkscape:export-xdpi="90" inkscape:export-ydpi="90"> (actor); -actor->body = myWorld->CreateBody(&bodyDef); +```c +GameEntity* entity = GameCreateEntity(); +b2BodyDef bodyDef = b2DefaultBodyDef(); +bodyDef.userData = entity; +entity->bodyId = b2CreateBody(myWorldId, &bodyDef); ``` -You can also use this to hold an integral value rather than a pointer. - Here are some examples of cases where you would need the user data: -- Applying damage to an actor using a collision result. +- Applying damage to an entity using a collision result. - Playing a scripted event if the player is inside an axis-aligned box. - Accessing a game structure when Box2D notifies you that a joint is going to be destroyed. Keep in mind that user data is optional and you can put anything in it. However, you should be consistent. For example, if you want to store an -actor pointer on one body, you should keep an actor pointer on all -bodies. Don't store an actor pointer on one body, and a foo pointer on -another body. Casting an actor pointer to a foo pointer may lead to a -crash. - -User data pointers are 0 by default. - -For fixtures you might consider defining a user data structure that lets -you store game specific information, such as material type, effects -hooks, sound hooks, etc. - -```cpp -struct FixtureUserData -{ - int materialIndex; - // ... -}; - -FixtureUserData myData = new FixtureUserData; -myData->materialIndex = 2; - -b2FixtureDef fixtureDef; -fixtureDef.shape = &someShape; -fixtureDef.userData.pointer = reinterpret_cast(myData); - -b2Fixture* fixture = body->CreateFixture(&fixtureDef); -// ... - -delete fixture->GetUserData(); -body->DestroyFixture(fixture); -``` - -## Custom User Data -You can define custom data structures that are embedded in the Box2D data -structures. This is done by defining `B2_USER_SETTINGS` and providing the -file `b2_user_settings.h`. See `b2_settings.h` for details. - -## Implicit Destruction -Box2D doesn't use reference counting. So if you destroy a body it is -really gone. Accessing a pointer to a destroyed body has undefined -behavior. In other words, your program will likely crash and burn. To -help fix these problems, the debug build memory manager fills destroyed -entities with FDFDFDFD. This can help find problems more easily in some -cases. - -If you destroy a Box2D entity, it is up to you to make sure you remove -all references to the destroyed object. This is easy if you only have a -single reference to the entity. If you have multiple references, you -might consider implementing a handle class to wrap the raw pointer. - -Often when using Box2D you will create and destroy many bodies, shapes, -and joints. Managing these entities is somewhat automated by Box2D. If -you destroy a body then all associated shapes and joints are -automatically destroyed. This is called implicit destruction. - -When you destroy a body, all its attached shapes, joints, and contacts -are destroyed. This is called implicit destruction. Any body connected -to one of those joints and/or contacts is woken. This process is usually -convenient. However, you must be aware of one crucial issue: - -> **Caution**: -> When a body is destroyed, all fixtures and joints attached to the body -> are automatically destroyed. You must nullify any pointers you have to -> those shapes and joints. Otherwise, your program will die horribly if -> you try to access or destroy those shapes or joints later. - -To help you nullify your joint pointers, Box2D provides a listener class -named b2DestructionListener that you can implement and provide to your -world object. Then the world object will notify you when a joint is -going to be implicitly destroyed - -Note that there no notification when a joint or fixture is explicitly -destroyed. In this case ownership is clear and you can perform the -necessary cleanup on the spot. If you like, you can call your own -implementation of b2DestructionListener to keep cleanup code -centralized. - -Implicit destruction is a great convenience in many cases. It can also -make your program fall apart. You may store pointers to shapes and -joints somewhere in your code. These pointers become orphaned when an -associated body is destroyed. The situation becomes worse when you -consider that joints are often created by a part of the code unrelated -to management of the associated body. For example, the testbed creates a -b2MouseJoint for interactive manipulation of bodies on the screen. - -Box2D provides a callback mechanism to inform your application when -implicit destruction occurs. This gives your application a chance to -nullify the orphaned pointers. This callback mechanism is described -later in this manual. - -You can implement a `b2DestructionListener` that allows b2World to inform -you when a shape or joint is implicitly destroyed because an associated -body was destroyed. This will help prevent your code from accessing -orphaned pointers. - -```cpp -class MyDestructionListener : public b2DestructionListener -{ - void SayGoodbye(b2Joint* joint) - { - // remove all references to joint. - } -}; -``` - -You can then register an instance of your destruction listener with your -world object. You should do this during world initialization. - -```cpp -myWorld->SetListener(myDestructionListener); -``` +entity pointer on one body, you should keep an entity pointer on all +bodies. Don't store a `GameEntity` pointer on one body, and a `ParticleSystem` +pointer on another body. Casting a `GameEntity` to a `ParticleSystem` pointer +may lead to a crash. ## Pixels and Coordinate Systems -Recall that Box2D uses MKS (meters, kilograms, and seconds) units and +I recommend using MKS (meters, kilograms, and seconds) units and radians for angles. You may have trouble working with meters because your game is expressed in terms of pixels. To deal with this in the -testbed I have the whole *game* work in meters and just use an OpenGL +sample I have the whole *game* world in meters and just use an OpenGL viewport transformation to scale the world into screen space. -```cpp +You use code like this to scale your graphics. + +```c float lowerX = -25.0f, upperX = 25.0f, lowerY = -5.0f, upperY = 25.0f; gluOrtho2D(lowerX, upperX, lowerY, upperY); ``` -If your game must work in pixel units then you should convert your +If your game must work in pixel units then you could convert your length units from pixels to meters when passing values from Box2D. Likewise you should convert the values received from Box2D from meters to pixels. This will improve the stability of the physics simulation. @@ -183,32 +76,33 @@ If you use a conversion factor, you should try tweaking it globally to make sure nothing breaks. You can also try adjusting it to improve stability. +If this conversion is not possible, you can set the length units used +by Box2D using `b2SetLengthUnitsPerMeter()`. This is experimental and not +well tested. + ## Debug Drawing -You can implement the b2DebugDraw class to get detailed drawing of the -physics world. Here are the available entities: -- shape outlines -- joint connectivity +You can implement the function pointers in `b2DebugDraw` struct to get detailed +drawing of the Box2D world. Debug draw provides: +- shapes +- joints - broad-phase axis-aligned bounding boxes (AABBs) - center of mass +- contact points -![Debug Draw](images/debug_draw.png) - -This is the preferred method of drawing these physics entities, rather +This is the preferred method of drawing the Box2D simulation, rather than accessing the data directly. The reason is that much of the necessary data is internal and subject to change. -The testbed draws physics entities using the debug draw facility and the -contact listener, so it serves as the primary example of how to -implement debug drawing as well as how to draw contact points. +The samples application draws the Box2D world using the `b2DebugDraw`. ## Limitations Box2D uses several approximations to simulate rigid body physics efficiently. This brings some limitations. Here are the current limitations: -1. Stacking heavy bodies on top of much lighter bodies is not stable. Stability degrades as the mass ratio passes 10:1. -2. Chains of bodies connected by joints may stretch if a lighter body is supporting a heavier body. For example, a wrecking ball connect to a chain of light weight bodies may not be stable. Stability degrades as the mass ratio passes 10:1. -3. There is typically around 0.5cm of slop in shape versus shape collision. -4. Continuous collision does not handle joints. So you may see joint stretching on fast moving objects. -5. Box2D uses the symplectic Euler integration scheme. It does not reproduce parabolic motion of projectiles and has only first-order accuracy. However it is fast and has good stability. -6. Box2D uses an iterative solver to provide real-time performance. You will not get precisely rigid collisions or pixel perfect accuracy. Increasing the iterations will improve accuracy. +1. Extreme mass ratios may cause joint stretching and collision overlap. +2. Box2D uses soft constraints to improve robustness. This can lead to joint and contact flexing. +3. Continuous collision does not handle all situations. For example, general dynamic versus dynamic continuous collision is not handled. [Bullets](#bullets) handle this in a limited way. This is done for performance reasons. +4. Continuous collision does not handle joints. So you may see joint stretching on fast moving objects. Usually the joints recover after a few time steps. +5. Box2D uses the [semi-implicit Euler method](https://en.wikipedia.org/wiki/Semi-implicit_Euler_method) to solve the [equations of motion](https://en.wikipedia.org/wiki/Equations_of_motion). It does not reproduce exactly the parabolic motion of projectiles and has only first-order accuracy. However it is fast and has good stability. +6. Box2D uses a the [Gauss-Seidel method](https://en.wikipedia.org/wiki/Gauss%E2%80%93Seidel_method) to solve constraints and achieve real-time performance. You will not get precisely rigid collisions or pixel perfect accuracy. Increasing the sub-step count will improve accuracy. diff --git a/docs/migration.md b/docs/migration.md index 51b14713..cb41867c 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -34,7 +34,7 @@ b2WorldDef worldDef = b2DefaultWorldDef(); worldDef.gravity = gravity; b2WorldId worldId = b2CreateWorld(&worldDef); ``` -There is now a required world defition. C does not have constructors, so you need to initialize **ALL** structures that you pass to Box2D. Box2D provides and initialization helper for almost all structures. For example `b2DefaultWorldDef()` is used here to initialize `b2WorldDef`. `b2WorldDef` provides many options, but the defaults are good enough to get going. +There is now a required world definition. C does not have constructors, so you need to initialize **ALL** structures that you pass to Box2D. Box2D provides and initialization helper for almost all structures. For example `b2DefaultWorldDef()` is used here to initialize `b2WorldDef`. `b2WorldDef` provides many options, but the defaults are good enough to get going. In Version 3.0, Box2D objects are generally hidden and you only have an identifier. This keeps the API small. So when you create a world you just get a `b2WorldId` which you should treat as an atomic object, like `int` or `float`. It is small and should be passed by value. @@ -73,7 +73,7 @@ Version 3.0: b2DestroyBody(bodyId); bodyId = b2_nullBodyId; ``` -Notice there is a little magic here in Version 3.0. `b2BodyId` knows what world it comes from. So you do not need to provide `worldId` when destroying the body. Version 3.0 supports up to 32 worlds. This may increased or be overriden in the future. +Notice there is a little magic here in Version 3.0. `b2BodyId` knows what world it comes from. So you do not need to provide `worldId` when destroying the body. Version 3.0 supports up to 128 worlds. This may increased or be overridden in the future. There is a significant behavior change with body destruction in Version 3.0. > Destroying a body no longer destroys the attached joints, it is up to you to destroy them. @@ -107,9 +107,9 @@ shapeDef.friction = 0.3f; b2ShapeId shapeId = b2CreatePolygonShape(bodyId, &shapeDef, &box); ``` -So basically v2.4 shapes are no longer shapes, they are _geometry_ with no inheritance (of course). This freed the term _shape_ to be used where _fixture_ was used before. In v3.0 the shape definition is generic and there are different functions for creating each shape type, such as `b2CreateCircleShape` or `b2CreateSegmentShape`. +So basically v2.4 shapes are no longer shapes, they are *primitives* with no inheritance (of course). This freed the term _shape_ to be used where _fixture_ was used before. In v3.0 the shape definition is generic and there are different functions for creating each shape type, such as `b2CreateCircleShape` or `b2CreateSegmentShape`. -Again notice the structure initialization with `b2_defaultShapeDef`. Unfortunately we cannot have meaningful definitions with zero initialization. You must initialize your structures. +Again notice the structure initialization with `b2DefaultShapeDef()`. Unfortunately we cannot have meaningful definitions with zero initialization. You must initialize your structures. Another important change for shapes is that the default density in the shape definition is now 1 instead of 0. Static and kinematic bodies will ignore the density. You can now make an entire game without touching the density. @@ -205,20 +205,16 @@ The friction joint has been removed since it is a subset of the motor joint. The pulley and gear joints have been removed. I'm not quite happy with how they work and plan to implement improved versions in the future. -There is one major change related to joints and related to reducing the number of callbacks. It is now required that you destroy joints before destroying the attached bodies. There is no implicit desctruction of joints and therefore no implicit joint destruction callback. - -> You must destroy joints before destroying the attached bodies. This means you need to keep track of your joint ids. - ### New solver -There is a new solver that uses sub-stepping. Intead of specifying velocity iterations or position iterations, you now specify the number of sub-steps. +There is a new solver that uses sub-stepping. Instead of specifying velocity iterations or position iterations, you now specify the number of sub-steps. ```c void b2World_Step(b2WorldId worldId, float timeStep, int32_t subStepCount); ``` -It is recommend to start with 4 sub-steps and adjust as needed. The sub-stepping only computes contact points once per full time step, so contact events are for the full time step. +It is recommended to start with 4 sub-steps and adjust as needed. The sub-stepping only computes contact points once per full time step, so contact events are for the full time step. -With a sub-stepping solver you need to think differently about how you interact with bodies. Externally applied impulses or velocity adjustments no longer exist after the first sub-step. So if you try to control the movement of a body by setting the velocity every time step then you may get unexpected results. You will get more predicatable results by applying a force and/or torque. Forces and torques are spread across all time steps. +With a sub-stepping solver you need to think differently about how you interact with bodies. Externally applied impulses or velocity adjustments no longer exist after the first sub-step. So if you try to control the movement of a body by setting the velocity every time step then you may get unexpected results. You will get more predictable results by applying a force and/or torque. Forces and torques are spread across all time steps. -If you want full control over the movement of a body, considering setting the body type to `b2_kinematicBody`. Prefably this is done in the `b2BodyDef`: +If you want full control over the movement of a body, considering setting the body type to `b2_kinematicBody`. Preferably this is done in the `b2BodyDef`: ```c b2BodyDef bodyDef = b2DefaultBodyDef(); bodyDef.type = b2_kinematicBody; @@ -244,8 +240,10 @@ typedef struct b2ContactEvents { b2ContactBeginTouchEvent* beginEvents; b2ContactEndTouchEvent* endEvents; + b2ContactHitEvent* hitEvents; int beginCount; int endCount; + int hitCount; } b2ContactEvents; ``` You can loop through these events after the time step. These events are in deterministic order, even with multithreading. See the `sample_events.cpp` file for examples. @@ -263,12 +261,13 @@ int count = b2Shape_GetContactData(shapeId, contactData, 10); ``` This includes contact data for contacts reported in begin events. This data is also in deterministic order. -Presolve contact modification is available using a callback. +Pre-solve contact modification is available using a callback. ```c typedef bool b2PreSolveFcn(b2ShapeId shapeIdA, b2ShapeId shapeIdB, b2Manifold* manifold, void* context); void b2World_SetPreSolveCallback(b2WorldId worldId, b2PreSolveFcn* fcn, void* context); ``` -You can define a presolve callback and register that with the world. You can also provide a context variable that will be passed back to your callback. This is **not** enough to get a presolve callback. You also need to enable it on your shape using `enablePreSolveEvents` in `b2ShapeDef`. This is false by default. +You can define a pre-solve callback and register that with the world. You can also provide a context variable that will be passed back to your callback. This is **not** enough to get a pre-solve callback. You also need to enable it on your shape using `enablePreSolveEvents` in `b2ShapeDef`. This is false by default. + > Pre-solve callbacks are dangerous. You must avoid race conditions and you must understand that behavior may not be deterministic. This is especially true if you have multiple pre-solve callbacks that are sensitive to order. ### Sensors @@ -286,10 +285,14 @@ Version 2.4 has `b2World::QueryAABB` and `b2World::RayCast`. This functionality Another new feature is `b2QueryFilter` which allows you to filter raycast results before they reach your callback. This query filter is tested against `b2Filter` on shapes that the query encounters. +Ray casts now take an origin and translation rather than start and end points. This convention works better with the added shape cast functions. + +### World iteration +Iterating over all bodies/shapes/joints/contacts in a world is very inefficient and has been removed from Version 3.0. Instead, you should be using `b2BodyEvents` and `b2ContactEvents`. Events are efficient and data-oriented. + ### Library configuration Version 3.0 offers more library configuration. You can override the allocator and you can intercept assertions by registering global callbacks. These are for expert users and they must be thread safe. ```c void b2SetAllocator(b2AllocFcn* allocFcn, b2FreeFcn* freeFcn); void b2SetAssertFcn(b2AssertFcn* assertFcn); ``` - diff --git a/docs/overview.md b/docs/overview.md index c209a78e..e2231f8e 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -1,12 +1,12 @@ -# Overview (NOT UPDATED for Box2D version 3.0) +# Overview Box2D is a 2D rigid body simulation library for games. Programmers can use it in their games to make objects move in realistic ways and make the game world more interactive. From the game engine's point of view, a physics engine is just a system for procedural animation. -Box2D is written in portable C++. Most of the types defined in the +Box2D is written in portable C11. Most of the types defined in the engine begin with the b2 prefix. Hopefully this is sufficient to avoid -name clashing with your game engine. +name clashing with your application. ## Prerequisites In this manual I'll assume you are familiar with basic physics @@ -14,22 +14,22 @@ concepts, such as mass, force, torque, and impulses. If not, please first consult Google search and Wikipedia. Box2D was created as part of a physics tutorial at the Game Developer -Conference. You can get these tutorials from the download section of -box2d.org. +Conference. You can get these tutorials from the publications section of +[box2d.org](https://box2d.org/publications/). -Since Box2D is written in C++, you are expected to be experienced in C++ -programming. Box2D should not be your first C++ programming project! You +Since Box2D is written in C, you are expected to be experienced in C +programming. Box2D should not be your first C programming project! You should be comfortable with compiling, linking, and debugging. > **Caution**: -> Box2D should not be your first C++ project. Please learn C++ +> Box2D should not be your first C project. Please learn C > programming, compiling, linking, and debugging before working with -> Box2D. There are many resources for this on the net. +> Box2D. There are many resources for this online. ## Scope This manual covers the majority of the Box2D API. However, not every -aspect is covered. Please look at the testbed included -with Box2D to learn more. +aspect is covered. Please look at the Reference section and samples +application included with Box2D to learn more. This manual is only updated with new releases. The latest version of Box2D may be out of sync with this manual. @@ -46,28 +46,25 @@ There is also a [Discord server](https://discord.gg/NKYgCBP) and a [subreddit](https://reddit.com/r/box2d) for Box2D. ## Core Concepts -Box2D works with several fundamental concepts and objects. We briefly +Box2D works with several fundamental concepts and objects. I briefly define these objects here and more details are given later in this document. -### shape -A shape is 2D geometrical object, such as a circle or polygon. - ### rigid body A chunk of matter that is so strong that the distance between any two bits of matter on the chunk is constant. They are hard like a diamond. -In the following discussion we use body interchangeably with rigid body. +In the following discussion I use *body* interchangeably with rigid body. -### fixture -A fixture binds a shape to a body and adds material properties such as -density, friction, and restitution. A fixture puts a shape into the +### shape +A shape binds collision geometry to a body and adds material properties such as +density, friction, and restitution. A shape puts collision geometry into the collision system (broad-phase) so that it can collide with other shapes. ### constraint A constraint is a physical connection that removes degrees of freedom from bodies. A 2D body has 3 degrees of freedom (two translation -coordinates and one rotation coordinate). If we take a body and pin it -to the wall (like a pendulum) we have constrained the body to the wall. +coordinates and one rotation coordinate). If I take a body and pin it +to the wall (like a pendulum) I have constrained the body to the wall. At this point the body can only rotate about the pin, so the constraint has removed 2 degrees of freedom. @@ -76,10 +73,10 @@ A special constraint designed to prevent penetration of rigid bodies and to simulate friction and restitution. You do not create contact constraints; they are created automatically by Box2D. -### joint +### joint constraint This is a constraint used to hold two or more bodies together. Box2D supports several joint types: revolute, prismatic, distance, and more. -Some joints may have limits and motors. +Joints may have limits, motors, and/or springs. ### joint limit A joint limit restricts the range of motion of a joint. For example, the @@ -88,17 +85,26 @@ human elbow only allows a certain range of angles. ### joint motor A joint motor drives the motion of the connected bodies according to the joint's degrees of freedom. For example, you can use a motor to drive -the rotation of an elbow. +the rotation of an elbow. Motors have a target speed and a maximum force +or torque. The simulation will apply the force or torque required to +achieve the desired speed. + +### joint spring +A joint spring has a stiffness and damping. In Box2D spring stiffness is +expressed in terms or Hertz or cycles per second. This lets you configure how +quickly a spring reacts regardless of the body masses. Joint springs also +have a damping ratio to let you specify how quickly the spring will come to +rest. ### world -A physics world is a collection of bodies, fixtures, and constraints -that interact together. Box2D supports the creation of multiple worlds, -but this is usually not necessary or desirable. +A physics world is a collection of bodies, shapes, joints, and contacts +that interact together. Box2D supports the creation of multiple worlds which +are completely independent. ### solver The physics world has a solver that is used to advance time and to resolve contact and joint constraints. The Box2D solver is a high -performance iterative solver that operates in order N time, where N is +performance sequential solver that operates in order N time, where N is the number of constraints. ### continuous collision @@ -108,17 +114,28 @@ intervention this can lead to tunneling. Box2D contains specialized algorithms to deal with tunneling. First, the collision algorithms can interpolate the motion of two bodies to find -the first time of impact (TOI). Second, there is a sub-stepping solver -that moves bodies to their first time of impact and then resolves the -collision. +the first time of impact (TOI). Second, speculative collision is used to create +contact constraints between bodies before they touch. + +### events +World simulation leads to the creation of events that are available at the end +of the time step: + +- body movement events +- contact begin and end events +- contact hit events + +These events allow your application to react to changes in the simulation. ## Modules -Box2D is composed of three modules: Common, Collision, and Dynamics. The -Common module has code for allocation, math, and settings. The Collision -module defines shapes, a broad-phase, and collision functions/queries. -Finally the Dynamics module provides the simulation world, bodies, -fixtures, and joints. -![Box2D Modules](images/modules.svg) +Box2D's primary purpose is to provide rigid body simulation. However, +there are math and collision features that may be useful apart from the +rigid body simulation. These are provided in the `include` directory. Anything +in the `include` directory is considered public, while everything in the `src` +directory is consider internal. + +Public features are supported and you can get help with these on the Discord +server. Using internal code directly is not supported. ## Units Box2D works with floating point numbers and tolerances have to be used @@ -126,7 +143,8 @@ to make Box2D perform well. These tolerances have been tuned to work well with meters-kilogram-second (MKS) units. In particular, Box2D has been tuned to work well with moving shapes between 0.1 and 10 meters. So this means objects between soup cans and buses in size should work well. -Static shapes may be up to 50 meters long without trouble. +Static shapes may be up to 50 meters long without trouble. If you have a +large world, you should split it up into multiple static bodies. Being a 2D physics engine, it is tempting to use pixels as your units. Unfortunately this will lead to a poor simulation and possibly weird @@ -134,10 +152,11 @@ behavior. An object of length 200 pixels would be seen by Box2D as the size of a 45 story building. > **Caution**: -> Box2D is tuned for MKS units. Keep the size of moving objects roughly -> between 0.1 and 10 meters. You'll need to use some scaling system when -> you render your environment and actors. The Box2D testbed does this by -> using an OpenGL viewport transform. DO NOT USE PIXELS. +> Box2D is tuned for MKS units. Keep the size of moving objects larger than 1cm. +> You'll need to use some scaling system when +> you render your environment and actors. The Box2D samples application +> does this by using an OpenGL viewport transform. Do not use pixel units +> unless you understand the implications. It is best to think of Box2D bodies as moving billboards upon which you attach your artwork. The billboard may move in a unit system of meters, @@ -146,71 +165,90 @@ factor. You can then use those pixel coordinates to place your sprites, etc. You can also account for flipped coordinate axes. Another limitation to consider is overall world size. If your world units -become larger than 2 kilometers or so, then the lost precision can affect +become larger than 12 kilometers or so, then the lost precision can affect stability. > **Caution**: -> Box2D works best with world sizes less than 2 kilometers. Use -> b2World::ShiftOrigin to support larger worlds. +> Box2D works best with world sizes less than 12 kilometers. If you are +> careful with your simulation tuning, this can be pushed up to around 24 +> kilometers, which is much larger than most game worlds. -If you need to have a larger game world, consider using -b2World::ShiftOrigin to keep the world origin close to your player. I recommend -to use grid lines along with some hysteresis for triggering calls to ShiftOrigin. -This call should be made infrequently because it is has CPU cost. You may -need to store a physics offset when translating between game units and Box2D units. - -Box2D uses radians for angles. The body rotation is stored in radians -and may grow unbounded. Consider normalizing the angle of your bodies if -the magnitude of the angle becomes too large (use `b2Body::SetTransform`). +Box2D uses radians for angles. The body rotation is stored a complex number, +so when you access the angle of a body, it will be between \f$-\pi\f$ and \f$\pi\f$ radians. > **Caution**: > Box2D uses radians, not degrees. ## Changing the length units -Advanced users may change the length unit modifying `b2_lengthUnitsPerMeter`. -You can avoid merge conflicts by defining `B2_USER_SETTINGS` and providing -`b2_user_settings.h`. See the file `b2_settings.h` for details. +Advanced users may change the length unit by calling `b2SetLengthUnitsPerMeter()` +at application startup. If you keep Box2D in a shared library, you will need +to call this if the shared library is reloaded. + +If you change the length units to pixels you will need to decide how many pixels +represent a meter. You will also +need to figure out reasonable values for gravity, density, force, and torque. +One of the benefits of using MKS units for physics simulation is that you +can use real world values to get reasonable results. -## Factories and Definitions +It is also harder to get support for using Box2D if you change the unit +system, because values are harder to communicate and may become non-intuitive. + +## Ids and Definitions Fast memory management plays a central role in the design of the Box2D -API. So when you create a b2Body or a b2Joint, you need to call the -factory functions on b2World. You should never try to allocate these -types in another manner. +interface. When you create a world, body, shape or joint, you will receive +a handle called an *id*. These ids are opaque and are passed to various functions +to access the underlying data. + +These ids provide some safety. If you use an id after it has been freed you will +usually get an assertion. All ids support 64k generations of safety. All ids +also have a corresponding function you can call to check if it is valid. -There are creation functions: +When you create a world, body, shape, or joint, you need to provide a definition structure. +These definitions contain all the information needed to build the Box2D object. By using +this approach I can prevent construction errors, keep the number of function parameters +small, provide sensible defaults, and reduce the number of accessors. -```cpp -b2Body* b2World::CreateBody(const b2BodyDef* def) -b2Joint* b2World::CreateJoint(const b2JointDef* def) +Here is an example of body creation: + +```c +b2BodyDef bodyDef = b2DefaultBodyDef(); +bodyDef.position = (b2Vec2){10.0f, 5.0f}; +b2BodyId myBodyId = b2CreateBody(myWorldId, &bodyDef); ``` -And there are corresponding destruction functions: +Notice the body definition is initialize by calling `b2DefaultBodyDef()`. This is needed +because C does not have constructors and zero initialization is not suitable for the definitions +used in Box2D. + +Also notice that the body definition is a temporary object that is fully copied into the internal +body data structures. Definitions should usually be created on the stack as temporaries. + +This is how a body is destroyed: -```cpp -void b2World::DestroyBody(b2Body* body) -void b2World::DestroyJoint(b2Joint* joint) +```c +b2DestroyBody(myBodyId); +myBodyId = b2_nullBodyId; ``` -When you create a body or joint, you need to provide a definition. These -definitions contain all the information needed to build the body or -joint. By using this approach we can prevent construction errors, keep -the number of function parameters small, provide sensible defaults, and -reduce the number of accessors. +Notice that the body id is set to null using the constant `b2_nullBodyId`. You should treat +ids as opaque data, however you may zero initialize all Box2D ids and they will be considered +*null*. -Since fixtures (shapes) must be parented to a body, they are created and -destroyed using a factory method on b2Body: +Shapes are created in a similar way. For example, here is how a box shape is created: -```cpp -b2Fixture* b2Body::CreateFixture(const b2FixtureDef* def) -void b2Body::DestroyFixture(b2Fixture* fixture) +```c +b2ShapeDef shapeDef = b2DefaultShapeDef(); +shapeDef.friction = 0.42f; +b2Polygon box = b2MakeBody(0.5f, 0.25f); +b2ShapeId myShapeId = b2CreateCircleShape(myBodyId, &shapeDef, &box); ``` -There is also shortcut to create a fixture directly from the shape and -density. +And the shape may be destroyed as follows: -```cpp -b2Fixture* b2Body::CreateFixture(const b2Shape* shape, float density) +```c +b2DestroyShape(myShapeId); +myShapeId = b2_nullShapeId; ``` -Factories do not retain references to the definitions. So you can create -definitions on the stack and keep them in temporary resources. +For convenience, Box2D will destroy all shapes on a body when the body is destroyed. +Therefore, you may not need to store the shape id. diff --git a/docs/references.md b/docs/reading.md similarity index 91% rename from docs/references.md rename to docs/reading.md index 4d2aa5a4..071b6f49 100644 --- a/docs/references.md +++ b/docs/reading.md @@ -1,4 +1,4 @@ -# References +# Further Reading - [Erin Catto's Publications](https://box2d.org/publications/) - Collision Detection in Interactive 3D Environments, Gino van den Bergen, 2004 - Real-Time Collision Detection, Christer Ericson, 2005 diff --git a/docs/samples.md b/docs/samples.md new file mode 100644 index 00000000..1a2c0849 --- /dev/null +++ b/docs/samples.md @@ -0,0 +1,21 @@ +# Samples {#samples} +Once you have conquered the HelloWorld example, you should start looking +at Box2D's samples application. The samples application is a testing framework and demo +environment. Here are some of the features: +- Camera with pan and zoom +- Mouse dragging of dynamic bodies +- Many samples in a tree view +- GUI for selecting samples, parameter tuning, and debug drawing options +- Pause and single step simulation +- Multithreading and performance data + +![Box2D Samples](images/samples.png) + +The samples application has many examples of Box2D usage in the test cases and the +framework itself. I encourage you to explore and tinker with the samples +as you learn Box2D. + +Note: the sample application is written using [GLFW](https://www.glfw.org), +[imgui](https://github.com/ocornut/imgui), and [enkiTS](https://github.com/dougbinks/enkiTS). +The samples app is not part of the Box2D library. The Box2D library is agnostic about rendering. +As shown by the HelloWorld example, you don't need a renderer to use Box2D. diff --git a/docs/simulation.md b/docs/simulation.md new file mode 100644 index 00000000..ab7a3e0a --- /dev/null +++ b/docs/simulation.md @@ -0,0 +1,1717 @@ +# Simulation +Rigid body simulation is the primary feature of Box2D. It is the most complex part of +Box2D and is the part you will likely interact with the most. Simulation sits on top of +the foundation and collision types and functions, so you should be somewhat familiar +with those by now. + +Rigid body simulation contains: +- worlds +- bodies +- shapes +- contacts +- joints +- events + +There are many dependencies between these objects so it is difficult to +describe one without referring to another. In the following, you +may see some references to objects that have not been described yet. +Therefore, you may want to quickly skim this section before reading it +closely. + +## Ids +Box2D has a C interface. Typically in a C/C++ library when you create an object with a long lifetime +you will keep a pointer (or smart pointer) to the object. + +Box2D works differently. Instead of pointers, you are given an *id* when you create an object. +This *id* acts as a [handle](https://en.wikipedia.org/wiki/Handle_(computing)) and help avoid +problems with [dangling pointers](https://en.wikipedia.org/wiki/Dangling_pointer). + +This also allows Box2D to use [data-oriented design](https://en.wikipedia.org/wiki/Data-oriented_design) internally. +This helps to reduce cache misses drastically and also allows for [SIMD](https://en.wikipedia.org/wiki/Single_instruction,_multiple_data) +optimizations. + +So you will be dealing with `b2WorldId`, `b2BodyId`, etc. These are small opaque structures that you +will pass around by value, just like pointers. Box2D creation functions return an id. Functions +that operate on Box2D objects take ids. + +```c +b2BodyId myBodyId = b2CreateBody(myWorldId, &myBodyDef); +``` + +There are functions to check if an id is valid. Box2D functions will assert if you use an invalid id. +This makes debugging easier than using dangling pointers. + +```c +if (b2Body_IsValid(myBodyId) == false) +{ + // oops +} +``` + +## World +The Box2D world contains the bodies and joints. It manages all aspects +of the simulation and allows for asynchronous queries (like AABB queries +and ray-casts). Much of your interactions with Box2D will be with a +world object, using `b2WorldId`. + +### World Definition +Worlds are created using a *definition* structure. This is temporary structure that +you can use to configure options for world creation. You **must** initialize the world definition +using `b2DefaultWorldDef()`. + +```c +b2WorldDef worldDef = b2DefaultWorldDef(); +``` + +The world definition has lots of options, but for most you will use the defaults. You may want to set the gravity: + +```c +worldDef.gravity = (b2Vec2){0.0f, -10.0f}; +``` + +If your game doesn't need sleep, you can get a performance boost by completely disabling sleep: + +```c +worldDef.enableSleep = false; +``` + +You can also configure multithreading to improve performance: + +```c +worldDef.workerCount = 4; +worldDef.enqueueTask = myAddTaskFunction; +worldDef.finishTask = myFinishTaskFunction; +worldDef.userTaskContext = &myTaskSystem; +``` + +Multithreading is not required but it can improve performance substantially. Read more [here](#multi). + +### World Lifetime +Creating a world is done using a world definition. + +```c +b2WorldId myWorldId = b2CreateWorld(&worldDef); + +// ... do stuff ... + +b2DestroyWorld(myWorldId); + +// Nullify id for safety +myWorldId = b2_nullWorldId; +``` + +You can create up to 128 worlds. These worlds do not interact and may be simulated in parallel. + +When you destroy a world, every body, shape, and joint is also destroyed. This is much faster +than destroying individual objects. + +### Simulation +The world is used to drive the simulation. You specify a time step +and a sub-step count. For example: + +```c +float timeStep = 1.0f / 60.f; +int32_t subSteps = 10; +b2World_Step(myWorldId, timeStep, subSteps); +``` + +After the time step you can examine your bodies and joints for +information. Most likely you will grab the position off the bodies so +that you can update your game objects and render them. Or more optimally, you +will use `b2World_GetBodyEvents()`. + +You can perform the time step anywhere in your game loop, but you should be aware of the +order of things. For example, you must create bodies before the time +step if you want to get collision results for the new bodies in that +frame. + +As I discussed in the [HelloWorld tutorial](#hello), you should use a fixed +time step. By using a larger time step you can improve performance in +low frame rate scenarios. But generally you should use a time step no +larger than 1/30 seconds (30Hz). A time step of 1/60 seconds (60Hz) will usually +deliver a high quality simulation. + +The sub-step count is used to increase accuracy. By sub-stepping the solver +divides up time into small increments and the bodies move by a small amount. +This allows joints and contacts to respond with finer detail. The recommended +sub-step count is 4. However, increasing the sub-step count may improve +accuracy. For example, long joint chains will stretch less with more sub-steps. + +The scissor lift sample shown [here](#samples) works better with more sub-steps +and is configured to use 8 sub-steps. With a primary time step of 1/60 seconds, +the scissor lift is taking sub-steps at 480Hz! + +## Rigid Bodies +Rigid bodies, or just *bodies* have position and velocity. You can apply forces, torques, +and impulses to bodies. Bodies can be static, kinematic, or dynamic. Here +are the body type definitions: + +### Body types +#b2_staticBody: +A static body does not move under simulation and behaves as if it has infinite mass. +Internally, Box2D stores zero for the mass and the inverse mass. A static body has zero +velocity. Static bodies do not collide with other static or kinematic bodies. + +#b2_kinematicBody: +A kinematic body moves under simulation according to its velocity. +Kinematic bodies do not respond to forces. A kinematic body is moved by setting its +velocity. A kinematic body behaves as if it has infinite mass, however, +Box2D stores zero for the mass and the inverse mass. Kinematic bodies do +not collide with other kinematic or static bodies. Generally you should use +a kinematic body if you want a shape to be animated and not affected by +forces or collisions. + +#b2_dynamicBody: +A dynamic body is fully simulated and moves according to forces and torques. +A dynamic body can collide with all body types. A dynamic body always has +finite, non-zero mass. + +> **Caution**: +> Generally you should not set the transform on bodies after creation. +> Box2D treats this as a teleport and may result in undesirable behavior. + +Bodies carry shapes and moves them around in the world. Bodies are always +rigid bodies in Box2D. That means that two shapes attached to the same rigid body never move +relative to each other and shapes attached to the same body don't +collide. + +Shapes have collision geometry and density. Normally, bodies acquire +their mass properties from the shapes. However, you can override the +mass properties after a body is constructed. + +You usually keep ids to all the bodies you create. This way you can +query the body positions to update the positions of your graphical +entities. You should also keep body ids so you can destroy them +when you are done with them. + +### Body Definition +Before a body is created you must create a body definition (`b2BodyDef`). +The body definition holds the data needed to create and initialize a +body correctly. + +Because Box2D uses a C API, a function is provided to create a default +body definition. + +```c +b2BodyDef myBodyDef = b2DefaultBodyDef(); +``` + +This ensures the body definition is valid and this initialization is **mandatory**. + +Box2D copies the data out of the body definition; it does not keep a +pointer to the body definition. This means you can recycle a body +definition to create multiple bodies. + +Let's go over some of the key members of the body definition. + +### Body Type +As discussed previously, there are three different +body types: static, kinematic, and dynamic. b2_staticBody is the default. +You should establish the body type at creation because changing the body type +later is expensive. + +```c +b2BodyDef bodyDef; +bodyDef.type = b2_dynamicBody; +``` + +### Position and Angle +You can initialize the body position and angle in the body definition. This has far +better performance than creating the body at the world origin and then moving the body. + +> **Caution**: +> Do not create a body at the origin and then move it. If you create +> several bodies at the origin, then performance will suffer. + +A body has two main points of interest. The first point is the body's +origin. Shapes and joints are attached relative to the body's origin. +The second point of interest is the center of mass. The center of mass +is determined from the mass distribution of the attached shapes or is +explicitly set with `b2MassData`. Much of Box2D's internal computations +use the center of mass position. For example the body stores the linear +velocity for the center of mass, not the body origin. + +![Body Origin and Center of Mass](images/center_of_mass.svg) + +When you are building the body definition, you may not know where the +center of mass is located. Therefore you specify the position of the +body's origin. You may also specify the body's angle in radians. If you later +change the mass properties of the body, then the center of mass may move +on the body, but the origin position and body angle does not change and the attached +shapes and joints do not move. + +```c +b2BodyDef bodyDef = b2DefaultBodyDef(); +bodyDef.position = (b2Vec2){0.0f, 2.0f}; +bodyDef.angle = 0.25f * b2_pi; +``` + +A rigid body is a frame of reference. You can define shapes and +joints in that frame. Those shapes and joint anchors never move in the +local frame of the body. + +### Damping +Damping is used to reduce the world velocity of bodies. Damping is +different than friction because friction only occurs with contact. +Damping is not a replacement for friction and the two effects are +used together. + +Damping parameter are non-negative. Normally you will use a +damping value between 0 and 1. I generally do not use linear damping +because it makes bodies look like they are floating. + +```c +bodyDef.linearDamping = 0.0f; +bodyDef.angularDamping = 0.1f; +``` + +Damping is approximated to improve performance. At small damping +values the damping effect is mostly independent of the time step. At +larger damping values, the damping effect will vary with the time step. +This is not an issue if you use a fixed time step (recommended). + +Here's some math for the curious. A first-order different equation for velocity damping is: + +\f[ +\frac{dv}{dt} + c v = 0 +\f] + +The solution with initial velocity \f$v_0\f$ is +\f[ +v = v_0 e^{-c t} +\f] + +Across a single time step \f$h\f$ the velocity evolves like so +\f[ +v(t + h) = v_0 e^{-c (t + h)} = v_0 e^{-c t} e^{-c h} = v(t) e^{-c h} +\f] + +Using the [Pade approximation](https://en.wikipedia.org/wiki/Pad%C3%A9_table) for the +exponential function gives the update formula: +\f[ +v(t + h) \approx \frac{1}{1 + c h} v(t) +\f] + +This is the formula used in the Box2D solver. + +### Gravity Scale +You can use the gravity scale to adjust the gravity on a single body. Be +careful though, a large gravity magnitude can decrease stability. + +```c +// Set the gravity scale to zero so this body will float +bodyDef.gravityScale = 0.0f; +``` + +### Sleep Parameters +What does sleep mean? Well it is expensive to simulate bodies, so the +less we have to simulate the better. When a body comes to rest we would +like to stop simulating it. + +When Box2D determines that a body (or group of bodies) has come to rest, +the body enters a sleep state which has very little CPU overhead. If a +body is awake and collides with a sleeping body, then the sleeping body +wakes up. Bodies will also wake up if a joint or contact attached to +them is destroyed. You can also wake a body manually. + +The body definition lets you specify whether a body can sleep and +whether a body is created sleeping. + +```c +bodyDef.enableSleep = true; +bodyDef.isAwake = true; +``` + +The `isAwake` flag is ignored if `enableSleep` is false. + +### Fixed Rotation +You may want a rigid body, such as a character, to have a fixed +rotation. Such a body does not rotate, even under load. You can use +the fixed rotation setting to achieve this: + +```c +bodyDef.fixedRotation = true; +``` + +The fixed rotation flag causes the rotational inertia and its inverse to +be set to zero. + +### Bullets {#bullets} +Game simulation usually generates a sequence of transforms that are played +at some frame rate. This is called discrete simulation. In discrete +simulation, rigid bodies can move by a large amount in one time step. If +a physics engine doesn't account for the large motion, you may see some +objects incorrectly pass through each other. This effect is called +*tunneling*. + +By default, Box2D uses continuous collision detection (CCD) to prevent +dynamic bodies from tunneling through static bodies. This is done by +sweeping shapes from their old position to their new positions. The +engine looks for new collisions during the sweep and computes the time +of impact (TOI) for these collisions. Bodies are moved to their first +TOI at the end of the time step. + +Normally CCD is not used between dynamic bodies. This is done to keep +performance reasonable. In some game scenarios you need dynamic bodies +to use CCD. For example, you may want to shoot a high speed bullet at a +stack of dynamic bricks. Without CCD, the bullet might tunnel through +the bricks. + +Fast moving objects in Box2D can be configured as *bullets*. Bullets will +perform CCD with all body types, but not other bullets. You should decide what +bodies should be bullets based on your game design. If you decide a body +should be treated as a bullet, use the following setting. + +```c +bodyDef.isBullet = true; +``` + +The bullet flag only affects dynamic bodies. I recommend using bullets sparingly. + +### Disabling +You may wish a body to be created but not participate in collision or +simulation. This state is similar to sleeping except the body will not be +woken by other bodies and the body's shapes will not collide with anything. +This means the body will not participate in collisions, ray +casts, etc. + +You can create a body as disabled and later enable it. + +```c +bodyDef.isEnabled = false; + +// Later ... +b2Body_Enable(myBodyId); +``` + +Joints may be connected to disabled bodies. These joints will not be +simulated. You should be careful when you enable a body that its +joints are not distorted. + +Note that enabling a body is almost as expensive as creating the body +from scratch. So you should not use body disabling for streaming worlds. Instead, use +creation/destruction for streaming worlds to save memory. + +Body disabling is a convenience and is generally not good for performance. + +### User Data +User data is a void pointer. This gives you a hook to link your +application objects to bodies. You should be consistent to use the same +object type for all body user data. + +```c +bodyDef.userData = &myGameObject; +``` + +This is useful when you receive results from a query such as a ray-cast +or event and you want to get back to your game object. You can acquire the +use data from a body using `b2Body_GetUserData()`. + +### Body Lifetime +Bodies are created and destroyed using a world id. This lets the world create +the body with an efficient allocator and add the body to the world data structure. + +```c +b2BodyId myBodyId = b2CreateBody(myWorldId, &bodyDef); + +// ... do stuff ... + +b2DestroyBody(myBodyId); + +// Nullify body id for safety +myBodyId = b2_nullBodyId; +``` + +Box2D does not keep a reference to the body definition or any of the +data it holds (except user data pointers). So you can create temporary +body definitions and reuse the same body definitions. + +Box2D allows you to avoid destroying bodies by destroying the world +directly using `b2DestroyWorld()`, which does all the cleanup work for you. +However, you should be mindful to nullify body ids that you keep in your application. + +When you destroy a body, the attached shapes and joints are +automatically destroyed. This has important implications for how you +manage shape and joint ids. You should nullify these ids after destroying +a body. + +### Using a Body +After creating a body, there are many operations you can perform on the +body. These include setting mass properties, accessing position and +velocity, applying forces, and transforming points and vectors. + +### Mass Data +A body has mass (scalar), center of mass (2-vector), and rotational +inertia (scalar). For static bodies, the mass and rotational inertia are +set to zero. When a body has fixed rotation, its rotational inertia is +zero. + +Normally the mass properties of a body are established automatically +when shapes are added to the body. You can also adjust the mass of a +body at run-time. This is usually done when you have special game +scenarios that require altering the mass. + +```c +b2MassData myMassData; +myMassData.mass = 10.0f; +myMassData.center = (b2Vec2){0.0f, 0.0f}; +myMassData.I = 100.0f; +b2Body_SetMassData(myBodyId, &myMassData); +``` + +After setting a body's mass directly, you may wish to revert to the +mass determined by the shapes. You can do this with: + +```c +b2Body_ApplyMassFromShapes(myBodyId); +``` + +The body's mass data is available through the following functions: + +```c +float mass = b2Body_GetMass(myBodyId); +float inertia = b2Body_GetInertiaTensor(myBodyId); +b2Vec2 localCenter b2Body_GetLocalCenterOfMass(myBodyId); +b2MassData massData = b2Body_GetMassData(myBodyId); +``` + +### State Information +There are many aspects to the body's state. You can access this state +data through the following functions: + +```c +b2Body_SetType(myBodyId, b2_kinematicBody); +b2BodyType bodyType = b2Body_GetType(myBodyId); +b2Body_SetBullet(myBodyId, true); +bool isBullet = b2Body_IsBullet(myBodyId); +b2Body_EnableSleep(myBodyId, false); +bool isSleepEnabled = b2Body_IsSleepingEnabled(myBodyId); +b2Body_SetAwake(myBodyId, true); +bool isAwake = b2Body_IsAwake(myBodyId); +b2Body_Disable(myBodyId); +b2Body_Enable(myBodyId); +bool isEnabled = b2Body_IsEnabled(myBodyId); +b2Body_SetFixedRotation(myBodyId, true); +bool isFixedRotation = b2Body_IsFixedRotation(myBodyId); +``` + +Please see the comments on these functions for more details. + +### Position and Velocity +You can access the position and rotation of a body. This is common when +rendering your associated game object. You can also set the position and angle, +although this is less common since you will normally use Box2D to +simulate movement. + +Keep in mind that the Box2D interface uses *radians*. + +```c +b2Body_SetTransform(myBodyId, position, angleInRadians); +b2Transform transform = b2Body_GetTransform(myBodyId); +b2Vec2 position = b2Body_GetPosition(myBodyId); +b2Rot rotation = b2Body_GetRotation(myBodyId); +float angleInRadians = b2Body_GetAngle(myBodyId); +``` + +You can access the center of mass position in local and world +coordinates. Much of the internal simulation in Box2D uses the center of +mass. However, you should normally not need to access it. Instead you +will usually work with the body transform. For example, you may have a +body that is square. The body origin might be a corner of the square, +while the center of mass is located at the center of the square. + +```c +b2Vec2 worldCenter = b2Body_GetWorldCenterOfMass(myBodyId); +b2Vec2 localCenter = b2Body_GetLocalCenterOfMass(myBodyId); +``` + +You can access the linear and angular velocity. The linear velocity is +for the center of mass. Therefore, the linear velocity may change if the +mass properties change. Since Box2D uses radians, the angular velocity is +in radians per second. + +```c +b2Vec2 linearVelocity = b2Body_GetLinearVelocity(myBodyId); +float angularVelocity = b2Body_GetAngularVelocity(myBodyId); +``` + +### Forces and Impulses +You can apply forces, torques, and impulses to a body. When you apply a +force or an impulse, you can provide a world point where the load is +applied. This often results in a torque about the center of mass. + +```c +b2Body_ApplyForce(myBodyId, force, worldPoint, wake); +b2Body_ApplyTorque(myBodyId, torque, wake); +b2Body_ApplyLinearImpulse(myBodyId, linearImpulse, worldPoint, wake); +b2Body_ApplyAngularImpulse(myBodyId, angularImpulse, wake); +``` + +Applying a force, torque, or impulse optionally wakes the body. If you don't +wake the body and it is asleep, then the force or impulse will be ignored. + +You can also apply a force and linear impulse to the center of mass to avoid rotation. + +```c +b2Body_ApplyForceToCenter(myBodyId, force, wake); +b2Body_ApplyLinearImpulseToCenter(myBodyId, linearImpulse, wake); +``` + +> **Caution**: +> Since Box2D uses sub-stepping, you should not apply a steady impulse +> for several frames. Instead you should apply a force which Box2D will +> spread out evenly across the sub-steps, resulting in smoother movement. + +### Coordinate Transformations +The body has some utility functions to help you transform points +and vectors between local and world space. If you don't understand +these concepts, I recommend reading \"Essential Mathematics for Games and +Interactive Applications\" by Jim Van Verth and Lars Bishop. + +```c +b2Vec2 worldPoint = b2Body_GetWorldPoint(myBodyId, localPoint); +b2Vec2 worldVector = b2Body_GetWorldVector(myBodyId, localVector); +b2Vec2 localPoint = b2Body_GetLocalPoint(myBodyId, worldPoint); +b2Vec2 localVector = b2Body_GetLocalVector(myBodyId, worldVector); +``` + +### Accessing Shapes and Joints +You can access the shapes on a body. You can get the number of shapes first. + +```c +int shapeCount = b2Body_GetShapeCount(myBodyId); +``` + +If you have bodies with many shapes, you can allocate an array or if you +know the number is limited you can use a fixed size array. + +```c +b2ShapeId shapeIds[10]; +int returnCount = b2Body_GetShapes(myBodyId, shapeIds, 10); + +for (int i = 0; i < returnCount; ++i) +{ + b2ShapeId shapeId = shapeIds[i]; + + // do something with shapeId +} +``` + +You can similarly get an array of the joints on a body. + +### Body Events +While you can gather transforms from all your bodies after every time step, this is inefficient. +Many bodies may not have moved because they are sleeping. Also iterating across many bodies +will have lots of cache misses. + +Box2D provides `b2BodyEvents` that you can access after every call to `b2World_Step()` to get +an array of body movement events. Since this data is contiguous, it is cache friendly. + +```c +b2BodyEvents events = b2World_GetBodyEvents(m_worldId); +for (int i = 0; i < events.moveCount; ++i) +{ + const b2BodyMoveEvent* event = events.moveEvents + i; + MyGameObject* gameObject = event->userData; + MoveGameObject(gameObject, event->transform); + if (event->fellAsleep) + { + SleepGameObject(gameObject); + } +} +``` + +The body event also indicates if the body fell asleep this time step. This might be useful to +optimize your application. + +## Shapes +A body may have zero or more shapes. A body with multiple shapes is sometimes +called a *compound body.* + +Shapes hold the following: +- a shape primitive +- density, friction, and restitution +- collision filtering flags +- parent body id +- user data +- sensor flag + +These are described in the following sections. + +### Shape Lifetime +Shapes are created by initializing a shape definition and a shape primitive. +These are passed to a creation function specific to each shape type. + +```c +b2ShapeDef shapeDef = b2DefaultShapeDef(); +shapeDef.density = 10.0f; +shapeDef.friction = 0.7f; + +b2Polygon box = b2MakeBox(0.5f, 1.0f); +b2ShapeId myShapeId = b2CreatePolygonShape(myBodyId, &shapeDef, &box); +``` + +This creates a polygon and attaches it to the body. You do not need to +store the shape id since the shape will automatically be +destroyed when the parent body is destroyed. However, you may wish to store the shape id if you plan +to change properties on it later. + +You can create multiple shapes on a single body. They all can contribute +to the mass of the body. These shapes never collide with each other and may overlap. + +You can destroy a shape on the parent body. You may do this to model a +breakable object. Otherwise you can just leave the shape alone and let +the body destruction take care of destroying the attached shapes. + +```c +b2DestroyShape(myShapeId); +``` + +Material properties such as density, friction, and restitution are associated with shapes +instead of bodies. Since you can attach multiple shapes to a body, this allows for more +possible setups. For example, you can make a car that is heavier in the back. + +### Density +The shape density is used to compute the mass properties of the parent +body. The density can be zero or positive. You should generally use +similar densities for all your shapes. This will improve stacking +stability. + +The mass of a body is not adjusted when you set the density. You must +call `b2Body_ApplyMassFromShapes()` for this to occur. Generally you should establish +the shape density in `b2ShapeDef` and avoid modifying it later because this +can be expensive, especially on a compound body. + +```c +b2Shape_SetDensity(myShapeId, 5.0f); +b2Body_ApplyMassFromShapes(myBodyId); +``` + +### Friction +Friction is used to make objects slide along each other realistically. +Box2D supports static and dynamic friction, but uses the same parameter +for both. Box2D attempts to simulate friction accurately and the friction +strength is proportional to the normal force. This is called [Coulomb +friction](https://en.wikipedia.org/wiki/Friction). The friction parameter +is usually set between 0 and 1, but +can be any non-negative value. A friction value of 0 turns off friction +and a value of 1 makes the friction strong. When the friction force is +computed between two shapes, Box2D must combine the friction parameters +of the two parent shapes. This is done with the +[geometric mean](https://en.wikipedia.org/wiki/Geometric_mean): + +```c +float mixedFriction = sqrtf(b2Shape_GetFriction(shapeIdA) * b2Shape_GetFriction(shapeIdB)); +``` + +If one shape has zero friction then the mixed friction will be zero. + +### Restitution +[Restitution](https://en.wikipedia.org/wiki/Coefficient_of_restitution) is used to make +objects bounce. The restitution value is +usually set to be between 0 and 1. Consider dropping a ball on a table. +A value of zero means the ball won't bounce. This is called an +*inelastic* collision. A value of one means the ball's velocity will be +exactly reflected. This is called a *perfectly elastic* collision. +Restitution is combined using the following formula. + +```c +float mixedRestitution = b2MaxFloat(b2Shape_GetRestitution(shapeIdA), b2Shape_GetRestitution(shapeIdB)); +``` + +Restitution is combined this way so that you can have a bouncy super +ball without having a bouncy floor. + +When a shape develops multiple contacts, restitution is simulated +approximately. This is because Box2D uses an sequential solver. Box2D +also uses inelastic collisions when the collision velocity is small. +This is done to prevent jitter. See `b2WorldDef::restitutionThreshold`. + +### Filtering {#filtering} +Collision filtering allows you to efficiently prevent collision between shapes. +For example, say you make a character that rides a bicycle. You want the +bicycle to collide with the terrain and the character to collide with +the terrain, but you don't want the character to collide with the +bicycle (because they must overlap). Box2D supports such collision +filtering using categories, masks, and groups. + +Box2D supports 32 collision categories. For each shape you can specify +which category it belongs to. You can also specify what other categories +this shape can collide with. For example, you could specify in a +multiplayer game that players don't collide with each other. Rather +than identifying all the situations where things should not collide, I recommend +identifying all the situations where things should collide. This way you +don't get into situations where you are using +[double negatives](https://en.wikipedia.org/wiki/Double_negative). +You can specify which things can collide using mask bits. For example: + +```c +enum MyCategories +{ + PLAYER = 0x00000002, + MONSTER = 0x00000004, +}; + +b2ShapeDef playerShapeDef = b2DefaultShapeDef(); +b2ShapeDef monsterShapeDef = b2DefaultShapeDef(); +playerShapeDef.filter.categoryBits = PLAYER; +monsterShapeDef.filter.categoryBits = MONSTER; + +// Players collide with monsters, but not with other players +playerShapeDef.filter.maskBits = MONSTER; + +// Monsters collide with players and other monsters +monsterShapeDef.filter.maskBits = PLAYER | MONSTER; +``` + +Here is the rule for a collision to occur: + +```c +uint32_t catA = shapeA.filter.categoryBits; +uint32_t maskA = shapeA.filter.maskBits; +uint32_t catB = shapeB.filter.categoryBits; +uint32_t maskB = shapeB.filter.maskBits; + +if ((catA & maskB) != 0 && (catB & maskA) != 0) +{ + // shapes can collide +} +``` + +Another filtering feature is *collision group*. +Collision groups let you specify a group index. You can have +all shapes with the same group index always collide (positive index) +or never collide (negative index). Group indices are usually used for +things that are somehow related, like the parts of a bicycle. In the +following example, shape1 and shape2 always collide, but shape3 +and shape4 never collide. + +```c +shape1Def.filter.groupIndex = 2; +shape2Def.filter.groupIndex = 2; +shape3Def.filter.groupIndex = -8; +shape4Def.filter.groupIndex = -8; +``` + +Collisions between shapes of different group indices are filtered +according the category and mask bits. If two shapes have the +same non-zero group index, then this overrides the category and mask. +Collision groups have a higher priority than categories and masks. + +Note that additional collision filtering occurs automatically in Box2D. Here is a +list: +- A shape on a static body can only collide with a dynamic body. +- A shape on a kinematic body can only collide with a dynamic body. +- Shapes on the same body never collide with each other. +- You can optionally enable/disable collision between bodies connected by a joint. + +Sometimes you might need to change collision filtering after a shape +has already been created. You can get and set the `b2Filter` structure on +an existing shape using `b2Shape_GetFilter()` and +`b2Shape_SetFilter()`. Changing the filter is expensive because +it causes contacts to be destroyed. + +### Chain Shapes +The chain shape provides an efficient way to connect many line segments together +to construct your static game worlds. Chain shapes automatically +eliminate ghost collisions and provide one-sided collision. + +If you don't care about ghost collisions, you can create a bunch of +two-sided segment shapes. The performance is similar. + +The simplest way to use chain shapes is to create loops. Simply provide an +array of vertices. + +```c +b2Vec2 points[4] = { + {1.7f, 0.0f}, + {1.0f, 0.25f}, + {0.0f, 0.0f}, + {-1.7f, 0.4f}}; + +b2ChainDef chainDef = b2DefaultChainDef(); +chainDef.points = points; +chainDef.count = 4; + +b2ChainId myChainId = b2CreateChain(myBodyId, &chainDef); + +// Later ... +b2DestroyChain(myChainId); + +// Nullify id for safety +myChainId = b2_nullChainId; +``` + +The segment normal depends on the winding order. A counter-clockwise winding order orients the normal outwards and a clockwise winding order orients the normal inwards. + +![Chain Shape Outwards Loop](images/chain_loop_outwards.svg) + +![Chain Shape Inwards Loop](images/chain_loop_inwards.svg) + +You may have a scrolling game world and would like to connect several chains together. +You can connect chains together using ghost vertices. To do this you must have the first three or last three points of each chain overlap. See the sample `ChainLink` for details. + +![Chain Shape](images/chain_shape.svg) + +Self-intersection of chain shapes is not supported. It might work, it +might not. The code that prevents ghost collisions assumes there are no +self-intersections of the chain. Also, very close vertices can cause +problems. Make sure all your points are more than than about a centimeter apart. + +![Self Intersection is Bad](images/self_intersect.svg) + +Each segment in the chain is created as a `b2SmoothSegment` shape on the body. If you have the +shape id for a smooth segment shape, you can get the owning chain id. This will return `b2_nullChainId` +if the shape is not a smooth segment. + +```c +b2ChainId chainId = b2SHape_GetParentChain(myShapeId); +``` + +You cannot create a smooth segment shape directly. + + +### Sensors +Sometimes game logic needs to know when two shapes overlap yet there +should be no collision response. This is done by using sensors. A sensor +is a shape that detects overlap but does not produce a response. + +You can flag any shape as being a sensor. Sensors may be static, +kinematic, or dynamic. Remember that you may have multiple shapes per +body and you can have any mix of sensors and solid shapes. Also, +sensors only form contacts when at least one body is dynamic, so you +will not get sensors overlap detection for kinematic versus kinematic, +kinematic versus static, or static versus static. Finally sensors do not +detect other sensors. + +Sensor overlap detection is achieved using events, which are described +below. + +## Contacts +Contacts are internal objects created by Box2D to manage collision between pairs of +shapes. They are fundamental to rigid body simulation in games. + +### Terminology +Contacts have a fair bit of terminology that are important to review. + +#### contact point +A contact point is a point where two shapes touch. Box2D approximates +contact with a small number of points. Specifically, contact between +two shapes has 0, 1, or 2 points. This is possible because Box2D uses +convex shapes. + +#### contact normal +A contact normal is a unit vector that points from one shape to another. +By convention, the normal points from shapeA to shapeB. + +#### contact separation +Separation is the opposite of penetration. Separation is negative when +shapes overlap. + +#### contact manifold +Contact between two convex polygons may generate up to 2 contact points. +Both of these points use the same normal, so they are grouped into a +contact manifold, which is an approximation of a continuous region of +contact. + +#### normal impulse +The normal force is the force applied at a contact point to prevent the +shapes from penetrating. For convenience, Box2D uses impulses. The +normal impulse is just the normal force multiplied by the time step. Since +Box2D uses sub-stepping, this is the sub-step time step. + +#### tangent impulse +The tangent force is generated at a contact point to simulate friction. +For convenience, this is stored as an impulse. + +#### contact point id +Box2D tries to re-use the contact impulse results from a time step as the +initial guess for the next time step. Box2D uses contact point ids to match +contact points across time steps. The ids contain geometric features +indices that help to distinguish one contact point from another. + +#### speculative contact +When two shapes are close together, Box2D will create up to two contact +points even if the shapes are not touching. This lets Box2D anticipate +collision to improve behavior. Speculative contact points have positive +separation. + +### Contact Lifetime +Contacts are created when two shape's AABBs begin to overlap. Sometimes +collision filtering will prevent the creation of contacts. Contacts are +destroyed with the AABBs cease to overlap. + +So you might gather that there may be contacts created for shapes that +are not touching (just their AABBs). Well, this is correct. It's a +\"chicken or egg\" problem. We don't know if we need a contact object +until one is created to analyze the collision. We could delete the +contact right away if the shapes are not touching, or we can just wait +until the AABBs stop overlapping. Box2D takes the latter approach +because it lets the system cache information to improve performance. + +### Contact Data +As mentioned before, the contact is created and destroyed by +Box2D automatically. Contact data is not created by the user. However, you are +able to access the contact data. + +You can get contact data from shapes or bodies. The contact data +on a shape is a sub-set of the contact data on a body. The contact +data is only returned for touching contacts. Contacts that are not +touching provide no meaningful information for an application. + +Contact data is returned in arrays. So first you can ask a shape or +body how much space you'll need in your array. This number is conservative +and the actual number of contacts you'll receive may be less than +this number, but never more. + +```c +int shapeContactCapacity = b2Shape_GetContactCapacity(myShapeId); +int bodyContactCapacity = b2Body_GetContactCapacity(myBodyId); +``` + +You could allocate array space to get all the contact data in all cases, or you could use a fixed size +array and get a limited number of results. + +```c +b2ContactData contactData[10]; +int shapeContactCount = b2Shape_GetContactData(myShapeId, contactData, 10); +int bodyContactCount = b2Body_GetContactData(myBodyId, contactData, 10); +``` + +`b2ContactData` contains the two shape ids and the manifold. + +```c +for (int i = 0; i < bodyContactCount; ++i) +{ + b2ContactData* data = contactData + i; + printf("point count = %d\n", data->manifold.pointCount); +} +``` + +Getting contact data off shapes and bodies is not the most efficient +way to handle contact data. Instead you should use contact events. + +### Sensor Events +Sensor events are available after every call to `b2World_Step()`. +Sensor events are the best way to get information about sensors overlaps. There are +events for when a shape begins to overlap with a sensor. + +```c +b2SensorEvents sensorEvents = b2World_GetSensorEvents(myWorldId); +for (int i = 0; i < sensorEvents.beginCount; ++i) +{ + b2SensorBeginTouchEvent* beginTouch = sensorEvents.beginEvents + i; + void* myUserData = b2Shape_GetUserData(beginTouch->visitorShapeId); + // process begin event +} +``` + +And there are events when a shape stops overlapping with a sensor. + +```c +for (int i = 0; i < sensorEvents.endCount; ++i) +{ + b2SensorEndTouchEvent* endTouch = sensorEvents.endEvents + i; + void* myUserData = b2Shape_GetUserData(endTouch->visitorShapeId); + // process end event +} +``` + +You will not get end events if a shape is destroyed. Sensor events should +be processed after the world step and before other game logic. This should +help you avoid processing stale data. + +Sensor events are only enabled for a non-sensor shape if `b2ShapeDef::enableSensorEvents` +is true. + +### Contact Events +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. + +You can access all contact events in a single data structure. This is much more efficient +than using functions like `b2Body_GetContactData()`. + +```c +b2ContactEvents contactEvents = b2World_GetContactEvents(myWorldId); +``` + +None of this data applies to sensors. All events involve at least one dynamic body. + +There are three kinds of contact events: +1. Begin touch events +2. End touch events +3. Hit events + +#### Contact Touch Event +`b2ContactBeginTouchEvent` is recorded when two shapes begin touching. These only +contain the two shape ids. + +```c +for (int i = 0; i < contactEvents.beginCount; ++i) +{ + b2ContactBeginTouchEvent* beginEvent = contactEvents.beginEvents + i; + ShapesStartTouching(beginEvent->shapeIdA, beginEvent->shapeIdB); +} +``` + +`b2ContactEndTouchEvent` is recorded when two shapes stop touching. These only +contain the two shape ids. + +```c +for (int i = 0; i < contactEvents.endCount; ++i) +{ + b2ContactEndTouchEvent* endEvent = contactEvents.endEvents + i; + ShapesStopTouching(endEvent->shapeIdA, endEvent->shapeIdB); +} +``` + +The end touch events are not generated when you destroy a shape or the body that owns it. + +Shapes only generate begin and end touch events if `b2ShapeDef::enableContactEvents` is true. + +#### Hit Events +Typically in games you are mainly concerned about getting contact events for when +two shapes collide at a significant speed so you can play a sound and/or particle effect. Hit +events are the answer for this. + +```c +for (int i = 0; i < contactEvents.hitCount; ++i) +{ + b2ContactHitEvent* hitEvent = contactEvents.hitEvents + i; + if (hitEvent->approachSpeed > 10.0f) + { + // play sound + } +} +``` + +Shapes only generate hit events if `b2ShapeDef::enableHitEvents` is true. +I recommend you only enable this for shapes that need hit events because +it creates some overhead. Box2D also only reports hit events that have an +approach speed is larger than `b2WorldDef::hitEventThreshold`. + +### Contact Filtering +Often in a game you don't want all objects to collide. For example, you +may want to create a door that only certain characters can pass through. +This is called contact filtering, because some interactions are filtered +out. + +Contact filtering is setup on shapes and is covered [here](#filtering). + +### Advanced Contact Handling + +#### Custom Filtering Callback +For the best performance, use the contact filtering provided by `b2Filter`. +However, in some cases you may need custom filtering. You can do +this by registering a custom filter callback that implements `b2CustomFilterFcn()`. + +```c +bool MyCustomFilter(b2ShapeId shapeIdA, b2ShapeId shapeIdB, void* context) +{ + MyGame* myGame = context; + return myGame->WantsCollision(shapeIdA, shapeIdB); +} + +// Elsewhere +b2World_SetCustomFilterCallback(myWorldId, MyCustomFilter, myGame); +``` + +This function must be [thread-safe](https://en.wikipedia.org/wiki/Thread_safety) and must not read from or write to the Box2D world. Otherwise you will get a [race condition](https://en.wikipedia.org/wiki/Race_condition). + +#### Pre-Solve Callback +This is called after collision detection, but before collision +resolution. This gives you a chance to disable the contact based on the contact geometry. For example, you can implement a one-sided platform using this callback. + +The contact will be re-enabled each time through collision processing, +so you will need to disable the contact every time-step. This function must be thread-safe +and must not read from or write to the Box2D world. + +```c +bool MyPreSolve(b2ShapeId shapeIdA, b2ShapeId shapeIdB, b2Manifold* manifold, void* context) +{ + MyGame* myGame = context; + + if (myGame->IsHittingBelowPlatform(shapeIdA, shapeIdB, manifold)) + { + return false; + } + + return true; +} + +// Elsewhere +b2World_SetPreSolveCallback(myWorldId, MyPreSolve, myGame); +``` + +Note this currently does not work with high speed collisions, so you may see a +pause in those situations. + +See the `Platformer` sample for more details. + +## Joints +Joints are used to constrain bodies to the world or to each other. +Typical examples in games include ragdolls, teeters, and pulleys. Joints +can be combined in many different ways to create interesting motions. + +Some joints provide limits so you can control the range of motion. Some +joints provide motors which can be used to drive the joint at a +prescribed speed until a prescribed force/torque is exceeded. And some +joints provide springs with damping. + +Joint motors can be used in many ways. You can use motors to control +position by specifying a joint velocity that is proportional to the +difference between the actual and desired position. You can also use +motors to simulate joint friction: set the joint velocity to zero and +provide a small, but significant maximum motor force/torque. Then the +motor will attempt to keep the joint from moving until the load becomes +too strong. + +### Joint Definition +Each joint type has an associated joint definition. All +joints are connected between two different bodies. One body may be static. +Joints between static and/or kinematic bodies are allowed, but have no +effect and use some processing time. + +If a joint is connected to a disabled body, that joint is effectively disabled. +When the both bodies on a joint become enabled, the joint will automatically +be enabled as well. In other words, you do not need to explicitly enable +or disable a joint. + +You can specify user data for any joint type and you can provide a flag +to prevent the attached bodies from colliding with each other. This is +the default behavior and you must set the `collideConnected` +Boolean to allow collision between to connected bodies. + +Many joint definitions require that you provide some geometric data. +Often a joint will be defined by anchor points. These are points fixed +in the attached bodies. Box2D requires these points to be specified in +local coordinates. This way the joint can be specified even when the +current body transforms violate the joint constraint. Additionally, some joint +definitions need a reference angle between the bodies. +This may be necessary to constrain rotation correctly. + +The rest of the joint definition data depends on the joint type. I +cover these below. + +### Joint Lifetime +Joints are created using creation functions supplied for each joint type. They are destroyed +with a shared function. All joint types share a single id type `b2JointId`. + +Here's an example of the lifetime of a revolute joint: + +```c +b2RevoluteJointDef jointDef = b2DefaultRevoluteJointDef(); +jointDef.bodyIdA = myBodyA; +jointDef.bodyIdB = myBodyB; +jointDef.localAnchorA = (b2Vec2){0.0f, 0.0f}; +jointDef.localAnchorB = (b2Vec2){1.0f, 2.0f}; + +b2JointId myJointId = b2CreateRevoluteJoint(myWorldId, &jointDef); + +// ... do stuff ... + +b2DestroyJoint(myJointId); +myJointId = b2_nullJointId; +``` + +It is always good to nullify your ids after they are destroyed. + +Joint lifetime is related to body lifetime. Joints cannot exist detached from a body. +So when a body is destroyed, all joints attached to that body are automatically destroyed. +This means you need to be careful to avoid using joint ids when the attached body was +destroyed. Box2D will assert if you use a dangling joint id. + +> **Caution**: +> Joints are destroyed when an attached body is destroyed. + +Fortunately you can check if your joint id is valid. + +```c +if (b2Joint_IsValid(myJointId) == false) +{ + myJointId = b2_nullJointId; +} +``` + +This is certainly useful, but should not be overused because if you are creating +and destroying many joints, this may eventually alias to a different joint. All ids have +a limit of 64k generations. + +### Using Joints +Many simulations create the joints and don't access them again until +they are destroyed. However, there is a lot of useful data contained in +joints that you can use to create a rich simulation. + +First of all, you can get the type, bodies, anchor points, and user data from +a joint. + +```c +b2JointType jointType = b2Joint_GetType(myJointId); +b2BodyId bodyIdA = b2Joint_GetBodyA(myJointId); +b2BodyId bodyIdB = b2Joint_GetBodyB(myJointId); +b2Vec2 localAnchorA = b2Joint_GetLocalAnchorA(myJointId); +b2Vec2 localAnchorB = b2Joint_GetLocalAnchorB(myJointId); +void* myUserData = b2Joint_GetUserData(myJointId); +``` + +All joints have a reaction force and torque. Reaction forces are +related to the [free body diagram](https://en.wikipedia.org/wiki/Free_body_diagram). +The Box2D convention is that the reaction force +is applied to body B at the anchor point. You can use reaction forces to +break joints or trigger other game events. These functions may do some +computations, so don't call them if you don't need the result. + +```c +b2Vec2 force = b2Joint_GetConstraintForce(myJointId); +float torque = b2Joint_GetConstraintTorque(myJointId); +``` + +See the sample `BreakableJoint` for more details. + +### Distance Joint +One of the simplest joints is a distance joint which says that the +distance between two points on two bodies must be constant. When you +specify a distance joint the two bodies should already be in place. Then +you specify the two anchor points in local coordinates. The first anchor +point is connected to body A, and the second anchor point is connected +to body B. These points imply the length of the distance constraint. + +![Distance Joint](images/distance_joint.svg) + +Here is an example of a distance joint definition. In this case I +decided to allow the bodies to collide. + +```c +b2DistanceJointDef jointDef = b2DefaultDistanceJointDef(); +jointDef.bodyIdA = myBodyIdA; +jointDef.bodyIdB = myBodyIdB; +jointDef.localAnchorA = (b2Vec2){1.0f, -3.0f}; +jointDef.localAnchorB = (b2Vec2){0.0f, 0.5f}; +b2Vec2 anchorA = b2Body_GetWorldPoint(myBodyIdA, jointDef.localAnchorA); +b2Vec2 anchorB = b2Body_GetWorldPoint(myBodyIdB, jointDef.localAnchorB); +jointDef.length = b2Distance(anchorA, anchorB); +jointDef.collideConnected = true; + +b2JointId myJointId = b2CreateDistanceJoint(myWorldId, &jointDef); +``` + +The distance joint can also be made soft, like a spring-damper +connection. Softness is achieved by enabling the spring and tuning two values in the definition: +Hertz and damping ratio. + +```c +jointDef.enableSpring = true; +jointDef.hertz = 2.0f; +jointDef.dampingRatio = 0.5f; +``` + +The hertz is the frequency of a [harmonic oscillator](https://en.wikipedia.org/wiki/Harmonic_oscillator) (like a +guitar string). Typically the frequency +should be less than a half the frequency of the time step. So if you are using +a 60Hz time step, the frequency of the distance joint should be less than 30Hz. +The reason is related to the [Nyquist frequency](https://en.wikipedia.org/wiki/Nyquist_frequency). + +The damping ratio controls how fast the oscillations dissipate. A damping +ratio of one is [critical damping](https://en.wikipedia.org/wiki/Damping) and prevents +oscillation. + +It is also possible to define a minimum and maximum length for the distance joint. +You can even motorize the distance joint to adjust its length dynamically. +See `b2DistanceJointDef` and the `DistanceJoint` sample for details. + +### Revolute Joint +A revolute joint forces two bodies to share a common anchor point, often +called a hinge point or pivot. The revolute joint has a single degree of freedom: +the relative rotation of the two bodies. This is called the joint angle. + +![Revolute Joint](images/revolute_joint.svg) + +Like all joints, the anchor points are specified in local coordinates. +However, you can use the body utility functions to simplify this. + +```c +b2Vec2 worldPivot = {10.0f, -4.0f}; +b2RevoluteJointDef jointDef = b2DefaultRevoluteJointDef(); +jointDef.bodyIdA = myBodyIdA; +jointDef.bodyIdB = myBodyIdB; +jointDef.localAnchorA = b2Body_GetLocalPoint(myBodyIdA, worldPivot); +jointDef.localAnchorB = b2Body_GetLocalPoint(myBodyIdB, worldPivot); + +b2JointId myJointId = b2CreateRevoluteJoint(myWorldId, &jointDef); +``` + +The revolute joint angle is positive when bodyB rotates counter-clockwise +about the +anchor point. Like all angles in Box2D, the revolute angle is measured in +radians. By convention the revolute joint angle is zero when the two bodies +have equal angles. You can offset this using `b2RevoluteJointDef::referenceAngle`. + +In some cases you might wish to control the joint angle. For this, the +revolute joint can simulate a joint limit and/or a motor. + +A joint limit forces the joint angle to remain between a lower and upper +angle. The limit will apply as much torque as needed to make this +happen. The limit range should include zero, otherwise the joint will +lurch when the simulation begins. The lower and upper limit are relative to +the reference angle. + +A joint motor allows you to specify the joint speed. The speed can be negative or +positive. A motor can have infinite force, but this is usually not desirable. Recall the eternal +question: + +> *What happens when an irresistible force meets an immovable object?* + +I can tell you it's not pretty. So you can provide a maximum torque for +the joint motor. The joint motor will maintain the specified speed +unless the required torque exceeds the specified maximum. When the +maximum torque is exceeded, the joint will slow down and can even +reverse. + +You can use a joint motor to simulate joint friction. Just set the joint +speed to zero, and set the maximum torque to some small, but significant +value. The motor will try to prevent the joint from rotating, but will +yield to a significant load. + +Here's a revision of the revolute joint definition above; this time the +joint has a limit and a motor enabled. The motor is setup to simulate +joint friction. + +```c +b2Vec2 worldPivot = {10.0f, -4.0f}; +b2RevoluteJointDef jointDef = b2DefaultRevoluteJointDef(); +jointDef.bodyIdA = myBodyIdA; +jointDef.bodyIdB = myBodyIdB; +jointDef.localAnchorA = b2Body_GetLocalPoint(myBodyIdA, worldPivot); +jointDef.localAnchorB = b2Body_GetLocalPoint(myBodyIdB, worldPivot); +jointDef.lowerAngle = -0.5f * b2_pi; // -90 degrees +jointDef.upperAngle = 0.25f * b2_pi; // 45 degrees +jointDef.enableLimit = true; +jointDef.maxMotorTorque = 10.0f; +jointDef.motorSpeed = 0.0f; +jointDef.enableMotor = true; +``` +You can access a revolute joint's angle, speed, and motor torque. + +```c +float angleInRadians = b2RevoluteJoint_GetAngle(myJointId); +float speed = b2RevoluteJoint_GetMotorSpeed(myJointId); +float currentTorque = b2RevoluteJoint_GetMotorTorque(myJointId); +``` + +You also update the motor parameters each step. + +```c +b2RevoluteJoint_SetMotorSpeed(myJointId, 20.0f); +b2RevoluteJoint_SetMaxMotorTorque(myJointId, 100.0f); +``` + +Joint motors have some interesting abilities. You can update the joint +speed every time step so you can make the joint move back-and-forth like +a sine-wave or according to whatever function you want. + +```c +// ... Game Loop Begin ... + +b2RevoluteJoint_SetMotorSpeed(myJointId, cosf(0.5f * time)); + +// ... Game Loop End ... +``` + +You can also use joint motors to track a desired joint angle. For example: + +```c +// ... Game Loop Begin ... + +float angleError = b2RevoluteJoint_GetAngle(myJointId) - angleTarget; +float gain = 0.1f; +b2RevoluteJoint_SetMotorSpeed(myJointId, -gain * angleError); + +// ... Game Loop End ... +``` + +Generally your gain parameter should not be too large. Otherwise your +joint may become unstable. + +### Prismatic Joint +A prismatic joint allows for relative translation of two bodies along a +local axis. A prismatic joint prevents relative rotation. Therefore, +a prismatic joint has a single degree of freedom. + +![Prismatic Joint](images/prismatic_joint.svg) + +The prismatic joint definition is similar to the revolute joint +description; just substitute translation for angle and force for torque. +Using this analogy provides an example prismatic joint definition with a +joint limit and a friction motor: + +```c +b2Vec2 worldPivot = {10.0f, -4.0f}; +b2Vec2 worldAxis = {1.0f, 0.0f}; +b2PrismaticJointDef jointDef; +b2RevoluteJointDef jointDef = b2DefaultRevoluteJointDef(); +jointDef.bodyIdA = myBodyIdA; +jointDef.bodyIdB = myBodyIdB; +jointDef.localAnchorA = b2Body_GetLocalPoint(myBodyIdA, worldPivot); +jointDef.localAnchorB = b2Body_GetLocalPoint(myBodyIdB, worldPivot); +jointDef.localAxisA = b2Body_GetLocalVector(myBodyIdA, worldAxis); +jointDef.lowerTranslation = -5.0f; +jointDef.upperTranslation = 2.5f; +jointDef.enableLimit = true; +jointDef.maxMotorForce = 1.0f; +jointDef.motorSpeed = 0.0f; +jointDef.enableMotor = true; +``` + +The revolute joint has an implicit axis coming out of the screen. The +prismatic joint needs an explicit axis parallel to the screen. This axis +is fixed in body A. + +The prismatic joint translation is zero when the anchor points overlap. I recommend +to have the prismatic anchor points close to the center of mass of the two bodies. +This will improve joint stiffness. + +Using a prismatic joint is similar to using a revolute joint. Here are +the relevant member functions: + +```c +float PrismaticJoint::GetJointTranslation() const; +float PrismaticJoint::GetJointSpeed() const; +float PrismaticJoint::GetMotorForce() const; +void PrismaticJoint::SetMotorSpeed(float speed); +void PrismaticJoint::SetMotorForce(float force); +``` + +### Mouse Joint +The mouse joint is used in the samples to manipulate bodies with the +mouse. It attempts to drive a point on a body towards the current +position of the cursor. There is no restriction on rotation. + +The mouse joint definition has a target point, maximum force, Hertz, +and damping ratio. The target point initially coincides with the body's +anchor point. The maximum force is used to prevent violent reactions +when multiple dynamic bodies interact. You can make this as large as you +like. The frequency and damping ratio are used to create a spring/damper +effect similar to the distance joint. + +### Wheel Joint +The wheel joint restricts a point on bodyB to a line on bodyA. The wheel +joint also provides a suspension spring and a motor. See the `Driving` sample +for details. + +![Wheel Joint](images/wheel_joint.svg) + +### Weld Joint +The weld joint attempts to constrain all relative motion between two +bodies. See the `Cantilever` sample to see how the weld joint +behaves. + +It is tempting to use the weld joint to define breakable structures. +However, the Box2D solver is approximate so the joints can be soft in some +cases regardless of the joint settings. So chains of bodies connected by weld +joints may flex. + +See the `ContactEvent` sample for an example of how to merge and split bodies +without using the weld joint. + +### Motor Joint +A motor joint lets you control the motion of a body by specifying target +position and rotation offsets. You can set the maximum motor force and +torque that will be applied to reach the target position and rotation. +If the body is blocked, it will stop and the contact forces will be +proportional the maximum motor force and torque. See `b2MotorJointDef` and +the `MotorJoint` sample for details. + +### Wheel Joint +The wheel joint is designed specifically for vehicles. It provides a translation +and rotation. The translation has a spring and damper to simulate the vehicle +suspension. The rotation allows the wheel to rotate. You can specify an rotational +motor to drive the wheel and to apply braking. See `b2WheelJointDef` and the `Drive` +sample for details. + +You may also use the wheel joint where you want free rotation and translation along +an axis. See the `ScissorLift` sample for details. + +## Spatial Queries {#spatial} +Spatial queries allow you to inspect the world geometrically. There are overlap queries, +ray-casts, and shape-casts. These allow you to do things like: +- find a treasure chest near the player +- shoot a laser beam and destroy all asteroids in the path +- throw a grenade that is represented as a circle moving along a parabolic path + +### Overlap Queries +Sometimes you want to determine all the shapes in a region. The world has a fast +log(N) method for this using the broad-phase data structure. Box2D provides these +overlap tests: +- axis-aligned bound box (AABB) overlap +- circle overlap +- capsule overlap +- polygon overlap + +#### Query Filtering +A basic understanding of query filtering is needed before considering the specific queries. +Shape versus shape filtering was discussed [here](#filtering). A similar setup is used +for queries. This lets your queries only consider certain categories of shapes, it also +lets your shapes ignore certain queries. + +Just like shapes, queries themselves can have a category. For example, you can have a `CAMERA` +or `PROJECTILE` category. + +```c +enum MyCategories +{ + STATIC = 0x00000001, + PLAYER = 0x00000002, + MONSTER = 0x00000004, + WINDOW = 0x00000008, + CAMERA = 0x00000010, + PROJECTILE = 0x00000020, +}; + +// Grenades collide with the static world, monsters, and windows but +// not players or other projectiles. +b2QueryFilter grenadeFilter; +grenadeFilter.categoryBits = PROJECTILE; +grenadeFilter.maskBits = STATIC | MONSTER | WINDOW; + +// The view collides with the static world, monsters, and players. +b2QueryFilter viewFilter; +viewFilter.categoryBits = CAMERA; +viewFilter.maskBits = STATIC | PLAYER | MONSTER; +``` + +If you want to query everything you can use `b2DefaultQueryFilter()`; + +#### AABB Overlap +You provide an AABB in world coordinates and an +implementation of `b2OverlapResultFcn()`. The world calls your function with each +shape whose AABB overlaps the query AABB. Return true to continue the +query, otherwise return false. For example, the following code finds all +the shapes that potentially intersect a specified AABB and wakes up +all of the associated bodies. + +```c +bool MyOverlapCallback(b2ShapeId shapeId, void* context) +{ + b2BodyId bodyId = b2Shape_GetBody(shapeId); + b2Body_SetAwake(bodyId, true); + + // Return true to continue the query. + return true; +} + +// Elsewhere ... +MyOverlapCallback callback; +b2AABB aabb; +aabb.lowerBound = (b2Vec2){-1.0f, -1.0f}; +aabb.upperBound = (b2Vec2){1.0f, 1.0f}; +b2QueryFilter filter = b2DefaultQueryFilter(); +b2World_OverlapAABB(myWorldId, aabb, filter, MyOverlapCallback, &myGame); +``` + +Do not make any assumptions about the order of the callback. The order shapes +are returned to your callback may seem arbitrary. + +#### Shape Overlap +The AABB overlap is very fast but not very accurate because it only considers +the shape bounding box. If you want an accurate overlap test, you can use a shape +overlap query. For example, here is how you can get all shapes that overlap +with a query circle. + +```c +b2Circle circle = {b2Vec2_zero, 0.2f}; +b2World_OverlapCircle(myWorldId, &circle, b2Transform_identity, grenadeFilter, MyOverlapCallback, &myGame); +``` + +### Ray-casts +You can use ray-casts to do line-of-sight checks, fire guns, etc. You +perform a ray-cast by implementing the `b2CastResultFcn()` callback +function and providing the +origin point and translation. The world calls your function with each shape +hit by the ray. Your callback is provided with the shape, the point of +intersection, the unit normal vector, and the fractional distance along +the ray. You cannot make any assumptions about the order of the points +sent to the callback. The callback may receive points that are further away +before receiving points that are closer. + +You control the continuation of the ray-cast by returning a fraction. +Returning a fraction of zero indicates the ray-cast should be +terminated. A fraction of one indicates the ray-cast should continue as +if no hit occurred. If you return the fraction from the argument list, +the ray will be clipped to the current intersection point. So you can +ray-cast any shape, ray-cast all shapes, or ray-cast the closest shape +by returning the appropriate fraction. + +You may also return of fraction of -1 to filter the shape. Then the +ray-cast will proceed as if the shape does not exist. + +Here is an example: + +```c +// This struct captures the closest hit shape +struct MyRayCastContext +{ + b2ShapeId shapeId; + b2Vec2 point; + b2Vec2 normal; + float fraction; +}; + +float MyCastCallback(b2ShapeId shapeId, b2Vec2 point, b2Vec2 normal, float fraction, void* context) +{ + MyRayCastContext* myContext = context; + myContext->shape = shape; + myContext->point = point; + myContext->normal = normal; + myContext->fraction = fraction; + return fraction; +} + +// Elsewhere ... +MyRayCastContext context = {0}; +b2Vec2 origin = {-1.0f, 0.0f}; +b2Vec2 end(3.0f, 1.0f); +b2Vec2 translation = b2Sub(end, origin); +b2World_CastRay(myWorldId, origin, translation, viewFilter, MyCastCallback, &context); +``` + +Ray-cast results may be delivered in an arbitrary order. This doesn't affect the result for closest point ray-casts (except in ties). When you are collecting multiple hits along the ray, you may want to sort them according to the hit fraction. See the `RayCastWorld` sample for details. + +### Shape-casts +Shape-casts are similar to ray-casts. You can view a ray-cast as tracing a point along a line. A shape-cast +allows you to trace a shape along a line. Since shapes can have rotation, you provide an origin transform instead of an origin point. + +```c +// This struct captures the closest hit shape +struct MyRayCastContext +{ + b2ShapeId shapeId; + b2Vec2 point; + b2Vec2 normal; + float fraction; +}; + +float MyCastCallback(b2ShapeId shapeId, b2Vec2 point, b2Vec2 normal, float fraction, void* context) +{ + MyRayCastContext* myContext = context; + myContext->shape = shape; + myContext->point = point; + myContext->normal = normal; + myContext->fraction = fraction; + return fraction; +} + +// Elsewhere ... +MyRayCastContext context = {0}; +b2Circle circle = {b2Vec2_zero, {0.05f}}; +b2Transform originTransform; +originTransform.p = (b2Vec2){-1.0f, 0.0f}; +originTransform.q = b2Rot_identity; +b2Vec2 translation = {10.0f, -5.0f}; +b2World_CastCircle(myWorldId, &circle, originTransform, translation, grenadeFilter, MyCastCallback, &context); +``` + +Otherwise, shape-casts are setup identically to ray-casts. You can expect shape-casts to generally be slower +than ray-casts. So only use a shape-cast if a ray-cast won't do. + +Just like ray-casts, shape-casts results may be sent to the callback in any order. If you need multiple sorted results, you will need to write some code to collect and sort the results. diff --git a/docs/testbed.md b/docs/testbed.md deleted file mode 100644 index 3de439f1..00000000 --- a/docs/testbed.md +++ /dev/null @@ -1,21 +0,0 @@ -# Testbed (NOT UPDATED for Box2D version 3.0) -Once you have conquered the HelloWorld example, you should start looking -at Box2D's testbed. The testbed is a testing framework and demo -environment. Here are some of the features: -- Camera with pan and zoom. -- Mouse picking of shapes attached to dynamic bodies. -- Extensible set of tests. -- GUI for selecting tests, parameter tuning, and debug drawing options. -- Pause and single step simulation. -- Text rendering. - -![Box2D Testbed](images/testbed.png) - -The testbed has many examples of Box2D usage in the test cases and the -framework itself. I encourage you to explore and tinker with the testbed -as you learn Box2D. - -Note: the testbed is written using [GLFW](https://www.glfw.org) and -[imgui](https://github.com/ocornut/imgui). The testbed is not part of the -Box2D library. The Box2D library is agnostic about rendering. As shown by -the HelloWorld example, you don't need a renderer to use Box2D. diff --git a/include/box2d/api.h b/include/box2d/base.h similarity index 68% rename from include/box2d/api.h rename to include/box2d/base.h index ff02b9e6..566954d8 100644 --- a/include/box2d/api.h +++ b/include/box2d/base.h @@ -3,6 +3,8 @@ #pragma once +#include + // Shared library macros #if defined(_MSC_VER) && defined(box2d_EXPORTS) // build the Windows DLL @@ -19,16 +21,26 @@ #endif // C++ macros +// clang-format off #ifdef __cplusplus #define B2_API extern "C" BOX2D_EXPORT #define B2_INLINE inline #define B2_LITERAL(T) T + #define B2_ZERO_INIT {} #else #define B2_API BOX2D_EXPORT #define B2_INLINE static inline /// Used for C literals like (b2Vec2){1.0f, 2.0f} where C++ requires b2Vec2{1.0f, 2.0f} #define B2_LITERAL(T) (T) + #define B2_ZERO_INIT {0} #endif +// clang-format on + +/** + * @defgroup base Base + * Base functionality + * @{ + */ /// Prototype for user allocation function /// @param size the allocation size in bytes @@ -54,17 +66,42 @@ B2_API int b2GetByteCount(void); B2_API void b2SetAssertFcn(b2AssertFcn* assertFcn); /// Version numbering scheme. -/// See http://en.wikipedia.org/wiki/Software_versioning +/// See https://semver.org/ typedef struct b2Version { - /// significant changes + /// Significant changes int major; - /// incremental changes + /// Incremental changes int minor; - /// bug fixes + /// Bug fixes int revision; } b2Version; +/// Get the current version of Box2D B2_API b2Version b2GetVersion(void); + +/**@}*/ + +//! @cond +// Timer for profiling. This has platform specific code and may not work on every platform. +typedef struct b2Timer +{ +#if defined(_WIN32) + int64_t start; +#elif defined(__linux__) || defined(__APPLE__) + unsigned long long start_sec; + unsigned long long start_usec; +#else + int32_t dummy; +#endif +} b2Timer; + +B2_API b2Timer b2CreateTimer(void); +B2_API int64_t b2GetTicks(b2Timer* timer); +B2_API float b2GetMilliseconds(const b2Timer* timer); +B2_API float b2GetMillisecondsAndReset(b2Timer* timer); +B2_API void b2SleepMilliseconds(int milliseconds); +B2_API void b2Yield(); +//! @endcond diff --git a/include/box2d/box2d.h b/include/box2d/box2d.h index 61f1683b..4ce220a9 100644 --- a/include/box2d/box2d.h +++ b/include/box2d/box2d.h @@ -3,22 +3,13 @@ #pragma once -#include "api.h" -#include "callbacks.h" -#include "event_types.h" -#include "geometry.h" +#include "base.h" +#include "collision.h" #include "id.h" -#include "joint_types.h" #include "types.h" #include -typedef struct b2Capsule b2Capsule; -typedef struct b2Circle b2Circle; -typedef struct b2DebugDraw b2DebugDraw; -typedef struct b2Polygon b2Polygon; -typedef struct b2Segment b2Segment; - /** * @defgroup world World * These functions allow you to create a simulation world. @@ -124,6 +115,9 @@ B2_API void b2World_SetRestitutionThreshold(b2WorldId worldId, float value); /// @see b2WorldDef::hitEventThreshold B2_API void b2World_SetHitEventThreshold(b2WorldId worldId, float value); +/// Register the custom filter callback. This is optional. +B2_API void b2World_SetCustomFilterCallback(b2WorldId worldId, b2CustomFilterFcn* fcn, void* context); + /// Register the pre-solve callback. This is optional. B2_API void b2World_SetPreSolveCallback(b2WorldId worldId, b2PreSolveFcn* fcn, void* context); @@ -512,7 +506,7 @@ B2_API void b2Shape_EnableContactEvents(b2ShapeId shapeId, bool flag); B2_API bool b2Shape_AreContactEventsEnabled(b2ShapeId shapeId); /// Enable pre-solve contact events for this shape. Only applies to dynamic bodies. These are expensive -/// and must be carefully handled due to multi-threading. Ignored for sensors. +/// and must be carefully handled due to multithreading. Ignored for sensors. /// @see b2PreSolveFcn B2_API void b2Shape_EnablePreSolveEvents(b2ShapeId shapeId, bool flag); diff --git a/include/box2d/callbacks.h b/include/box2d/callbacks.h deleted file mode 100644 index 36823d54..00000000 --- a/include/box2d/callbacks.h +++ /dev/null @@ -1,86 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Erin Catto -// SPDX-License-Identifier: MIT - -#pragma once - -#include "id.h" -#include "math_types.h" - -#include - -typedef struct b2Manifold b2Manifold; - -/// Task interface -/// This is prototype for a Box2D task. Your task system is expected to invoke the Box2D task with these arguments. -/// The task spans a range of the parallel-for: [startIndex, endIndex) -/// The worker index must correctly identify each worker in the user thread pool, expected in [0, workerCount). -/// A worker must only exist on only one thread at a time and is analogous to the thread index. -/// The task context is the context pointer sent from Box2D when it is enqueued. -/// The startIndex and endIndex are expected in the range [0, itemCount) where itemCount is the argument to b2EnqueueTaskCallback -/// below. Box2D expects startIndex < endIndex and will execute a loop like this: -/// -/// @code{.c} -/// for (int i = startIndex; i < endIndex; ++i) -/// { -/// DoWork(); -/// } -/// @endcode -/// @ingroup world -typedef void b2TaskCallback(int32_t startIndex, int32_t endIndex, uint32_t workerIndex, void* taskContext); - -/// These functions can be provided to Box2D to invoke a task system. These are designed to work well with enkiTS. -/// Returns a pointer to the user's task object. May be nullptr. A nullptr indicates to Box2D that the work was executed -/// serially within the callback and there is no need to call b2FinishTaskCallback. -/// The itemCount is the number of Box2D work items that are to be partitioned among workers by the user's task system. -/// This is essentially a parallel-for. The minRange parameter is a suggestion of the minimum number of items to assign -/// per worker to reduce overhead. For example, suppose the task is small and that itemCount is 16. A minRange of 8 suggests -/// that your task system should split the work items among just two workers, even if you have more available. -/// In general the range [startIndex, endIndex) send to b2TaskCallback should obey: -/// endIndex - startIndex >= minRange -/// The exception of course is when itemCount < minRange. -/// @ingroup world -typedef void* b2EnqueueTaskCallback(b2TaskCallback* task, int32_t itemCount, int32_t minRange, void* taskContext, - void* userContext); - -/// Finishes a user task object that wraps a Box2D task. -/// @ingroup world -typedef void b2FinishTaskCallback(void* userTask, void* userContext); - -/// Prototype for a pre-solve callback. -/// This is called after a contact is updated. This allows you to inspect a -/// contact before it goes to the solver. If you are careful, you can modify the -/// contact manifold (e.g. modify the normal). -/// Notes: -/// - this function must be thread-safe -/// - this is only called if the shape has enabled presolve events -/// - this is called only for awake dynamic bodies -/// - this is not called for sensors -/// - the supplied manifold has impulse values from the previous step -/// Return false if you want to disable the contact this step -/// @warning Do not attempt to modify the world inside this callback -/// @ingroup world -typedef bool b2PreSolveFcn(b2ShapeId shapeIdA, b2ShapeId shapeIdB, b2Manifold* manifold, void* context); - -/// Prototype callback for overlap queries. -/// Called for each shape found in the query. -/// @see b2World_QueryAABB -/// @return false to terminate the query. -/// @ingroup world -typedef bool b2OverlapResultFcn(b2ShapeId shapeId, void* context); - -/// Prototype callback for ray casts. -/// Called for each shape found in the query. You control how the ray cast -/// proceeds by returning a float: -/// return -1: ignore this shape and continue -/// return 0: terminate the ray cast -/// return fraction: clip the ray to this point -/// return 1: don't clip the ray and continue -/// @param shapeId the shape hit by the ray -/// @param point the point of initial intersection -/// @param normal the normal vector at the point of intersection -/// @param fraction the fraction along the ray at the point of intersection -/// @param context the user context -/// @return -1 to filter, 0 to terminate, fraction to clip the ray for closest hit, 1 to continue -/// @see b2World_CastRay -/// @ingroup world -typedef float b2CastResultFcn(b2ShapeId shapeId, b2Vec2 point, b2Vec2 normal, float fraction, void* context); diff --git a/include/box2d/collision.h b/include/box2d/collision.h index 7e8f901a..dbee5f6a 100644 --- a/include/box2d/collision.h +++ b/include/box2d/collision.h @@ -3,8 +3,10 @@ #pragma once -#include "api.h" -#include "types.h" +#include "base.h" +#include "math_functions.h" + +#include typedef struct b2Circle b2Circle; typedef struct b2Capsule b2Capsule; @@ -13,6 +15,432 @@ typedef struct b2Polygon b2Polygon; typedef struct b2Segment b2Segment; typedef struct b2SmoothSegment b2SmoothSegment; +typedef struct b2Hull b2Hull; + +/** + * @defgroup geometry Geometry + * @brief Geometry types and algorithms + * + * Definitions of circles, capsules, segments, and polygons. Various algorithms to compute hulls, mass properties, and so on. + * @{ + */ + +/// The maximum number of vertices on a convex polygon. Changing this affects performance even if you +/// don't use more vertices. +#define b2_maxPolygonVertices 8 + +/// Low level ray-cast input data +typedef struct b2RayCastInput +{ + /// Start point of the ray cast + b2Vec2 origin; + + /// Translation of the ray cast + b2Vec2 translation; + + /// The maximum fraction of the translation to consider, typically 1 + float maxFraction; +} b2RayCastInput; + +/// Low level shape cast input in generic form. This allows casting an arbitrary point +/// cloud wrap with a radius. For example, a circle is a single point with a non-zero radius. +/// A capsule is two points with a non-zero radius. A box is four points with a zero radius. +typedef struct b2ShapeCastInput +{ + /// A point cloud to cast + b2Vec2 points[b2_maxPolygonVertices]; + + /// The number of points + int32_t count; + + /// The radius around the point cloud + float radius; + + /// The translation of the shape cast + b2Vec2 translation; + + /// The maximum fraction of the translation to consider, typically 1 + float maxFraction; +} b2ShapeCastInput; + +/// Low level ray-cast or shape-cast output data +typedef struct b2CastOutput +{ + /// The surface normal at the hit point + b2Vec2 normal; + + /// The surface hit point + b2Vec2 point; + + /// The fraction of the input translation at collision + float fraction; + + /// The number of iterations used + int32_t iterations; + + /// Did the cast hit? + bool hit; +} b2CastOutput; + +/// This holds the mass data computed for a shape. +typedef struct b2MassData +{ + /// The mass of the shape, usually in kilograms. + float mass; + + /// The position of the shape's centroid relative to the shape's origin. + b2Vec2 center; + + /// The rotational inertia of the shape about the local origin. + float I; +} b2MassData; + +/// A solid circle +typedef struct b2Circle +{ + /// The local center + b2Vec2 center; + + /// The radius + float radius; +} b2Circle; + +/// A solid capsule can be viewed as two semicircles connected +/// by a rectangle. +typedef struct b2Capsule +{ + /// Local center of the first semicircle + b2Vec2 center1; + + /// Local center of the second semicircle + b2Vec2 center2; + + /// The radius of the semicircles + float radius; +} b2Capsule; + +/// A solid convex polygon. It is assumed that the interior of the polygon is to +/// the left of each edge. +/// Polygons have a maximum number of vertices equal to b2_maxPolygonVertices. +/// In most cases you should not need many vertices for a convex polygon. +/// @warning DO NOT fill this out manually, instead use a helper function like +/// b2MakePolygon or b2MakeBox. +typedef struct b2Polygon +{ + /// The polygon vertices + b2Vec2 vertices[b2_maxPolygonVertices]; + + /// The outward normal vectors of the polygon sides + b2Vec2 normals[b2_maxPolygonVertices]; + + /// The centroid of the polygon + b2Vec2 centroid; + + /// The external radius for rounded polygons + float radius; + + /// The number of polygon vertices + int32_t count; +} b2Polygon; + +/// A line segment with two-sided collision. +typedef struct b2Segment +{ + /// The first point + b2Vec2 point1; + + /// The second point + b2Vec2 point2; +} b2Segment; + +/// A smooth line segment with one-sided collision. Only collides on the right side. +/// Several of these are generated for a chain shape. +/// ghost1 -> point1 -> point2 -> ghost2 +typedef struct b2SmoothSegment +{ + /// The tail ghost vertex + b2Vec2 ghost1; + + /// The line segment + b2Segment segment; + + /// The head ghost vertex + b2Vec2 ghost2; + + /// The owning chain shape index (internal usage only) + int32_t chainId; +} b2SmoothSegment; + +/// Validate ray cast input data (NaN, etc) +B2_API bool b2IsValidRay(const b2RayCastInput* input); + +/// Make a convex polygon from a convex hull. This will assert if the hull is not valid. +B2_API b2Polygon b2MakePolygon(const b2Hull* hull, float radius); + +/// Make an offset convex polygon from a convex hull. This will assert if the hull is not valid. +B2_API b2Polygon b2MakeOffsetPolygon(const b2Hull* hull, float radius, b2Transform transform); + +/// Make a square polygon, bypassing the need for a convex hull. +B2_API b2Polygon b2MakeSquare(float h); + +/// Make a box (rectangle) polygon, bypassing the need for a convex hull. +B2_API b2Polygon b2MakeBox(float hx, float hy); + +/// Make a rounded box, bypassing the need for a convex hull. +B2_API b2Polygon b2MakeRoundedBox(float hx, float hy, float radius); + +/// Make an offset box, bypassing the need for a convex hull. +B2_API b2Polygon b2MakeOffsetBox(float hx, float hy, b2Vec2 center, float angle); + +/// Transform a polygon. This is useful for transferring a shape from one body to another. +B2_API b2Polygon b2TransformPolygon(b2Transform transform, const b2Polygon* polygon); + +/// Compute mass properties of a circle +B2_API b2MassData b2ComputeCircleMass(const b2Circle* shape, float density); + +/// Compute mass properties of a capsule +B2_API b2MassData b2ComputeCapsuleMass(const b2Capsule* shape, float density); + +/// Compute mass properties of a polygon +B2_API b2MassData b2ComputePolygonMass(const b2Polygon* shape, float density); + +/// Compute the bounding box of a transformed circle +B2_API b2AABB b2ComputeCircleAABB(const b2Circle* shape, b2Transform transform); + +/// Compute the bounding box of a transformed capsule +B2_API b2AABB b2ComputeCapsuleAABB(const b2Capsule* shape, b2Transform transform); + +/// Compute the bounding box of a transformed polygon +B2_API b2AABB b2ComputePolygonAABB(const b2Polygon* shape, b2Transform transform); + +/// Compute the bounding box of a transformed line segment +B2_API b2AABB b2ComputeSegmentAABB(const b2Segment* shape, b2Transform transform); + +/// Test a point for overlap with a circle in local space +B2_API bool b2PointInCircle(b2Vec2 point, const b2Circle* shape); + +/// Test a point for overlap with a capsule in local space +B2_API bool b2PointInCapsule(b2Vec2 point, const b2Capsule* shape); + +/// Test a point for overlap with a convex polygon in local space +B2_API bool b2PointInPolygon(b2Vec2 point, const b2Polygon* shape); + +/// Ray cast versus circle in shape local space. Initial overlap is treated as a miss. +B2_API b2CastOutput b2RayCastCircle(const b2RayCastInput* input, const b2Circle* shape); + +/// Ray cast versus capsule in shape local space. Initial overlap is treated as a miss. +B2_API b2CastOutput b2RayCastCapsule(const b2RayCastInput* input, const b2Capsule* shape); + +/// Ray cast versus segment in shape local space. Optionally treat the segment as one-sided with hits from +/// the left side being treated as a miss. +B2_API b2CastOutput b2RayCastSegment(const b2RayCastInput* input, const b2Segment* shape, bool oneSided); + +/// Ray cast versus polygon in shape local space. Initial overlap is treated as a miss. +B2_API b2CastOutput b2RayCastPolygon(const b2RayCastInput* input, const b2Polygon* shape); + +/// Shape cast versus a circle. Initial overlap is treated as a miss. +B2_API b2CastOutput b2ShapeCastCircle(const b2ShapeCastInput* input, const b2Circle* shape); + +/// Shape cast versus a capsule. Initial overlap is treated as a miss. +B2_API b2CastOutput b2ShapeCastCapsule(const b2ShapeCastInput* input, const b2Capsule* shape); + +/// Shape cast versus a line segment. Initial overlap is treated as a miss. +B2_API b2CastOutput b2ShapeCastSegment(const b2ShapeCastInput* input, const b2Segment* shape); + +/// Shape cast versus a convex polygon. Initial overlap is treated as a miss. +B2_API b2CastOutput b2ShapeCastPolygon(const b2ShapeCastInput* input, const b2Polygon* shape); + +/// A convex hull. Used to create convex polygons. +typedef struct b2Hull +{ + /// The final points of the hull + b2Vec2 points[b2_maxPolygonVertices]; + + /// The number of points + int32_t count; +} b2Hull; + +/// Compute the convex hull of a set of points. Returns an empty hull if it fails. +/// Some failure cases: +/// - all points very close together +/// - all points on a line +/// - less than 3 points +/// - more than b2_maxPolygonVertices points +/// This welds close points and removes collinear points. +B2_API b2Hull b2ComputeHull(const b2Vec2* points, int32_t count); + +/// This determines if a hull is valid. Checks for: +/// - convexity +/// - collinear points +/// This is expensive and should not be called at runtime. +B2_API bool b2ValidateHull(const b2Hull* hull); + +/**@}*/ + +/** + * @defgroup distance Distance + * Functions for computing the distance between shapes. + * + * These are advanced functions you can use to perform distance calculations. There + * are functions for computing the closest points between shapes, doing linear shape casts, + * and doing rotational shape casts. The latter is called time of impact (TOI). + * @{ + */ + +/// Result of computing the distance between two line segments +typedef struct b2SegmentDistanceResult +{ + /// The closest point on the first segment + b2Vec2 closest1; + + /// The closest point on the second segment + b2Vec2 closest2; + + /// The barycentric coordinate on the first segment + float fraction1; + + /// The barycentric coordinate on the first segment + float fraction2; + + /// The squared distance between the closest points + float distanceSquared; +} b2SegmentDistanceResult; + +/// Compute the distance between two line segments, clamping at the end points if needed. +B2_API b2SegmentDistanceResult b2SegmentDistance(b2Vec2 p1, b2Vec2 q1, b2Vec2 p2, b2Vec2 q2); + +/// A distance proxy is used by the GJK algorithm. It encapsulates any shape. +typedef struct b2DistanceProxy +{ + /// The point cloud + b2Vec2 points[b2_maxPolygonVertices]; + + /// The number of points + int32_t count; + + /// The external radius of the point cloud + float radius; +} b2DistanceProxy; + +/// Used to warm start b2Distance. Set count to zero on first call or +/// use zero initialization. +typedef struct b2DistanceCache +{ + /// Length or area + float metric; + + /// The number of stored simplex points + uint16_t count; + + /// The cached simplex indices on shape A + uint8_t indexA[3]; + + /// The cached simplex indices on shape B + uint8_t indexB[3]; +} b2DistanceCache; + +static const b2DistanceCache b2_emptyDistanceCache = B2_ZERO_INIT; + +/// Input for b2Distance +typedef struct b2DistanceInput +{ + /// The proxy for shape A + b2DistanceProxy proxyA; + + /// The proxy for shape B + b2DistanceProxy proxyB; + + /// The world transform for shape A + b2Transform transformA; + + /// The world transform for shape B + b2Transform transformB; + + /// Should the proxy radius be considered? + bool useRadii; +} b2DistanceInput; + +/// Output for b2Distance +typedef struct b2DistanceOutput +{ + b2Vec2 pointA; ///< Closest point on shapeA + b2Vec2 pointB; ///< Closest point on shapeB + float distance; ///< The final distance, zero if overlapped + int32_t iterations; ///< Number of GJK iterations used +} b2DistanceOutput; + +/// Compute the closest points between two shapes. Supports any combination of: +/// b2Circle, b2Polygon, b2EdgeShape. The simplex cache is input/output. +/// On the first call set b2SimplexCache.count to zero. +B2_API b2DistanceOutput b2ShapeDistance(b2DistanceCache* cache, const b2DistanceInput* input); + +/// Input parameters for b2ShapeCast +typedef struct b2ShapeCastPairInput +{ + b2DistanceProxy proxyA; ///< The proxy for shape A + b2DistanceProxy proxyB; ///< The proxy for shape B + b2Transform transformA; ///< The world transform for shape A + b2Transform transformB; ///< The world transform for shape B + b2Vec2 translationB; ///< The translation of shape B + float maxFraction; ///< The fraction of the translation to consider, typically 1 +} b2ShapeCastPairInput; + +/// Perform a linear shape cast of shape B moving and shape A fixed. Determines the hit point, normal, and translation fraction. +B2_API b2CastOutput b2ShapeCast(const b2ShapeCastPairInput* input); + +/// Make a proxy for use in GJK and related functions. +B2_API b2DistanceProxy b2MakeProxy(const b2Vec2* vertices, int32_t count, float radius); + +/// This describes the motion of a body/shape for TOI computation. Shapes are defined with respect to the body origin, +/// which may not coincide with the center of mass. However, to support dynamics we must interpolate the center of mass +/// position. +typedef struct b2Sweep +{ + b2Vec2 localCenter; ///< Local center of mass position + b2Vec2 c1; ///< Starting center of mass world position + b2Vec2 c2; ///< Ending center of mass world position + b2Rot q1; ///< Starting world rotation + b2Rot q2; ///< Ending world rotation +} b2Sweep; + +/// Evaluate the transform sweep at a specific time. +B2_API b2Transform b2GetSweepTransform(const b2Sweep* sweep, float time); + +/// Input parameters for b2TimeOfImpact +typedef struct b2TOIInput +{ + b2DistanceProxy proxyA; ///< The proxy for shape A + b2DistanceProxy proxyB; ///< The proxy for shape B + b2Sweep sweepA; ///< The movement of shape A + b2Sweep sweepB; ///< The movement of shape B + float tMax; ///< Defines the sweep interval [0, tMax] +} b2TOIInput; + +/// Describes the TOI output +typedef enum b2TOIState +{ + b2_toiStateUnknown, + b2_toiStateFailed, + b2_toiStateOverlapped, + b2_toiStateHit, + b2_toiStateSeparated +} b2TOIState; + +/// Output parameters for b2TimeOfImpact. +typedef struct b2TOIOutput +{ + b2TOIState state; ///< The type of result + float t; ///< The time of the collision +} b2TOIOutput; + +/// Compute the upper bound on time before two shapes penetrate. Time is represented as +/// a fraction between [0,tMax]. This uses a swept separating axis and may miss some intermediate, +/// non-tunneling collisions. If you change the time interval, you should call this function +/// again. +B2_API b2TOIOutput b2TimeOfImpact(const b2TOIInput* input); + +/**@}*/ + /** * @defgroup collision Collision * @brief Functions for colliding pairs of shapes @@ -28,9 +456,12 @@ typedef struct b2ManifoldPoint /// @note Should only be used for debugging. b2Vec2 point; - /// Location of contact point relative to body origin in world space. + /// Location of the contact point relative to bodyA's origin in world space /// @note When used internally to the Box2D solver, these are relative to the center of mass. - b2Vec2 anchorA, anchorB; + b2Vec2 anchorA; + + /// Location of the contact point relative to bodyB's origin in world space + b2Vec2 anchorB; /// The separation of the contact point, negative if penetrating float separation; @@ -114,3 +545,221 @@ B2_API b2Manifold b2CollideSmoothSegmentAndPolygon(const b2SmoothSegment* smooth const b2Polygon* polygonB, b2Transform xfB, b2DistanceCache* cache); /**@}*/ + +/** + * @defgroup tree Dynamic Tree + * The dynamic tree is a binary AABB tree to organize and query large numbers of geometric objects + * + * Box2D uses the dynamic tree internally to sort collision shapes into a binary bounding volume hierarchy. + * This data structure may have uses in games for organizing other geometry data and may be used independently + * of Box2D rigid body simulation. + * + * A dynamic AABB tree broad-phase, inspired by Nathanael Presson's btDbvt. + * A dynamic tree arranges data in a binary tree to accelerate + * queries such as AABB queries and ray casts. Leaf nodes are proxies + * with an AABB. These are used to hold a user collision object, such as a reference to a b2Shape. + * Nodes are pooled and relocatable, so I use node indices rather than pointers. + * The dynamic tree is made available for advanced users that would like to use it to organize + * spatial game data besides rigid bodies. + * + * @note This is an advanced feature and normally not used by applications directly. + * @{ + */ + +/// The default category bit for a tree proxy. Used for collision filtering. +#define b2_defaultCategoryBits (0x00000001) + +/// Convenience mask bits to use when you don't need collision filtering and just want +/// all results. +#define b2_defaultMaskBits (0xFFFFFFFF) + +/// A node in the dynamic tree. This is private data placed here for performance reasons. +/// 16 + 16 + 8 + pad(8) +typedef struct b2TreeNode +{ + /// The node bounding box + b2AABB aabb; // 16 + + /// Category bits for collision filtering + uint32_t categoryBits; // 4 + + union + { + /// The node parent index + int32_t parent; + + /// The node freelist next index + int32_t next; + }; // 4 + + /// Child 1 index + int32_t child1; // 4 + + /// Child 2 index + int32_t child2; // 4 + + /// User data + // todo could be union with child index + int32_t userData; // 4 + + /// Leaf = 0, free node = -1 + int16_t height; // 2 + + /// Has the AABB been enlarged? + bool enlarged; // 1 + + /// Padding for clarity + char pad[9]; +} b2TreeNode; + +/// The dynamic tree structure. This should be considered private data. +/// It is placed here for performance reasons. +typedef struct b2DynamicTree +{ + /// The tree nodes + b2TreeNode* nodes; + + /// The root index + int32_t root; + + /// The number of nodes + int32_t nodeCount; + + /// The allocated node space + int32_t nodeCapacity; + + /// Node free list + int32_t freeList; + + /// Number of proxies created + int32_t proxyCount; + + /// Leaf indices for rebuild + int32_t* leafIndices; + + /// Leaf bounding boxes for rebuild + b2AABB* leafBoxes; + + /// Leaf bounding box centers for rebuild + b2Vec2* leafCenters; + + /// Bins for sorting during rebuild + int32_t* binIndices; + + /// Allocated space for rebuilding + int32_t rebuildCapacity; +} b2DynamicTree; + +/// Constructing the tree initializes the node pool. +B2_API b2DynamicTree b2DynamicTree_Create(void); + +/// Destroy the tree, freeing the node pool. +B2_API void b2DynamicTree_Destroy(b2DynamicTree* tree); + +/// Create a proxy. Provide an AABB and a userData value. +B2_API int32_t b2DynamicTree_CreateProxy(b2DynamicTree* tree, b2AABB aabb, uint32_t categoryBits, int32_t userData); + +/// Destroy a proxy. This asserts if the id is invalid. +B2_API void b2DynamicTree_DestroyProxy(b2DynamicTree* tree, int32_t proxyId); + +/// Move a proxy to a new AABB by removing and reinserting into the tree. +B2_API void b2DynamicTree_MoveProxy(b2DynamicTree* tree, int32_t proxyId, b2AABB aabb); + +/// Enlarge a proxy and enlarge ancestors as necessary. +B2_API void b2DynamicTree_EnlargeProxy(b2DynamicTree* tree, int32_t proxyId, b2AABB aabb); + +/// This function receives proxies found in the AABB query. +/// @return true if the query should continue +typedef bool b2TreeQueryCallbackFcn(int32_t proxyId, int32_t userData, void* context); + +/// Query an AABB for overlapping proxies. The callback class +/// is called for each proxy that overlaps the supplied AABB. +B2_API void b2DynamicTree_Query(const b2DynamicTree* tree, b2AABB aabb, uint32_t maskBits, + b2TreeQueryCallbackFcn* callback, void* context); + +/// This function receives clipped raycast input for a proxy. The function +/// returns the new ray fraction. +/// - return a value of 0 to terminate the ray cast +/// - return a value less than input->maxFraction to clip the ray +/// - return a value of input->maxFraction to continue the ray cast without clipping +typedef float b2TreeRayCastCallbackFcn(const b2RayCastInput* input, int32_t proxyId, int32_t userData, void* context); + +/// Ray-cast against the proxies in the tree. This relies on the callback +/// to perform a exact ray-cast in the case were the proxy contains a shape. +/// The callback also performs the any collision filtering. This has performance +/// roughly equal to k * log(n), where k is the number of collisions and n is the +/// number of proxies in the tree. +/// Bit-wise filtering using mask bits can greatly improve performance in some scenarios. +/// @param tree the dynamic tree to ray cast +/// @param input the ray-cast input data. The ray extends from p1 to p1 + maxFraction * (p2 - p1) +/// @param maskBits filter bits: `bool accept = (maskBits & node->categoryBits) != 0;` +/// @param callback a callback class that is called for each proxy that is hit by the ray +/// @param context user context that is passed to the callback +B2_API void b2DynamicTree_RayCast(const b2DynamicTree* tree, const b2RayCastInput* input, uint32_t maskBits, + b2TreeRayCastCallbackFcn* callback, void* context); + +/// This function receives clipped ray-cast input for a proxy. The function +/// returns the new ray fraction. +/// - return a value of 0 to terminate the ray-cast +/// - return a value less than input->maxFraction to clip the ray +/// - return a value of input->maxFraction to continue the ray cast without clipping +typedef float b2TreeShapeCastCallbackFcn(const b2ShapeCastInput* input, int32_t proxyId, int32_t userData, void* context); + +/// Ray-cast against the proxies in the tree. This relies on the callback +/// to perform a exact ray-cast in the case were the proxy contains a shape. +/// The callback also performs the any collision filtering. This has performance +/// roughly equal to k * log(n), where k is the number of collisions and n is the +/// number of proxies in the tree. +/// @param tree the dynamic tree to ray cast +/// @param input the ray-cast input data. The ray extends from p1 to p1 + maxFraction * (p2 - p1). +/// @param maskBits filter bits: `bool accept = (maskBits & node->categoryBits) != 0;` +/// @param callback a callback class that is called for each proxy that is hit by the shape +/// @param context user context that is passed to the callback +B2_API void b2DynamicTree_ShapeCast(const b2DynamicTree* tree, const b2ShapeCastInput* input, uint32_t maskBits, + b2TreeShapeCastCallbackFcn* callback, void* context); + +/// Validate this tree. For testing. +B2_API void b2DynamicTree_Validate(const b2DynamicTree* tree); + +/// Compute the height of the binary tree in O(N) time. Should not be +/// called often. +B2_API int b2DynamicTree_GetHeight(const b2DynamicTree* tree); + +/// Get the maximum balance of the tree. The balance is the difference in height of the two children of a node. +B2_API int b2DynamicTree_GetMaxBalance(const b2DynamicTree* tree); + +/// Get the ratio of the sum of the node areas to the root area. +B2_API float b2DynamicTree_GetAreaRatio(const b2DynamicTree* tree); + +/// Build an optimal tree. Very expensive. For testing. +B2_API void b2DynamicTree_RebuildBottomUp(b2DynamicTree* tree); + +/// Get the number of proxies created +B2_API int b2DynamicTree_GetProxyCount(const b2DynamicTree* tree); + +/// Rebuild the tree while retaining subtrees that haven't changed. Returns the number of boxes sorted. +B2_API int b2DynamicTree_Rebuild(b2DynamicTree* tree, bool fullBuild); + +/// Shift the world origin. Useful for large worlds. +/// The shift formula is: position -= newOrigin +/// @param tree the tree to shift +/// @param newOrigin the new origin with respect to the old origin +B2_API void b2DynamicTree_ShiftOrigin(b2DynamicTree* tree, b2Vec2 newOrigin); + +/// Get the number of bytes used by this tree +B2_API int b2DynamicTree_GetByteCount(const b2DynamicTree* tree); + +/// Get proxy user data +/// @return the proxy user data or 0 if the id is invalid +B2_INLINE int32_t b2DynamicTree_GetUserData(const b2DynamicTree* tree, int32_t proxyId) +{ + return tree->nodes[proxyId].userData; +} + +/// Get the AABB of a proxy +B2_INLINE b2AABB b2DynamicTree_GetAABB(const b2DynamicTree* tree, int32_t proxyId) +{ + return tree->nodes[proxyId].aabb; +} + +/**@}*/ diff --git a/include/box2d/color.h b/include/box2d/color.h deleted file mode 100644 index b0cf1cfc..00000000 --- a/include/box2d/color.h +++ /dev/null @@ -1,255 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Erin Catto -// SPDX-License-Identifier: MIT - -#pragma once - -/// These colors are used for debug draw. -typedef enum b2HexColor -{ - b2_colorAliceBlue = 0xf0f8ff, - b2_colorAntiqueWhite = 0xfaebd7, - b2_colorAqua = 0x00ffff, - b2_colorAquamarine = 0x7fffd4, - b2_colorAzure = 0xf0ffff, - b2_colorBeige = 0xf5f5dc, - b2_colorBisque = 0xffe4c4, - b2_colorBlack = 0x000000, - b2_colorBlanchedAlmond = 0xffebcd, - b2_colorBlue = 0x0000ff, - b2_colorBlueViolet = 0x8a2be2, - b2_colorBrown = 0xa52a2a, - b2_colorBurlywood = 0xdeb887, - b2_colorCadetBlue = 0x5f9ea0, - b2_colorChartreuse = 0x7fff00, - b2_colorChocolate = 0xd2691e, - b2_colorCoral = 0xff7f50, - b2_colorCornflowerBlue = 0x6495ed, - b2_colorCornsilk = 0xfff8dc, - b2_colorCrimson = 0xdc143c, - b2_colorCyan = 0x00ffff, - b2_colorDarkBlue = 0x00008b, - b2_colorDarkCyan = 0x008b8b, - b2_colorDarkGoldenrod = 0xb8860b, - b2_colorDarkGray = 0xa9a9a9, - b2_colorDarkGreen = 0x006400, - b2_colorDarkKhaki = 0xbdb76b, - b2_colorDarkMagenta = 0x8b008b, - b2_colorDarkOliveGreen = 0x556b2f, - b2_colorDarkOrange = 0xff8c00, - b2_colorDarkOrchid = 0x9932cc, - b2_colorDarkRed = 0x8b0000, - b2_colorDarkSalmon = 0xe9967a, - b2_colorDarkSeaGreen = 0x8fbc8f, - b2_colorDarkSlateBlue = 0x483d8b, - b2_colorDarkSlateGray = 0x2f4f4f, - b2_colorDarkTurquoise = 0x00ced1, - b2_colorDarkViolet = 0x9400d3, - b2_colorDeepPink = 0xff1493, - b2_colorDeepSkyBlue = 0x00bfff, - b2_colorDimGray = 0x696969, - b2_colorDodgerBlue = 0x1e90ff, - b2_colorFirebrick = 0xb22222, - b2_colorFloralWhite = 0xfffaf0, - b2_colorForestGreen = 0x228b22, - b2_colorFuchsia = 0xff00ff, - b2_colorGainsboro = 0xdcdcdc, - b2_colorGhostWhite = 0xf8f8ff, - b2_colorGold = 0xffd700, - b2_colorGoldenrod = 0xdaa520, - b2_colorGray = 0xbebebe, - b2_colorGray0 = 0x000000, - b2_colorGray1 = 0x030303, - b2_colorGray10 = 0x1a1a1a, - b2_colorGray100 = 0xffffff, - b2_colorGray11 = 0x1c1c1c, - b2_colorGray12 = 0x1f1f1f, - b2_colorGray13 = 0x212121, - b2_colorGray14 = 0x242424, - b2_colorGray15 = 0x262626, - b2_colorGray16 = 0x292929, - b2_colorGray17 = 0x2b2b2b, - b2_colorGray18 = 0x2e2e2e, - b2_colorGray19 = 0x303030, - b2_colorGray2 = 0x050505, - b2_colorGray20 = 0x333333, - b2_colorGray21 = 0x363636, - b2_colorGray22 = 0x383838, - b2_colorGray23 = 0x3b3b3b, - b2_colorGray24 = 0x3d3d3d, - b2_colorGray25 = 0x404040, - b2_colorGray26 = 0x424242, - b2_colorGray27 = 0x454545, - b2_colorGray28 = 0x474747, - b2_colorGray29 = 0x4a4a4a, - b2_colorGray3 = 0x080808, - b2_colorGray30 = 0x4d4d4d, - b2_colorGray31 = 0x4f4f4f, - b2_colorGray32 = 0x525252, - b2_colorGray33 = 0x545454, - b2_colorGray34 = 0x575757, - b2_colorGray35 = 0x595959, - b2_colorGray36 = 0x5c5c5c, - b2_colorGray37 = 0x5e5e5e, - b2_colorGray38 = 0x616161, - b2_colorGray39 = 0x636363, - b2_colorGray4 = 0x0a0a0a, - b2_colorGray40 = 0x666666, - b2_colorGray41 = 0x696969, - b2_colorGray42 = 0x6b6b6b, - b2_colorGray43 = 0x6e6e6e, - b2_colorGray44 = 0x707070, - b2_colorGray45 = 0x737373, - b2_colorGray46 = 0x757575, - b2_colorGray47 = 0x787878, - b2_colorGray48 = 0x7a7a7a, - b2_colorGray49 = 0x7d7d7d, - b2_colorGray5 = 0x0d0d0d, - b2_colorGray50 = 0x7f7f7f, - b2_colorGray51 = 0x828282, - b2_colorGray52 = 0x858585, - b2_colorGray53 = 0x878787, - b2_colorGray54 = 0x8a8a8a, - b2_colorGray55 = 0x8c8c8c, - b2_colorGray56 = 0x8f8f8f, - b2_colorGray57 = 0x919191, - b2_colorGray58 = 0x949494, - b2_colorGray59 = 0x969696, - b2_colorGray6 = 0x0f0f0f, - b2_colorGray60 = 0x999999, - b2_colorGray61 = 0x9c9c9c, - b2_colorGray62 = 0x9e9e9e, - b2_colorGray63 = 0xa1a1a1, - b2_colorGray64 = 0xa3a3a3, - b2_colorGray65 = 0xa6a6a6, - b2_colorGray66 = 0xa8a8a8, - b2_colorGray67 = 0xababab, - b2_colorGray68 = 0xadadad, - b2_colorGray69 = 0xb0b0b0, - b2_colorGray7 = 0x121212, - b2_colorGray70 = 0xb3b3b3, - b2_colorGray71 = 0xb5b5b5, - b2_colorGray72 = 0xb8b8b8, - b2_colorGray73 = 0xbababa, - b2_colorGray74 = 0xbdbdbd, - b2_colorGray75 = 0xbfbfbf, - b2_colorGray76 = 0xc2c2c2, - b2_colorGray77 = 0xc4c4c4, - b2_colorGray78 = 0xc7c7c7, - b2_colorGray79 = 0xc9c9c9, - b2_colorGray8 = 0x141414, - b2_colorGray80 = 0xcccccc, - b2_colorGray81 = 0xcfcfcf, - b2_colorGray82 = 0xd1d1d1, - b2_colorGray83 = 0xd4d4d4, - b2_colorGray84 = 0xd6d6d6, - b2_colorGray85 = 0xd9d9d9, - b2_colorGray86 = 0xdbdbdb, - b2_colorGray87 = 0xdedede, - b2_colorGray88 = 0xe0e0e0, - b2_colorGray89 = 0xe3e3e3, - b2_colorGray9 = 0x171717, - b2_colorGray90 = 0xe5e5e5, - b2_colorGray91 = 0xe8e8e8, - b2_colorGray92 = 0xebebeb, - b2_colorGray93 = 0xededed, - b2_colorGray94 = 0xf0f0f0, - b2_colorGray95 = 0xf2f2f2, - b2_colorGray96 = 0xf5f5f5, - b2_colorGray97 = 0xf7f7f7, - b2_colorGray98 = 0xfafafa, - b2_colorGray99 = 0xfcfcfc, - b2_colorGreen = 0x00ff00, - b2_colorGreenYellow = 0xadff2f, - b2_colorHoneydew = 0xf0fff0, - b2_colorHotPink = 0xff69b4, - b2_colorIndianRed = 0xcd5c5c, - b2_colorIndigo = 0x4b0082, - b2_colorIvory = 0xfffff0, - b2_colorKhaki = 0xf0e68c, - b2_colorLavender = 0xe6e6fa, - b2_colorLavenderBlush = 0xfff0f5, - b2_colorLawnGreen = 0x7cfc00, - b2_colorLemonChiffon = 0xfffacd, - b2_colorLightBlue = 0xadd8e6, - b2_colorLightCoral = 0xf08080, - b2_colorLightCyan = 0xe0ffff, - b2_colorLightGoldenrod = 0xeedd82, - b2_colorLightGoldenrodYellow = 0xfafad2, - b2_colorLightGray = 0xd3d3d3, - b2_colorLightGreen = 0x90ee90, - b2_colorLightPink = 0xffb6c1, - b2_colorLightSalmon = 0xffa07a, - b2_colorLightSeaGreen = 0x20b2aa, - b2_colorLightSkyBlue = 0x87cefa, - b2_colorLightSlateBlue = 0x8470ff, - b2_colorLightSlateGray = 0x778899, - b2_colorLightSteelBlue = 0xb0c4de, - b2_colorLightYellow = 0xffffe0, - b2_colorLime = 0x00ff00, - b2_colorLimeGreen = 0x32cd32, - b2_colorLinen = 0xfaf0e6, - b2_colorMagenta = 0xff00ff, - b2_colorMaroon = 0xb03060, - b2_colorMediumAquamarine = 0x66cdaa, - b2_colorMediumBlue = 0x0000cd, - b2_colorMediumOrchid = 0xba55d3, - b2_colorMediumPurple = 0x9370db, - b2_colorMediumSeaGreen = 0x3cb371, - b2_colorMediumSlateBlue = 0x7b68ee, - b2_colorMediumSpringGreen = 0x00fa9a, - b2_colorMediumTurquoise = 0x48d1cc, - b2_colorMediumVioletRed = 0xc71585, - b2_colorMidnightBlue = 0x191970, - b2_colorMintCream = 0xf5fffa, - b2_colorMistyRose = 0xffe4e1, - b2_colorMoccasin = 0xffe4b5, - b2_colorNavajoWhite = 0xffdead, - b2_colorNavy = 0x000080, - b2_colorNavyBlue = 0x000080, - b2_colorOldLace = 0xfdf5e6, - b2_colorOlive = 0x808000, - b2_colorOliveDrab = 0x6b8e23, - b2_colorOrange = 0xffa500, - b2_colorOrangeRed = 0xff4500, - b2_colorOrchid = 0xda70d6, - b2_colorPaleGoldenrod = 0xeee8aa, - b2_colorPaleGreen = 0x98fb98, - b2_colorPaleTurquoise = 0xafeeee, - b2_colorPaleVioletRed = 0xdb7093, - b2_colorPapayaWhip = 0xffefd5, - b2_colorPeachPuff = 0xffdab9, - b2_colorPeru = 0xcd853f, - b2_colorPink = 0xffc0cb, - b2_colorPlum = 0xdda0dd, - b2_colorPowderBlue = 0xb0e0e6, - b2_colorPurple = 0xa020f0, - b2_colorRebeccaPurple = 0x663399, - b2_colorRed = 0xff0000, - b2_colorRosyBrown = 0xbc8f8f, - b2_colorRoyalBlue = 0x4169e1, - b2_colorSaddleBrown = 0x8b4513, - b2_colorSalmon = 0xfa8072, - b2_colorSandyBrown = 0xf4a460, - b2_colorSeaGreen = 0x2e8b57, - b2_colorSeashell = 0xfff5ee, - b2_colorSienna = 0xa0522d, - b2_colorSilver = 0xc0c0c0, - b2_colorSkyBlue = 0x87ceeb, - b2_colorSlateBlue = 0x6a5acd, - b2_colorSlateGray = 0x708090, - b2_colorSnow = 0xfffafa, - b2_colorSpringGreen = 0x00ff7f, - b2_colorSteelBlue = 0x4682b4, - b2_colorTan = 0xd2b48c, - b2_colorTeal = 0x008080, - b2_colorThistle = 0xd8bfd8, - b2_colorTomato = 0xff6347, - b2_colorTurquoise = 0x40e0d0, - b2_colorViolet = 0xee82ee, - b2_colorVioletRed = 0xd02090, - b2_colorWheat = 0xf5deb3, - b2_colorWhite = 0xffffff, - b2_colorWhiteSmoke = 0xf5f5f5, - b2_colorYellow = 0xffff00, - b2_colorYellowGreen = 0x9acd32, -} b2HexColor; diff --git a/include/box2d/debug_draw.h b/include/box2d/debug_draw.h deleted file mode 100644 index 4ecac77e..00000000 --- a/include/box2d/debug_draw.h +++ /dev/null @@ -1,82 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Erin Catto -// SPDX-License-Identifier: MIT - -#pragma once - -#include "math_types.h" -#include "color.h" - -/// This struct holds callbacks you can implement to draw a Box2D world. -/// @ingroup world -typedef struct b2DebugDraw -{ - /// Draw a closed polygon provided in CCW order. - void (*DrawPolygon)(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* context); - - /// Draw a circle. - void (*DrawCircle)(b2Vec2 center, float radius, b2HexColor color, void* context); - - /// Draw a solid circle. - void (*DrawSolidCircle)(b2Transform transform, float radius, b2HexColor color, void* context); - - /// Draw a capsule. - void (*DrawCapsule)(b2Vec2 p1, b2Vec2 p2, float radius, b2HexColor color, void* context); - - /// Draw a solid capsule. - void (*DrawSolidCapsule)(b2Vec2 p1, b2Vec2 p2, float radius, b2HexColor color, void* context); - - /// Draw a line segment. - void (*DrawSegment)(b2Vec2 p1, b2Vec2 p2, b2HexColor color, void* context); - - /// Draw a transform. Choose your own length scale. - void (*DrawTransform)(b2Transform transform, void* context); - - /// Draw a point. - void (*DrawPoint)(b2Vec2 p, float size, b2HexColor color, void* context); - - /// Draw a string. - void (*DrawString)(b2Vec2 p, const char* s, void* context); - - /// Bounds to use if restricting drawing to a rectangular region - b2AABB drawingBounds; - - /// Option to restrict drawing to a rectangular region. May suffer from unstable depth sorting. - bool useDrawingBounds; - - /// Option to draw shapes - bool drawShapes; - - /// Option to draw joints - bool drawJoints; - - /// Option to draw additional information for joints - bool drawJointExtras; - - /// Option to draw the bounding boxes for shapes - bool drawAABBs; - - /// Option to draw the mass and center of mass of dynamic bodies - bool drawMass; - - /// Option to draw contact points - bool drawContacts; - - /// Option to visualize the graph coloring used for contacts and joints - bool drawGraphColors; - - /// Option to draw contact normals - bool drawContactNormals; - - /// Option to draw contact normal impulses - bool drawContactImpulses; - - /// Option to draw contact friction impulses - bool drawFrictionImpulses; - - /// User context that is passed as an argument to drawing callback functions - void* context; -} b2DebugDraw; diff --git a/include/box2d/distance.h b/include/box2d/distance.h deleted file mode 100644 index b62b26d3..00000000 --- a/include/box2d/distance.h +++ /dev/null @@ -1,148 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Erin Catto -// SPDX-License-Identifier: MIT - -#pragma once - -#include "api.h" -#include "geometry.h" - -/** - * @defgroup distance Distance - * Functions for computing the distance between shapes. - * - * These are advanced functions you can use to perform distance calculations. There - * are functions for computing the closest points between shapes, doing linear shape casts, - * and doing rotational shape casts. The latter is called time of impact (TOI). - * @{ - */ - -/// Result of computing the distance between two line segments -typedef struct b2SegmentDistanceResult -{ - b2Vec2 closest1; - b2Vec2 closest2; - float fraction1; - float fraction2; - float distanceSquared; -} b2SegmentDistanceResult; - -/// Compute the distance between two line segments, clamping at the end points if needed. -B2_API b2SegmentDistanceResult b2SegmentDistance(b2Vec2 p1, b2Vec2 q1, b2Vec2 p2, b2Vec2 q2); - -/// A distance proxy is used by the GJK algorithm. It encapsulates any shape. -typedef struct b2DistanceProxy -{ - b2Vec2 vertices[b2_maxPolygonVertices]; - int32_t count; - float radius; -} b2DistanceProxy; - -/// Used to warm start b2Distance. -/// Set count to zero on first call. -typedef struct b2DistanceCache -{ - float metric; ///< length or area - uint16_t count; - uint8_t indexA[3]; ///< vertices on shape A - uint8_t indexB[3]; ///< vertices on shape B -} b2DistanceCache; - -static const b2DistanceCache b2_emptyDistanceCache = B2_ZERO_INIT; - -/// Input for b2Distance. -/// You have to option to use the shape radii -/// in the computation. Even -typedef struct b2DistanceInput -{ - b2DistanceProxy proxyA; - b2DistanceProxy proxyB; - b2Transform transformA; - b2Transform transformB; - bool useRadii; -} b2DistanceInput; - -/// Output for b2Distance. -typedef struct b2DistanceOutput -{ - b2Vec2 pointA; ///< closest point on shapeA - b2Vec2 pointB; ///< closest point on shapeB - float distance; - int32_t iterations; ///< number of GJK iterations used -} b2DistanceOutput; - -/// Compute the closest points between two shapes. Supports any combination of: -/// b2Circle, b2Polygon, b2EdgeShape. The simplex cache is input/output. -/// On the first call set b2SimplexCache.count to zero. -B2_API b2DistanceOutput b2ShapeDistance(b2DistanceCache* cache, const b2DistanceInput* input); - -/// Input parameters for b2ShapeCast -typedef struct b2ShapeCastPairInput -{ - b2DistanceProxy proxyA; - b2DistanceProxy proxyB; - b2Transform transformA; - b2Transform transformB; - b2Vec2 translationB; - float maxFraction; -} b2ShapeCastPairInput; - -/// Perform a linear shape cast of shape B moving and shape A fixed. Determines the hit point, normal, and translation fraction. -B2_API b2CastOutput b2ShapeCast(const b2ShapeCastPairInput* input); - -/// Make a proxy for use in GJK and related functions. -B2_API b2DistanceProxy b2MakeProxy(const b2Vec2* vertices, int32_t count, float radius); - -/// This describes the motion of a body/shape for TOI computation. Shapes are defined with respect to the body origin, -/// which may not coincide with the center of mass. However, to support dynamics we must interpolate the center of mass -/// position. -typedef struct b2Sweep -{ - /// local center of mass position - b2Vec2 localCenter; - - /// center world positions - b2Vec2 c1, c2; - - /// world rotations - b2Rot q1, q2; -} b2Sweep; - -/// Evaluate the transform sweep at a specific time. -B2_API b2Transform b2GetSweepTransform(const b2Sweep* sweep, float time); - -/// Input parameters for b2TimeOfImpact -typedef struct b2TOIInput -{ - b2DistanceProxy proxyA; - b2DistanceProxy proxyB; - b2Sweep sweepA; - b2Sweep sweepB; - - // defines sweep interval [0, tMax] - float tMax; -} b2TOIInput; - -/// Describes the TOI output -typedef enum b2TOIState -{ - b2_toiStateUnknown, - b2_toiStateFailed, - b2_toiStateOverlapped, - b2_toiStateHit, - b2_toiStateSeparated -} b2TOIState; - -/// Output parameters for b2TimeOfImpact. -typedef struct b2TOIOutput -{ - b2TOIState state; - float t; -} b2TOIOutput; - -/// Compute the upper bound on time before two shapes penetrate. Time is represented as -/// a fraction between [0,tMax]. This uses a swept separating axis and may miss some intermediate, -/// non-tunneling collisions. If you change the time interval, you should call this function -/// again. -B2_API b2TOIOutput b2TimeOfImpact(const b2TOIInput* input); - -/**@}*/ diff --git a/include/box2d/dynamic_tree.h b/include/box2d/dynamic_tree.h deleted file mode 100644 index e9946c6f..00000000 --- a/include/box2d/dynamic_tree.h +++ /dev/null @@ -1,195 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Erin Catto -// SPDX-License-Identifier: MIT - -#pragma once - -#include "api.h" -#include "math_types.h" - -#include -#include - -/** - * @defgroup tree Dynamic Tree - * The dynamic tree is a binary AABB tree to organize and query large numbers of geometric objects - * - * Box2D uses the dynamic tree internally to sort collision shapes into a binary bounding volume hierarchy. - * This data structure may have uses in games for organizing other geometry data and may be used independently - * of Box2D rigid body simulation. - * - * @{ - */ - -/// The default category bit for a tree proxy. Used for collision filtering. -#define b2_defaultCategoryBits (0x00000001) - -/// Convenience mask bits to use when you don't need collision filtering and just want -/// all results. -#define b2_defaultMaskBits (0xFFFFFFFF) - -typedef struct b2RayCastInput b2RayCastInput; -typedef struct b2ShapeCastInput b2ShapeCastInput; - -/// A node in the dynamic tree. The user does not interact with this directly. -/// 16 + 16 + 8 + pad(8) -typedef struct b2TreeNode -{ - b2AABB aabb; // 16 - - // Category bits for collision filtering - uint32_t categoryBits; // 4 - - union - { - int32_t parent; - int32_t next; - }; // 4 - - int32_t child1; // 4 - int32_t child2; // 4 - - // todo could be union with child index - int32_t userData; // 4 - - // leaf = 0, free node = -1 - int16_t height; // 2 - - bool enlarged; // 1 - - char pad[9]; -} b2TreeNode; - -/// A dynamic AABB tree broad-phase, inspired by Nathanael Presson's btDbvt. -/// A dynamic tree arranges data in a binary tree to accelerate -/// queries such as AABB queries and ray casts. Leaf nodes are proxies -/// with an AABB. These are used to hold a user collision object, such as a reference to a b2Shape. -/// Nodes are pooled and relocatable, so I use node indices rather than pointers. -/// The dynamic tree is made available for advanced users that would like to use it to organize -/// spatial game data besides rigid bodies. -typedef struct b2DynamicTree -{ - b2TreeNode* nodes; - - int32_t root; - int32_t nodeCount; - int32_t nodeCapacity; - int32_t freeList; - int32_t proxyCount; - - int32_t* leafIndices; - b2AABB* leafBoxes; - b2Vec2* leafCenters; - int32_t* binIndices; - int32_t rebuildCapacity; -} b2DynamicTree; - -/// Constructing the tree initializes the node pool. -B2_API b2DynamicTree b2DynamicTree_Create(void); - -/// Destroy the tree, freeing the node pool. -B2_API void b2DynamicTree_Destroy(b2DynamicTree* tree); - -/// Create a proxy. Provide a tight fitting AABB and a userData value. -B2_API int32_t b2DynamicTree_CreateProxy(b2DynamicTree* tree, b2AABB aabb, uint32_t categoryBits, int32_t userData); - -/// Destroy a proxy. This asserts if the id is invalid. -B2_API void b2DynamicTree_DestroyProxy(b2DynamicTree* tree, int32_t proxyId); - -/// Move a proxy to a new AABB by removing and reinserting into the tree. -B2_API void b2DynamicTree_MoveProxy(b2DynamicTree* tree, int32_t proxyId, b2AABB aabb); - -/// Enlarge a proxy and enlarge ancestors as necessary. -B2_API void b2DynamicTree_EnlargeProxy(b2DynamicTree* tree, int32_t proxyId, b2AABB aabb); - -/// This function receives proxies found in the AABB query. -/// @return true if the query should continue -typedef bool b2TreeQueryCallbackFcn(int32_t proxyId, int32_t userData, void* context); - -/// Query an AABB for overlapping proxies. The callback class -/// is called for each proxy that overlaps the supplied AABB. -B2_API void b2DynamicTree_QueryFiltered(const b2DynamicTree* tree, b2AABB aabb, uint32_t maskBits, - b2TreeQueryCallbackFcn* callback, void* context); - -/// Query an AABB for overlapping proxies. The callback class -/// is called for each proxy that overlaps the supplied AABB. -B2_API void b2DynamicTree_Query(const b2DynamicTree* tree, b2AABB aabb, b2TreeQueryCallbackFcn* callback, void* context); - -/// This function receives clipped raycast input for a proxy. The function -/// returns the new ray fraction. -/// - return a value of 0 to terminate the ray cast -/// - return a value less than input->maxFraction to clip the ray -/// - return a value of input->maxFraction to continue the ray cast without clipping -typedef float b2TreeRayCastCallbackFcn(const b2RayCastInput* input, int32_t proxyId, int32_t userData, void* context); - -/// Ray-cast against the proxies in the tree. This relies on the callback -/// to perform a exact ray-cast in the case were the proxy contains a shape. -/// The callback also performs the any collision filtering. This has performance -/// roughly equal to k * log(n), where k is the number of collisions and n is the -/// number of proxies in the tree. -/// @param input the ray-cast input data. The ray extends from p1 to p1 + maxFraction * (p2 - p1). -/// @param callback a callback class that is called for each proxy that is hit by the ray. -B2_API void b2DynamicTree_RayCast(const b2DynamicTree* tree, const b2RayCastInput* input, uint32_t maskBits, - b2TreeRayCastCallbackFcn* callback, void* context); - -/// This function receives clipped raycast input for a proxy. The function -/// returns the new ray fraction. -/// - return a value of 0 to terminate the ray cast -/// - return a value less than input->maxFraction to clip the ray -/// - return a value of input->maxFraction to continue the ray cast without clipping -typedef float b2TreeShapeCastCallbackFcn(const b2ShapeCastInput* input, int32_t proxyId, int32_t userData, void* context); - -/// Ray-cast against the proxies in the tree. This relies on the callback -/// to perform a exact ray-cast in the case were the proxy contains a shape. -/// The callback also performs the any collision filtering. This has performance -/// roughly equal to k * log(n), where k is the number of collisions and n is the -/// number of proxies in the tree. -/// @param input the ray-cast input data. The ray extends from p1 to p1 + maxFraction * (p2 - p1). -/// @param callback a callback class that is called for each proxy that is hit by the ray. -B2_API void b2DynamicTree_ShapeCast(const b2DynamicTree* tree, const b2ShapeCastInput* input, uint32_t maskBits, - b2TreeShapeCastCallbackFcn* callback, void* context); - -/// Validate this tree. For testing. -B2_API void b2DynamicTree_Validate(const b2DynamicTree* tree); - -/// Compute the height of the binary tree in O(N) time. Should not be -/// called often. -B2_API int32_t b2DynamicTree_GetHeight(const b2DynamicTree* tree); - -/// Get the maximum balance of the tree. The balance is the difference in height of the two children of a node. -B2_API int32_t b2DynamicTree_GetMaxBalance(const b2DynamicTree* tree); - -/// Get the ratio of the sum of the node areas to the root area. -B2_API float b2DynamicTree_GetAreaRatio(const b2DynamicTree* tree); - -/// Build an optimal tree. Very expensive. For testing. -B2_API void b2DynamicTree_RebuildBottomUp(b2DynamicTree* tree); - -/// Get the number of proxies created -B2_API int32_t b2DynamicTree_GetProxyCount(const b2DynamicTree* tree); - -/// Rebuild the tree while retaining subtrees that haven't changed. Returns the number of boxes sorted. -B2_API int32_t b2DynamicTree_Rebuild(b2DynamicTree* tree, bool fullBuild); - -/// Shift the world origin. Useful for large worlds. -/// The shift formula is: position -= newOrigin -/// @param tree the tree to shift -/// @param newOrigin the new origin with respect to the old origin -B2_API void b2DynamicTree_ShiftOrigin(b2DynamicTree* tree, b2Vec2 newOrigin); - -/// Get the number of bytes used by this tree -B2_API int b2DynamicTree_GetByteCount(const b2DynamicTree* tree); - -/// Get proxy user data -/// @return the proxy user data or 0 if the id is invalid -B2_INLINE int32_t b2DynamicTree_GetUserData(const b2DynamicTree* tree, int32_t proxyId) -{ - return tree->nodes[proxyId].userData; -} - -/// Get the AABB of a proxy -B2_INLINE b2AABB b2DynamicTree_GetAABB(const b2DynamicTree* tree, int32_t proxyId) -{ - return tree->nodes[proxyId].aabb; -} - -/**@}*/ diff --git a/include/box2d/event_types.h b/include/box2d/event_types.h deleted file mode 100644 index b93068ff..00000000 --- a/include/box2d/event_types.h +++ /dev/null @@ -1,171 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Erin Catto -// SPDX-License-Identifier: MIT - -#pragma once - -#include "id.h" -#include "collision.h" - -#include -#include -#include - -/** - * @defgroup events Events - * World event types. - * - * Events are used to collect events that occur during the world time step. These events - * are then available to query after the time step is complete. This is preferable to callbacks - * because Box2D uses multi-threaded simulation. - * - * Also when events occur in the simulation step it may be problematic to modify the world, which is - * often what applications want to do when events occur. - * - * With event arrays, you can scan the events in a loop and modify the world. However, you need to be careful - * that some event data may become invalid. There are several samples that show how to do this safely. - * - * @{ - */ - -/// A begin touch event is generated when a shape starts to overlap a sensor shape. -typedef struct b2SensorBeginTouchEvent -{ - /// The id of the sensor shape - b2ShapeId sensorShapeId; - - /// The id of the dynamic shape that began touching the sensor shape - b2ShapeId visitorShapeId; -} b2SensorBeginTouchEvent; - -/// An end touch event is generated when a shape stops overlapping a sensor shape. -typedef struct b2SensorEndTouchEvent -{ - /// The id of the sensor shape - b2ShapeId sensorShapeId; - - /// The id of the dynamic shape that stopped touching the sensor shape - b2ShapeId visitorShapeId; -} b2SensorEndTouchEvent; - -/// Sensor events are buffered in the Box2D world and are available -/// as begin/end overlap event arrays after the time step is complete. -/// Note: these may become invalid if bodies and/or shapes are destroyed -typedef struct b2SensorEvents -{ - /// Array of sensor begin touch events - b2SensorBeginTouchEvent* beginEvents; - - /// Array of sensor end touch events - b2SensorEndTouchEvent* endEvents; - - /// The number of begin touch events - int32_t beginCount; - - /// The number of end touch events - int32_t endCount; -} b2SensorEvents; - -/// A begin touch event is generated when two shapes begin touching. -typedef struct b2ContactBeginTouchEvent -{ - /// Id of the first shape - b2ShapeId shapeIdA; - - /// Id of the second shape - b2ShapeId shapeIdB; -} b2ContactBeginTouchEvent; - -/// An end touch event is generated when two shapes stop touching. -typedef struct b2ContactEndTouchEvent -{ - /// Id of the first shape - b2ShapeId shapeIdA; - - /// Id of the second shape - b2ShapeId shapeIdB; -} b2ContactEndTouchEvent; - -/// A hit touch event is generated when two shapes collide with a speed faster than the hit speed threshold. -typedef struct b2ContactHitEvent -{ - /// Id of the first shape - b2ShapeId shapeIdA; - - /// Id of the second shape - b2ShapeId shapeIdB; - - /// Point where the shapes hit - b2Vec2 point; - - /// Normal vector pointing from shape A to shape B - b2Vec2 normal; - - /// The speed the shapes are approaching. Always positive. Typically in meters per second. - float approachSpeed; -} b2ContactHitEvent; - -/// Contact events are buffered in the Box2D world and are available -/// as event arrays after the time step is complete. -/// Note: these may become invalid if bodies and/or shapes are destroyed -typedef struct b2ContactEvents -{ - /// Array of begin touch events - b2ContactBeginTouchEvent* beginEvents; - - /// Array of end touch events - b2ContactEndTouchEvent* endEvents; - - /// Array of hit events - b2ContactHitEvent* hitEvents; - - /// Number of begin touch events - int32_t beginCount; - - /// Number of end touch events - int32_t endCount; - - /// Number of hit events - int32_t hitCount; -} b2ContactEvents; - -/// Body move events triggered when a body moves. -/// Triggered when a body moves due to simulation. Not reported for bodies moved by the user. -/// This also has a flag to indicate that the body went to sleep so the application can also -/// sleep that actor/entity/object associated with the body. -/// On the other hand if the flag does not indicate the body went to sleep then the application -/// can treat the actor/entity/object associated with the body as awake. -/// This is an efficient way for an application to update game object transforms rather than -/// calling functions such as b2Body_GetTransform() because this data is delivered as a contiguous array -/// and it is only populated with bodies that have moved. -/// @note If sleeping is disabled all dynamic and kinematic bodies will trigger move events. -typedef struct b2BodyMoveEvent -{ - b2Transform transform; - b2BodyId bodyId; - void* userData; - bool fellAsleep; -} b2BodyMoveEvent; - -/// Body events are buffered in the Box2D world and are available -/// as event arrays after the time step is complete. -/// Note: this date becomes invalid if bodies are destroyed -typedef struct b2BodyEvents -{ - /// Array of move events - b2BodyMoveEvent* moveEvents; - - /// Number of move events - int32_t moveCount; -} b2BodyEvents; - -/// The contact data for two shapes. By convention the manifold normal points -/// from shape A to shape B. -/// @see b2Shape_GetContactData() and b2Body_GetContactData() -typedef struct b2ContactData -{ - b2ShapeId shapeIdA; - b2ShapeId shapeIdB; - b2Manifold manifold; -} b2ContactData; - -/**@}*/ diff --git a/include/box2d/geometry.h b/include/box2d/geometry.h deleted file mode 100644 index 9fc4b2d4..00000000 --- a/include/box2d/geometry.h +++ /dev/null @@ -1,217 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Erin Catto -// SPDX-License-Identifier: MIT - -#pragma once - -#include "api.h" -#include "types.h" - -typedef struct b2Hull b2Hull; - -/** - * @defgroup geometry Geometry - * @brief Geometry types and algorithms - * - * Definitions of circles, capsules, segments, and polygons. Various algorithms to compute hulls, mass properties, and so on. - * @{ - */ - -/// The maximum number of vertices on a convex polygon. Changing this affects performance even if you -/// don't use more vertices. -#define b2_maxPolygonVertices 8 - -/// Low level ray-cast input data -typedef struct b2RayCastInput -{ - b2Vec2 origin, translation; - float maxFraction; -} b2RayCastInput; - -/// Low level shape cast input in generic form -typedef struct b2ShapeCastInput -{ - b2Vec2 points[b2_maxPolygonVertices]; - int32_t count; - float radius; - b2Vec2 translation; - float maxFraction; -} b2ShapeCastInput; - -/// Low level ray-cast or shape-cast output data -typedef struct b2CastOutput -{ - b2Vec2 normal; - b2Vec2 point; - float fraction; - int32_t iterations; - bool hit; -} b2CastOutput; - -/// This holds the mass data computed for a shape. -typedef struct b2MassData -{ - /// The mass of the shape, usually in kilograms. - float mass; - - /// The position of the shape's centroid relative to the shape's origin. - b2Vec2 center; - - /// The rotational inertia of the shape about the local origin. - float I; -} b2MassData; - -/// A solid circle -typedef struct b2Circle -{ - b2Vec2 center; - float radius; -} b2Circle; - -/// A solid capsule -typedef struct b2Capsule -{ - b2Vec2 center1, center2; - float radius; -} b2Capsule; - -/// A solid convex polygon. It is assumed that the interior of the polygon is to -/// the left of each edge. -/// Polygons have a maximum number of vertices equal to b2_maxPolygonVertices. -/// In most cases you should not need many vertices for a convex polygon. -/// @warning DO NOT fill this out manually, instead use a helper function like -/// b2MakePolygon or b2MakeBox. -typedef struct b2Polygon -{ - b2Vec2 vertices[b2_maxPolygonVertices]; - b2Vec2 normals[b2_maxPolygonVertices]; - b2Vec2 centroid; - float radius; - int32_t count; -} b2Polygon; - -/// A line segment with two-sided collision. -typedef struct b2Segment -{ - b2Vec2 point1, point2; -} b2Segment; - -/// A smooth line segment with one-sided collision. Only collides on the right side. -/// Several of these are generated for a chain shape. -/// ghost1 -> point1 -> point2 -> ghost2 -typedef struct b2SmoothSegment -{ - /// The tail ghost vertex - b2Vec2 ghost1; - - /// The line segment - b2Segment segment; - - /// The head ghost vertex - b2Vec2 ghost2; - - /// The owning chain shape index (internal usage only) - int32_t chainId; -} b2SmoothSegment; - -/// Validate ray cast input data (NaN, etc) -B2_API bool b2IsValidRay(const b2RayCastInput* input); - -/// Make a convex polygon from a convex hull. This will assert if the hull is not valid. -B2_API b2Polygon b2MakePolygon(const b2Hull* hull, float radius); - -/// Make an offset convex polygon from a convex hull. This will assert if the hull is not valid. -B2_API b2Polygon b2MakeOffsetPolygon(const b2Hull* hull, float radius, b2Transform transform); - -/// Make a square polygon, bypassing the need for a convex hull. -B2_API b2Polygon b2MakeSquare(float h); - -/// Make a box (rectangle) polygon, bypassing the need for a convex hull. -B2_API b2Polygon b2MakeBox(float hx, float hy); - -/// Make a rounded box, bypassing the need for a convex hull. -B2_API b2Polygon b2MakeRoundedBox(float hx, float hy, float radius); - -/// Make an offset box, bypassing the need for a convex hull. -B2_API b2Polygon b2MakeOffsetBox(float hx, float hy, b2Vec2 center, float angle); - -/// Transform a polygon. This is useful for transferring a shape from one body to another. -B2_API b2Polygon b2TransformPolygon(b2Transform transform, const b2Polygon* polygon); - -/// Compute mass properties of a circle -B2_API b2MassData b2ComputeCircleMass(const b2Circle* shape, float density); - -/// Compute mass properties of a capsule -B2_API b2MassData b2ComputeCapsuleMass(const b2Capsule* shape, float density); - -/// Compute mass properties of a polygon -B2_API b2MassData b2ComputePolygonMass(const b2Polygon* shape, float density); - -/// Compute the bounding box of a transformed circle -B2_API b2AABB b2ComputeCircleAABB(const b2Circle* shape, b2Transform transform); - -/// Compute the bounding box of a transformed capsule -B2_API b2AABB b2ComputeCapsuleAABB(const b2Capsule* shape, b2Transform transform); - -/// Compute the bounding box of a transformed polygon -B2_API b2AABB b2ComputePolygonAABB(const b2Polygon* shape, b2Transform transform); - -/// Compute the bounding box of a transformed line segment -B2_API b2AABB b2ComputeSegmentAABB(const b2Segment* shape, b2Transform transform); - -/// Test a point for overlap with a circle in local space -B2_API bool b2PointInCircle(b2Vec2 point, const b2Circle* shape); - -/// Test a point for overlap with a capsule in local space -B2_API bool b2PointInCapsule(b2Vec2 point, const b2Capsule* shape); - -/// Test a point for overlap with a convex polygon in local space -B2_API bool b2PointInPolygon(b2Vec2 point, const b2Polygon* shape); - -/// Ray cast versus circle in shape local space. Initial overlap is treated as a miss. -B2_API b2CastOutput b2RayCastCircle(const b2RayCastInput* input, const b2Circle* shape); - -/// Ray cast versus capsule in shape local space. Initial overlap is treated as a miss. -B2_API b2CastOutput b2RayCastCapsule(const b2RayCastInput* input, const b2Capsule* shape); - -/// Ray cast versus segment in shape local space. Optionally treat the segment as one-sided with hits from -/// the left side being treated as a miss. -B2_API b2CastOutput b2RayCastSegment(const b2RayCastInput* input, const b2Segment* shape, bool oneSided); - -/// Ray cast versus polygon in shape local space. Initial overlap is treated as a miss. -B2_API b2CastOutput b2RayCastPolygon(const b2RayCastInput* input, const b2Polygon* shape); - -/// Shape cast versus a circle. Initial overlap is treated as a miss. -B2_API b2CastOutput b2ShapeCastCircle(const b2ShapeCastInput* input, const b2Circle* shape); - -/// Shape cast versus a capsule. Initial overlap is treated as a miss. -B2_API b2CastOutput b2ShapeCastCapsule(const b2ShapeCastInput* input, const b2Capsule* shape); - -/// Shape cast versus a line segment. Initial overlap is treated as a miss. -B2_API b2CastOutput b2ShapeCastSegment(const b2ShapeCastInput* input, const b2Segment* shape); - -/// Shape cast versus a convex polygon. Initial overlap is treated as a miss. -B2_API b2CastOutput b2ShapeCastPolygon(const b2ShapeCastInput* input, const b2Polygon* shape); - -/// A convex hull. Used to create convex polygons. -typedef struct b2Hull -{ - b2Vec2 points[b2_maxPolygonVertices]; - int32_t count; -} b2Hull; - -/// Compute the convex hull of a set of points. Returns an empty hull if it fails. -/// Some failure cases: -/// - all points very close together -/// - all points on a line -/// - less than 3 points -/// - more than b2_maxPolygonVertices points -/// This welds close points and removes collinear points. -B2_API b2Hull b2ComputeHull(const b2Vec2* points, int32_t count); - -/// This determines if a hull is valid. Checks for: -/// - convexity -/// - collinear points -/// This is expensive and should not be called at runtime. -B2_API bool b2ValidateHull(const b2Hull* hull); - -/**@}*/ diff --git a/include/box2d/id.h b/include/box2d/id.h index b55ac2e4..216b7f26 100644 --- a/include/box2d/id.h +++ b/include/box2d/id.h @@ -3,16 +3,9 @@ #pragma once -#include +#include "box2d/base.h" -// clang-format off -#ifdef __cplusplus - #define B2_ZERO_INIT {} -#else - /// Used for C zero initialization, such as b2BodyId id = {0} where C++ requires b2BodyId id = {} - #define B2_ZERO_INIT {0} -#endif -// clang-format on +#include /** * @defgroup id Ids @@ -74,7 +67,7 @@ typedef struct b2JointId typedef struct b2ChainId { int32_t index1; - int16_t world0; + uint16_t world0; uint16_t revision; } b2ChainId; diff --git a/include/box2d/joint_types.h b/include/box2d/joint_types.h deleted file mode 100644 index 5d3ae31b..00000000 --- a/include/box2d/joint_types.h +++ /dev/null @@ -1,406 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Erin Catto -// SPDX-License-Identifier: MIT - -#pragma once - -#include "types.h" - -/// Joint type enumeration -/// -/// This is useful because all joint types use b2JointId and sometimes you -/// want to get the type of a joint. -/// @ingroup joint -typedef enum b2JointType -{ - b2_distanceJoint, - b2_motorJoint, - b2_mouseJoint, - b2_prismaticJoint, - b2_revoluteJoint, - b2_weldJoint, - b2_wheelJoint, -} b2JointType; - -/// Distance joint definition -/// -/// This requires defining an anchor point on both -/// bodies and the non-zero distance of the distance joint. The definition uses -/// local anchor points so that the initial configuration can violate the -/// constraint slightly. This helps when saving and loading a game. -/// @ingroup distance_joint -typedef struct b2DistanceJointDef -{ - /// The first attached body - b2BodyId bodyIdA; - - /// The second attached body - b2BodyId bodyIdB; - - /// The local anchor point relative to bodyA's origin - b2Vec2 localAnchorA; - - /// The local anchor point relative to bodyB's origin - b2Vec2 localAnchorB; - - /// The rest length of this joint. Clamped to a stable minimum value. - float length; - - /// Enable the distance constraint to behave like a spring. If false - /// then the distance joint will be rigid, overriding the limit and motor. - bool enableSpring; - - /// The spring linear stiffness Hertz, cycles per second - float hertz; - - /// The spring linear damping ratio, non-dimensional - float dampingRatio; - - /// Enable/disable the joint limit - bool enableLimit; - - /// Minimum length. Clamped to a stable minimum value. - float minLength; - - /// Maximum length. Must be greater than or equal to the minimum length. - float maxLength; - - /// Enable/disable the joint motor - bool enableMotor; - - /// The maximum motor force, usually in newtons - float maxMotorForce; - - /// The desired motor speed, usually in meters per second - float motorSpeed; - - /// Set this flag to true if the attached bodies should collide - bool collideConnected; - - /// User data pointer - void* userData; - -} b2DistanceJointDef; - -/// Use this to initialize your joint definition -/// @ingroup distance_joint -B2_API b2DistanceJointDef b2DefaultDistanceJointDef(); - -/// A motor joint is used to control the relative motion between two bodies -/// -/// A typical usage is to control the movement of a dynamic body with respect to the ground. -/// @ingroup motor_joint -typedef struct b2MotorJointDef -{ - /// The first attached body - b2BodyId bodyIdA; - - /// The second attached body - b2BodyId bodyIdB; - - /// Position of bodyB minus the position of bodyA, in bodyA's frame - b2Vec2 linearOffset; - - /// The bodyB angle minus bodyA angle in radians - float angularOffset; - - /// The maximum motor force in newtons - float maxForce; - - /// The maximum motor torque in newton-meters - float maxTorque; - - /// Position correction factor in the range [0,1] - float correctionFactor; - - /// Set this flag to true if the attached bodies should collide - bool collideConnected; - - /// User data pointer - void* userData; - -} b2MotorJointDef; - -/// Use this to initialize your joint definition -/// @ingroup motor_joint -B2_API b2MotorJointDef b2DefaultMotorJointDef(); - -/// A mouse joint is used to make a point on a body track a specified world point. -/// -/// This a soft constraint and allows the constraint to stretch without -/// applying huge forces. This also applies rotation constraint heuristic to improve control. -/// @ingroup mouse_joint -typedef struct b2MouseJointDef -{ - /// The first attached body. - b2BodyId bodyIdA; - - /// The second attached body. - b2BodyId bodyIdB; - - /// The initial target point in world space - b2Vec2 target; - - /// Stiffness in hertz - float hertz; - - /// Damping ratio, non-dimensional - float dampingRatio; - - /// Maximum force, typically in newtons - float maxForce; - - /// Set this flag to true if the attached bodies should collide. - bool collideConnected; - - /// User data pointer - void* userData; - -} b2MouseJointDef; - -/// Use this to initialize your joint definition -/// @ingroup mouse_joint -B2_API b2MouseJointDef b2DefaultMouseJointDef(); - -/// Prismatic joint definition -/// -/// This requires defining a line of motion using an axis and an anchor point. -/// The definition uses local anchor points and a local axis so that the initial -/// configuration can violate the constraint slightly. The joint translation is zero -/// when the local anchor points coincide in world space. -/// @ingroup prismatic_joint -typedef struct b2PrismaticJointDef -{ - /// The first attached body - b2BodyId bodyIdA; - - /// The second attached body - b2BodyId bodyIdB; - - /// The local anchor point relative to bodyA's origin - b2Vec2 localAnchorA; - - /// The local anchor point relative to bodyB's origin - b2Vec2 localAnchorB; - - /// The local translation unit axis in bodyA - b2Vec2 localAxisA; - - /// The constrained angle between the bodies: bodyB_angle - bodyA_angle - float referenceAngle; - - /// Enable a linear spring along the prismatic joint axis - bool enableSpring; - - /// The spring stiffness Hertz, cycles per second - float hertz; - - /// The spring damping ratio, non-dimensional - float dampingRatio; - - /// Enable/disable the joint limit - bool enableLimit; - - /// The lower translation limit - float lowerTranslation; - - /// The upper translation limit - float upperTranslation; - - /// Enable/disable the joint motor - bool enableMotor; - - /// The maximum motor force, typically in newtons - float maxMotorForce; - - /// The desired motor speed, typically in meters per second - float motorSpeed; - - /// Set this flag to true if the attached bodies should collide - bool collideConnected; - - /// User data pointer - void* userData; -} b2PrismaticJointDef; - -/// Use this to initialize your joint definition -/// @ingroupd prismatic_joint -B2_API b2PrismaticJointDef b2DefaultPrismaticJointDef(); - -/// Revolute joint definition -/// -/// This requires defining an anchor point where the bodies are joined. -/// The definition uses local anchor points so that the -/// initial configuration can violate the constraint slightly. You also need to -/// specify the initial relative angle for joint limits. This helps when saving -/// and loading a game. -/// The local anchor points are measured from the body's origin -/// rather than the center of mass because: -/// 1. you might not know where the center of mass will be -/// 2. if you add/remove shapes from a body and recompute the mass, the joints will be broken -/// @ingroup revolute_joint -typedef struct b2RevoluteJointDef -{ - /// The first attached body - b2BodyId bodyIdA; - - /// The second attached body - b2BodyId bodyIdB; - - /// The local anchor point relative to bodyA's origin - b2Vec2 localAnchorA; - - /// The local anchor point relative to bodyB's origin - b2Vec2 localAnchorB; - - /// The bodyB angle minus bodyA angle in the reference state (radians). - /// This defines the zero angle for the joint limit. - float referenceAngle; - - /// Enable a rotational spring on the revolute hinge axis - bool enableSpring; - - /// The spring stiffness Hertz, cycles per second - float hertz; - - /// The spring damping ratio, non-dimensional - float dampingRatio; - - /// A flag to enable joint limits - bool enableLimit; - - /// The lower angle for the joint limit in radians - float lowerAngle; - - /// The upper angle for the joint limit in radians - float upperAngle; - - /// A flag to enable the joint motor - bool enableMotor; - - /// The maximum motor torque, typically in newton-meters - float maxMotorTorque; - - /// The desired motor speed in radians per second - float motorSpeed; - - /// Scale the debug draw - float drawSize; - - /// Set this flag to true if the attached bodies should collide - bool collideConnected; - - /// User data pointer - void* userData; -} b2RevoluteJointDef; - -/// Use this to initialize your joint definition. -/// @ingroup revolute_joint -B2_API b2RevoluteJointDef b2DefaultRevoluteJointDef(); - -/// Weld joint definition -/// -/// A weld joint connect to bodies together rigidly. This constraint provides springs to mimic -/// soft-body simulation. -/// @note The approximate solver in Box2D cannot hold many bodies together rigidly -/// @ingroup weld_joint -typedef struct b2WeldJointDef -{ - /// The first attached body - b2BodyId bodyIdA; - - /// The second attached body - b2BodyId bodyIdB; - - /// The local anchor point relative to bodyA's origin - b2Vec2 localAnchorA; - - /// The local anchor point relative to bodyB's origin - b2Vec2 localAnchorB; - - /// The bodyB angle minus bodyA angle in the reference state (radians) - float referenceAngle; - - /// Linear stiffness expressed as Hertz (cycles per second). Use zero for maximum stiffness. - float linearHertz; - - /// Angular stiffness as Hertz (cycles per second). Use zero for maximum stiffness. - float angularHertz; - - /// Linear damping ratio, non-dimensional. Use 1 for critical damping. - float linearDampingRatio; - - /// Linear damping ratio, non-dimensional. Use 1 for critical damping. - float angularDampingRatio; - - /// Set this flag to true if the attached bodies should collide - bool collideConnected; - - /// User data pointer - void* userData; -} b2WeldJointDef; - -/// Use this to initialize your joint definition -/// @ingroup weld_joint -B2_API b2WeldJointDef b2DefaultWeldJointDef(); - -/// Wheel joint definition -/// -/// This requires defining a line of motion using an axis and an anchor point. -/// The definition uses local anchor points and a local axis so that the initial -/// configuration can violate the constraint slightly. The joint translation is zero -/// when the local anchor points coincide in world space. -/// @ingroup wheel_joint -typedef struct b2WheelJointDef -{ - /// The first attached body - b2BodyId bodyIdA; - - /// The second attached body - b2BodyId bodyIdB; - - /// The local anchor point relative to bodyA's origin - b2Vec2 localAnchorA; - - /// The local anchor point relative to bodyB's origin - b2Vec2 localAnchorB; - - /// The local translation unit axis in bodyA - b2Vec2 localAxisA; - - /// Enable a linear spring along the local axis - bool enableSpring; - - /// Spring stiffness in Hertz - float hertz; - - /// Spring damping ratio, non-dimensional - float dampingRatio; - - /// Enable/disable the joint linear limit - bool enableLimit; - - /// The lower translation limit - float lowerTranslation; - - /// The upper translation limit - float upperTranslation; - - /// Enable/disable the joint rotational motor - bool enableMotor; - - /// The maximum motor torque, typically in newton-meters - float maxMotorTorque; - - /// The desired motor speed in radians per second - float motorSpeed; - - /// Set this flag to true if the attached bodies should collide - bool collideConnected; - - /// User data pointer - void* userData; -} b2WheelJointDef; - -/// Use this to initialize your joint definition -/// @ingroup wheel_joint -B2_API b2WheelJointDef b2DefaultWheelJointDef(); diff --git a/include/box2d/math_cpp.h b/include/box2d/math_cpp.h deleted file mode 100644 index 2b82bc97..00000000 --- a/include/box2d/math_cpp.h +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Erin Catto -// SPDX-License-Identifier: MIT - -#pragma once - -#include "math_functions.h" - -/** - * @defgroup math_cpp C++ Math - * @brief Math operator overloads for C++ - * @{ - */ - -/// Unary add one vector to another -inline void operator+=(b2Vec2& a, b2Vec2 b) -{ - a.x += b.x; - a.y += b.y; -} - -/// Unary subtract one vector from another -inline void operator-=(b2Vec2& a, b2Vec2 b) -{ - a.x -= b.x; - a.y -= b.y; -} - -/// Unary multiply a vector by a scalar -inline void operator*=(b2Vec2& a, float b) -{ - a.x *= b; - a.y *= b; -} - -/// Unary negate a vector -inline b2Vec2 operator-(b2Vec2 a) -{ - return {-a.x, -a.y}; -} - -/// Binary vector addition -inline b2Vec2 operator+(b2Vec2 a, b2Vec2 b) -{ - return {a.x + b.x, a.y + b.y}; -} - -/// Binary vector subtraction -inline b2Vec2 operator-(b2Vec2 a, b2Vec2 b) -{ - return {a.x - b.x, a.y - b.y}; -} - -/// Binary scalar and vector multiplication -inline b2Vec2 operator*(float a, b2Vec2 b) -{ - return {a * b.x, a * b.y}; -} - -/// Binary scalar and vector multiplication -inline b2Vec2 operator*(b2Vec2 a, float b) -{ - return {a.x * b, a.y * b}; -} - -/// Binary vector equality -inline bool operator==(b2Vec2 a, b2Vec2 b) -{ - return a.x == b.x && a.y == b.y; -} - -/// Binary vector inequality -inline bool operator!=(b2Vec2 a, b2Vec2 b) -{ - return a.x != b.x || a.y != b.y; -} - -/**@}*/ diff --git a/include/box2d/math_functions.h b/include/box2d/math_functions.h index ca9c1298..4cb51d30 100644 --- a/include/box2d/math_functions.h +++ b/include/box2d/math_functions.h @@ -3,12 +3,59 @@ #pragma once -#include "api.h" -#include "math_types.h" +#include "base.h" #include #include +/** + * @defgroup math Math + * @brief Vector math types and functions + * @{ + */ + +/// https://en.wikipedia.org/wiki/Pi +#define b2_pi 3.14159265359f + +/// 2D vector +/// This can be used to represent a point or free vector +typedef struct b2Vec2 +{ + /// coordinates + float x, y; +} b2Vec2; + +/// 2D rotation +/// This is similar to using a complex number for rotation +typedef struct b2Rot +{ + /// cosine and sine + float c, s; +} b2Rot; + +/// A 2D rigid transform +typedef struct b2Transform +{ + b2Vec2 p; + b2Rot q; +} b2Transform; + +/// A 2-by-2 Matrix +typedef struct b2Mat22 +{ + /// columns + b2Vec2 cx, cy; +} b2Mat22; + +/// Axis-aligned bounding box +typedef struct b2AABB +{ + b2Vec2 lowerBound; + b2Vec2 upperBound; +} b2AABB; + +/**@}*/ + /** * @addtogroup math * @{ @@ -374,20 +421,20 @@ B2_INLINE b2Vec2 b2InvRotateVector(b2Rot q, b2Vec2 v) } /// Transform a point (e.g. local space to world space) -B2_INLINE b2Vec2 b2TransformPoint(b2Transform xf, const b2Vec2 p) +B2_INLINE b2Vec2 b2TransformPoint(b2Transform t, const b2Vec2 p) { - float x = (xf.q.c * p.x - xf.q.s * p.y) + xf.p.x; - float y = (xf.q.s * p.x + xf.q.c * p.y) + xf.p.y; + float x = (t.q.c * p.x - t.q.s * p.y) + t.p.x; + float y = (t.q.s * p.x + t.q.c * p.y) + t.p.y; return B2_LITERAL(b2Vec2){x, y}; } /// Inverse transform a point (e.g. world space to local space) -B2_INLINE b2Vec2 b2InvTransformPoint(b2Transform xf, const b2Vec2 p) +B2_INLINE b2Vec2 b2InvTransformPoint(b2Transform t, const b2Vec2 p) { - float vx = p.x - xf.p.x; - float vy = p.y - xf.p.y; - return B2_LITERAL(b2Vec2){xf.q.c * vx + xf.q.s * vy, -xf.q.s * vx + xf.q.c * vy}; + float vx = p.x - t.p.x; + float vy = p.y - t.p.y; + return B2_LITERAL(b2Vec2){t.q.c * vx + t.q.s * vy, -t.q.s * vx + t.q.c * vy}; } /// v2 = A.q.Rot(B.q.Rot(v1) + B.p) + A.p @@ -519,3 +566,80 @@ B2_API void b2SetLengthUnitsPerMeter(float lengthUnits); B2_API float b2GetLengthUnitsPerMeter(void); /**@}*/ + +/** + * @defgroup math_cpp C++ Math + * @brief Math operator overloads for C++ + * + * See math_functions.h for details. + * @{ + */ + +#ifdef __cplusplus + +/// Unary add one vector to another +inline void operator+=(b2Vec2& a, b2Vec2 b) +{ + a.x += b.x; + a.y += b.y; +} + +/// Unary subtract one vector from another +inline void operator-=(b2Vec2& a, b2Vec2 b) +{ + a.x -= b.x; + a.y -= b.y; +} + +/// Unary multiply a vector by a scalar +inline void operator*=(b2Vec2& a, float b) +{ + a.x *= b; + a.y *= b; +} + +/// Unary negate a vector +inline b2Vec2 operator-(b2Vec2 a) +{ + return {-a.x, -a.y}; +} + +/// Binary vector addition +inline b2Vec2 operator+(b2Vec2 a, b2Vec2 b) +{ + return {a.x + b.x, a.y + b.y}; +} + +/// Binary vector subtraction +inline b2Vec2 operator-(b2Vec2 a, b2Vec2 b) +{ + return {a.x - b.x, a.y - b.y}; +} + +/// Binary scalar and vector multiplication +inline b2Vec2 operator*(float a, b2Vec2 b) +{ + return {a * b.x, a * b.y}; +} + +/// Binary scalar and vector multiplication +inline b2Vec2 operator*(b2Vec2 a, float b) +{ + return {a.x * b, a.y * b}; +} + +/// Binary vector equality +inline bool operator==(b2Vec2 a, b2Vec2 b) +{ + return a.x == b.x && a.y == b.y; +} + +/// Binary vector inequality +inline bool operator!=(b2Vec2 a, b2Vec2 b) +{ + return a.x != b.x || a.y != b.y; +} + +#endif + +/**@}*/ diff --git a/include/box2d/math_types.h b/include/box2d/math_types.h deleted file mode 100644 index 6d5b489d..00000000 --- a/include/box2d/math_types.h +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Erin Catto -// SPDX-License-Identifier: MIT - -#pragma once - -/** - * @defgroup math Math - * @brief Vector math types and functions - * @{ - */ - -/// https://en.wikipedia.org/wiki/Pi -#define b2_pi 3.14159265359f - -/// 2D vector -/// This can be used to represent a point or free vector -typedef struct b2Vec2 -{ - /// coordinates - float x, y; -} b2Vec2; - -/// 2D rotation -/// This is similar to using a complex number for rotation -typedef struct b2Rot -{ - /// cosine and sine - float c, s; -} b2Rot; - -/// A 2D rigid transform -typedef struct b2Transform -{ - b2Vec2 p; - b2Rot q; -} b2Transform; - -/// A 2-by-2 Matrix -typedef struct b2Mat22 -{ - /// columns - b2Vec2 cx, cy; -} b2Mat22; - -/// Axis-aligned bounding box -typedef struct b2AABB -{ - b2Vec2 lowerBound; - b2Vec2 upperBound; -} b2AABB; - -/**@}*/ diff --git a/include/box2d/timer.h b/include/box2d/timer.h deleted file mode 100644 index 117301f5..00000000 --- a/include/box2d/timer.h +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Erin Catto -// SPDX-License-Identifier: MIT - -#pragma once - -#include "api.h" - -#include - -// Timer for profiling. This has platform specific code and may not work on every platform. - -typedef struct b2Timer -{ -#if defined(_WIN32) - int64_t start; -#elif defined(__linux__) || defined(__APPLE__) - unsigned long long start_sec; - unsigned long long start_usec; -#else - int32_t dummy; -#endif -} b2Timer; - -B2_API b2Timer b2CreateTimer(void); -B2_API int64_t b2GetTicks(b2Timer* timer); -B2_API float b2GetMilliseconds(const b2Timer* timer); -B2_API float b2GetMillisecondsAndReset(b2Timer* timer); -B2_API void b2SleepMilliseconds(int milliseconds); -B2_API void b2Yield(); diff --git a/include/box2d/types.h b/include/box2d/types.h index c64face4..532d84d8 100644 --- a/include/box2d/types.h +++ b/include/box2d/types.h @@ -3,14 +3,50 @@ #pragma once -#include "api.h" -#include "callbacks.h" +#include "base.h" +#include "collision.h" #include "id.h" -#include "math_types.h" +#include "math_functions.h" #include #include +/// Task interface +/// This is prototype for a Box2D task. Your task system is expected to invoke the Box2D task with these arguments. +/// The task spans a range of the parallel-for: [startIndex, endIndex) +/// The worker index must correctly identify each worker in the user thread pool, expected in [0, workerCount). +/// A worker must only exist on only one thread at a time and is analogous to the thread index. +/// The task context is the context pointer sent from Box2D when it is enqueued. +/// The startIndex and endIndex are expected in the range [0, itemCount) where itemCount is the argument to b2EnqueueTaskCallback +/// below. Box2D expects startIndex < endIndex and will execute a loop like this: +/// +/// @code{.c} +/// for (int i = startIndex; i < endIndex; ++i) +/// { +/// DoWork(); +/// } +/// @endcode +/// @ingroup world +typedef void b2TaskCallback(int32_t startIndex, int32_t endIndex, uint32_t workerIndex, void* taskContext); + +/// These functions can be provided to Box2D to invoke a task system. These are designed to work well with enkiTS. +/// Returns a pointer to the user's task object. May be nullptr. A nullptr indicates to Box2D that the work was executed +/// serially within the callback and there is no need to call b2FinishTaskCallback. +/// The itemCount is the number of Box2D work items that are to be partitioned among workers by the user's task system. +/// This is essentially a parallel-for. The minRange parameter is a suggestion of the minimum number of items to assign +/// per worker to reduce overhead. For example, suppose the task is small and that itemCount is 16. A minRange of 8 suggests +/// that your task system should split the work items among just two workers, even if you have more available. +/// In general the range [startIndex, endIndex) send to b2TaskCallback should obey: +/// endIndex - startIndex >= minRange +/// The exception of course is when itemCount < minRange. +/// @ingroup world +typedef void* b2EnqueueTaskCallback(b2TaskCallback* task, int32_t itemCount, int32_t minRange, void* taskContext, + void* userContext); + +/// Finishes a user task object that wraps a Box2D task. +/// @ingroup world +typedef void b2FinishTaskCallback(void* userTask, void* userContext); + /// Result from b2World_RayCastClosest /// @ingroup world typedef struct b2RayResult @@ -23,7 +59,7 @@ typedef struct b2RayResult } b2RayResult; /// World definition used to create a simulation world. -/// Must be initialized using b2DefaultWorldDef. +/// Must be initialized using b2DefaultWorldDef(). /// @ingroup world typedef struct b2WorldDef { @@ -71,8 +107,15 @@ typedef struct b2WorldDef /// User context that is provided to enqueueTask and finishTask void* userTaskContext; + + /// Used internally to detect a valid definition. DO NOT SET. + int32_t internalValue; } b2WorldDef; +/// Use this to initialize your world definition +/// @ingroup world +B2_API b2WorldDef b2DefaultWorldDef(); + /// The body simulation type. /// Each body is one of these three types. The type determines how the body behaves in the simulation. /// @ingroup body @@ -91,6 +134,7 @@ typedef enum b2BodyType /// A body definition holds all the data needed to construct a rigid body. /// You can safely re-use body definitions. Shapes are added to a body after construction. /// Body definitions are temporary objects used to bundle creation parameters. +/// Must be initialized using b2DefaultBodyDef(). /// @ingroup body typedef struct b2BodyDef { @@ -154,8 +198,15 @@ typedef struct b2BodyDef /// Automatically compute mass and related properties on this body from shapes. /// Triggers whenever a shape is add/removed/changed. Default is true. bool automaticMass; + + /// Used internally to detect a valid definition. DO NOT SET. + int32_t internalValue; } b2BodyDef; +/// Use this to initialize your body definition +/// @ingroup body +B2_API b2BodyDef b2DefaultBodyDef(); + /// This is used to filter collision on shapes. It affects shape-vs-shape collision /// and shape-versus-query collision (such as b2World_CastRay). /// @ingroup shape @@ -193,6 +244,10 @@ typedef struct b2Filter int32_t groupIndex; } b2Filter; +/// Use this to initialize your filter +/// @ingroup shape +B2_API b2Filter b2DefaultFilter(); + /// The query filter is used to filter collisions between queries and shapes. For example, /// you may want a ray-cast representing a projectile to hit players and the static environment /// but not debris. @@ -207,6 +262,10 @@ typedef struct b2QueryFilter uint32_t maskBits; } b2QueryFilter; +/// Use this to initialize your query filter +/// @ingroup shape +B2_API b2QueryFilter b2DefaultQueryFilter(); + /// Shape type /// @ingroup shape typedef enum b2ShapeType @@ -233,6 +292,7 @@ typedef enum b2ShapeType /// Used to create a shape. /// This is a temporary object used to bundle shape creation parameters. You may use /// the same shape definition to create multiple shapes. +/// Must be initialized using b2DefaultShapeDef(). /// @ingroup shape typedef struct b2ShapeDef { @@ -264,7 +324,7 @@ typedef struct b2ShapeDef bool enableHitEvents; /// Enable pre-solve contact events for this shape. Only applies to dynamic bodies. These are expensive - /// and must be carefully handled due to multi-threading. Ignored for sensors. + /// and must be carefully handled due to threading. Ignored for sensors. bool enablePreSolveEvents; /// Normally shapes on static bodies don't invoke contact creation when they are added to the world. This overrides @@ -272,8 +332,14 @@ typedef struct b2ShapeDef /// when there are many static shapes. bool forceContactCreation; + /// Used internally to detect a valid definition. DO NOT SET. + int32_t internalValue; } b2ShapeDef; +/// Use this to initialize your shape definition +/// @ingroup shape +B2_API b2ShapeDef b2DefaultShapeDef(); + /// Used to create a chain of edges. This is designed to eliminate ghost collisions with some limitations. /// - chains are one-sided /// - chains have no mass and should be used on static bodies @@ -286,6 +352,7 @@ typedef struct b2ShapeDef /// - you may overlap two open chains on their first three and/or last three points to get smooth collision /// - a chain shape creates multiple smooth edges shapes on the body /// https://en.wikipedia.org/wiki/Polygonal_chain +/// Must be initialized using b2DefaultChainDef(). /// @warning Do not use chain shapes unless you understand the limitations. This is an advanced feature. /// @ingroup shape typedef struct b2ChainDef @@ -310,8 +377,15 @@ typedef struct b2ChainDef /// Indicates a closed chain formed by connecting the first and last points bool isLoop; + + /// Used internally to detect a valid definition. DO NOT SET. + int32_t internalValue; } b2ChainDef; +/// Use this to initialize your chain definition +/// @ingroup shape +B2_API b2ChainDef b2DefaultChainDef(); + //! @cond /// Profiling data. Times are in milliseconds. typedef struct b2Profile @@ -358,26 +432,866 @@ typedef struct b2Counters } b2Counters; //! @endcond -/// Use this to initialize your world definition -/// @ingroup world -B2_API b2WorldDef b2DefaultWorldDef(); +/// Joint type enumeration +/// +/// This is useful because all joint types use b2JointId and sometimes you +/// want to get the type of a joint. +/// @ingroup joint +typedef enum b2JointType +{ + b2_distanceJoint, + b2_motorJoint, + b2_mouseJoint, + b2_prismaticJoint, + b2_revoluteJoint, + b2_weldJoint, + b2_wheelJoint, +} b2JointType; + +/// Distance joint definition +/// +/// This requires defining an anchor point on both +/// bodies and the non-zero distance of the distance joint. The definition uses +/// local anchor points so that the initial configuration can violate the +/// constraint slightly. This helps when saving and loading a game. +/// @ingroup distance_joint +typedef struct b2DistanceJointDef +{ + /// The first attached body + b2BodyId bodyIdA; -/// Use this to initialize your body definition -/// @ingroup body -B2_API b2BodyDef b2DefaultBodyDef(); + /// The second attached body + b2BodyId bodyIdB; -/// Use this to initialize your filter -/// @ingroup shape -B2_API b2Filter b2DefaultFilter(); + /// The local anchor point relative to bodyA's origin + b2Vec2 localAnchorA; -/// Use this to initialize your query filter -/// @ingroup shape -B2_API b2QueryFilter b2DefaultQueryFilter(); + /// The local anchor point relative to bodyB's origin + b2Vec2 localAnchorB; -/// Use this to initialize your shape definition -/// @ingroup shape -B2_API b2ShapeDef b2DefaultShapeDef(); + /// The rest length of this joint. Clamped to a stable minimum value. + float length; -/// Use this to initialize your chain definition -/// @ingroup shape -B2_API b2ChainDef b2DefaultChainDef(); + /// Enable the distance constraint to behave like a spring. If false + /// then the distance joint will be rigid, overriding the limit and motor. + bool enableSpring; + + /// The spring linear stiffness Hertz, cycles per second + float hertz; + + /// The spring linear damping ratio, non-dimensional + float dampingRatio; + + /// Enable/disable the joint limit + bool enableLimit; + + /// Minimum length. Clamped to a stable minimum value. + float minLength; + + /// Maximum length. Must be greater than or equal to the minimum length. + float maxLength; + + /// Enable/disable the joint motor + bool enableMotor; + + /// The maximum motor force, usually in newtons + float maxMotorForce; + + /// The desired motor speed, usually in meters per second + float motorSpeed; + + /// Set this flag to true if the attached bodies should collide + bool collideConnected; + + /// User data pointer + void* userData; + + /// Used internally to detect a valid definition. DO NOT SET. + int32_t internalValue; +} b2DistanceJointDef; + +/// Use this to initialize your joint definition +/// @ingroup distance_joint +B2_API b2DistanceJointDef b2DefaultDistanceJointDef(); + +/// A motor joint is used to control the relative motion between two bodies +/// +/// A typical usage is to control the movement of a dynamic body with respect to the ground. +/// @ingroup motor_joint +typedef struct b2MotorJointDef +{ + /// The first attached body + b2BodyId bodyIdA; + + /// The second attached body + b2BodyId bodyIdB; + + /// Position of bodyB minus the position of bodyA, in bodyA's frame + b2Vec2 linearOffset; + + /// The bodyB angle minus bodyA angle in radians + float angularOffset; + + /// The maximum motor force in newtons + float maxForce; + + /// The maximum motor torque in newton-meters + float maxTorque; + + /// Position correction factor in the range [0,1] + float correctionFactor; + + /// Set this flag to true if the attached bodies should collide + bool collideConnected; + + /// User data pointer + void* userData; + + /// Used internally to detect a valid definition. DO NOT SET. + int32_t internalValue; +} b2MotorJointDef; + +/// Use this to initialize your joint definition +/// @ingroup motor_joint +B2_API b2MotorJointDef b2DefaultMotorJointDef(); + +/// A mouse joint is used to make a point on a body track a specified world point. +/// +/// This a soft constraint and allows the constraint to stretch without +/// applying huge forces. This also applies rotation constraint heuristic to improve control. +/// @ingroup mouse_joint +typedef struct b2MouseJointDef +{ + /// The first attached body. + b2BodyId bodyIdA; + + /// The second attached body. + b2BodyId bodyIdB; + + /// The initial target point in world space + b2Vec2 target; + + /// Stiffness in hertz + float hertz; + + /// Damping ratio, non-dimensional + float dampingRatio; + + /// Maximum force, typically in newtons + float maxForce; + + /// Set this flag to true if the attached bodies should collide. + bool collideConnected; + + /// User data pointer + void* userData; + + /// Used internally to detect a valid definition. DO NOT SET. + int32_t internalValue; +} b2MouseJointDef; + +/// Use this to initialize your joint definition +/// @ingroup mouse_joint +B2_API b2MouseJointDef b2DefaultMouseJointDef(); + +/// Prismatic joint definition +/// +/// This requires defining a line of motion using an axis and an anchor point. +/// The definition uses local anchor points and a local axis so that the initial +/// configuration can violate the constraint slightly. The joint translation is zero +/// when the local anchor points coincide in world space. +/// @ingroup prismatic_joint +typedef struct b2PrismaticJointDef +{ + /// The first attached body + b2BodyId bodyIdA; + + /// The second attached body + b2BodyId bodyIdB; + + /// The local anchor point relative to bodyA's origin + b2Vec2 localAnchorA; + + /// The local anchor point relative to bodyB's origin + b2Vec2 localAnchorB; + + /// The local translation unit axis in bodyA + b2Vec2 localAxisA; + + /// The constrained angle between the bodies: bodyB_angle - bodyA_angle + float referenceAngle; + + /// Enable a linear spring along the prismatic joint axis + bool enableSpring; + + /// The spring stiffness Hertz, cycles per second + float hertz; + + /// The spring damping ratio, non-dimensional + float dampingRatio; + + /// Enable/disable the joint limit + bool enableLimit; + + /// The lower translation limit + float lowerTranslation; + + /// The upper translation limit + float upperTranslation; + + /// Enable/disable the joint motor + bool enableMotor; + + /// The maximum motor force, typically in newtons + float maxMotorForce; + + /// The desired motor speed, typically in meters per second + float motorSpeed; + + /// Set this flag to true if the attached bodies should collide + bool collideConnected; + + /// User data pointer + void* userData; + + /// Used internally to detect a valid definition. DO NOT SET. + int32_t internalValue; +} b2PrismaticJointDef; + +/// Use this to initialize your joint definition +/// @ingroupd prismatic_joint +B2_API b2PrismaticJointDef b2DefaultPrismaticJointDef(); + +/// Revolute joint definition +/// +/// This requires defining an anchor point where the bodies are joined. +/// The definition uses local anchor points so that the +/// initial configuration can violate the constraint slightly. You also need to +/// specify the initial relative angle for joint limits. This helps when saving +/// and loading a game. +/// The local anchor points are measured from the body's origin +/// rather than the center of mass because: +/// 1. you might not know where the center of mass will be +/// 2. if you add/remove shapes from a body and recompute the mass, the joints will be broken +/// @ingroup revolute_joint +typedef struct b2RevoluteJointDef +{ + /// The first attached body + b2BodyId bodyIdA; + + /// The second attached body + b2BodyId bodyIdB; + + /// The local anchor point relative to bodyA's origin + b2Vec2 localAnchorA; + + /// The local anchor point relative to bodyB's origin + b2Vec2 localAnchorB; + + /// The bodyB angle minus bodyA angle in the reference state (radians). + /// This defines the zero angle for the joint limit. + float referenceAngle; + + /// Enable a rotational spring on the revolute hinge axis + bool enableSpring; + + /// The spring stiffness Hertz, cycles per second + float hertz; + + /// The spring damping ratio, non-dimensional + float dampingRatio; + + /// A flag to enable joint limits + bool enableLimit; + + /// The lower angle for the joint limit in radians + float lowerAngle; + + /// The upper angle for the joint limit in radians + float upperAngle; + + /// A flag to enable the joint motor + bool enableMotor; + + /// The maximum motor torque, typically in newton-meters + float maxMotorTorque; + + /// The desired motor speed in radians per second + float motorSpeed; + + /// Scale the debug draw + float drawSize; + + /// Set this flag to true if the attached bodies should collide + bool collideConnected; + + /// User data pointer + void* userData; + + /// Used internally to detect a valid definition. DO NOT SET. + int32_t internalValue; +} b2RevoluteJointDef; + +/// Use this to initialize your joint definition. +/// @ingroup revolute_joint +B2_API b2RevoluteJointDef b2DefaultRevoluteJointDef(); + +/// Weld joint definition +/// +/// A weld joint connect to bodies together rigidly. This constraint provides springs to mimic +/// soft-body simulation. +/// @note The approximate solver in Box2D cannot hold many bodies together rigidly +/// @ingroup weld_joint +typedef struct b2WeldJointDef +{ + /// The first attached body + b2BodyId bodyIdA; + + /// The second attached body + b2BodyId bodyIdB; + + /// The local anchor point relative to bodyA's origin + b2Vec2 localAnchorA; + + /// The local anchor point relative to bodyB's origin + b2Vec2 localAnchorB; + + /// The bodyB angle minus bodyA angle in the reference state (radians) + float referenceAngle; + + /// Linear stiffness expressed as Hertz (cycles per second). Use zero for maximum stiffness. + float linearHertz; + + /// Angular stiffness as Hertz (cycles per second). Use zero for maximum stiffness. + float angularHertz; + + /// Linear damping ratio, non-dimensional. Use 1 for critical damping. + float linearDampingRatio; + + /// Linear damping ratio, non-dimensional. Use 1 for critical damping. + float angularDampingRatio; + + /// Set this flag to true if the attached bodies should collide + bool collideConnected; + + /// User data pointer + void* userData; + + /// Used internally to detect a valid definition. DO NOT SET. + int32_t internalValue; +} b2WeldJointDef; + +/// Use this to initialize your joint definition +/// @ingroup weld_joint +B2_API b2WeldJointDef b2DefaultWeldJointDef(); + +/// Wheel joint definition +/// +/// This requires defining a line of motion using an axis and an anchor point. +/// The definition uses local anchor points and a local axis so that the initial +/// configuration can violate the constraint slightly. The joint translation is zero +/// when the local anchor points coincide in world space. +/// @ingroup wheel_joint +typedef struct b2WheelJointDef +{ + /// The first attached body + b2BodyId bodyIdA; + + /// The second attached body + b2BodyId bodyIdB; + + /// The local anchor point relative to bodyA's origin + b2Vec2 localAnchorA; + + /// The local anchor point relative to bodyB's origin + b2Vec2 localAnchorB; + + /// The local translation unit axis in bodyA + b2Vec2 localAxisA; + + /// Enable a linear spring along the local axis + bool enableSpring; + + /// Spring stiffness in Hertz + float hertz; + + /// Spring damping ratio, non-dimensional + float dampingRatio; + + /// Enable/disable the joint linear limit + bool enableLimit; + + /// The lower translation limit + float lowerTranslation; + + /// The upper translation limit + float upperTranslation; + + /// Enable/disable the joint rotational motor + bool enableMotor; + + /// The maximum motor torque, typically in newton-meters + float maxMotorTorque; + + /// The desired motor speed in radians per second + float motorSpeed; + + /// Set this flag to true if the attached bodies should collide + bool collideConnected; + + /// User data pointer + void* userData; + + /// Used internally to detect a valid definition. DO NOT SET. + int32_t internalValue; +} b2WheelJointDef; + +/// Use this to initialize your joint definition +/// @ingroup wheel_joint +B2_API b2WheelJointDef b2DefaultWheelJointDef(); + +/** + * @defgroup events Events + * World event types. + * + * Events are used to collect events that occur during the world time step. These events + * are then available to query after the time step is complete. This is preferable to callbacks + * because Box2D uses multithreaded simulation. + * + * Also when events occur in the simulation step it may be problematic to modify the world, which is + * often what applications want to do when events occur. + * + * With event arrays, you can scan the events in a loop and modify the world. However, you need to be careful + * that some event data may become invalid. There are several samples that show how to do this safely. + * + * @{ + */ + +/// A begin touch event is generated when a shape starts to overlap a sensor shape. +typedef struct b2SensorBeginTouchEvent +{ + /// The id of the sensor shape + b2ShapeId sensorShapeId; + + /// The id of the dynamic shape that began touching the sensor shape + b2ShapeId visitorShapeId; +} b2SensorBeginTouchEvent; + +/// An end touch event is generated when a shape stops overlapping a sensor shape. +typedef struct b2SensorEndTouchEvent +{ + /// The id of the sensor shape + b2ShapeId sensorShapeId; + + /// The id of the dynamic shape that stopped touching the sensor shape + b2ShapeId visitorShapeId; +} b2SensorEndTouchEvent; + +/// Sensor events are buffered in the Box2D world and are available +/// as begin/end overlap event arrays after the time step is complete. +/// Note: these may become invalid if bodies and/or shapes are destroyed +typedef struct b2SensorEvents +{ + /// Array of sensor begin touch events + b2SensorBeginTouchEvent* beginEvents; + + /// Array of sensor end touch events + b2SensorEndTouchEvent* endEvents; + + /// The number of begin touch events + int32_t beginCount; + + /// The number of end touch events + int32_t endCount; +} b2SensorEvents; + +/// A begin touch event is generated when two shapes begin touching. +typedef struct b2ContactBeginTouchEvent +{ + /// Id of the first shape + b2ShapeId shapeIdA; + + /// Id of the second shape + b2ShapeId shapeIdB; +} b2ContactBeginTouchEvent; + +/// An end touch event is generated when two shapes stop touching. +typedef struct b2ContactEndTouchEvent +{ + /// Id of the first shape + b2ShapeId shapeIdA; + + /// Id of the second shape + b2ShapeId shapeIdB; +} b2ContactEndTouchEvent; + +/// A hit touch event is generated when two shapes collide with a speed faster than the hit speed threshold. +typedef struct b2ContactHitEvent +{ + /// Id of the first shape + b2ShapeId shapeIdA; + + /// Id of the second shape + b2ShapeId shapeIdB; + + /// Point where the shapes hit + b2Vec2 point; + + /// Normal vector pointing from shape A to shape B + b2Vec2 normal; + + /// The speed the shapes are approaching. Always positive. Typically in meters per second. + float approachSpeed; +} b2ContactHitEvent; + +/// Contact events are buffered in the Box2D world and are available +/// as event arrays after the time step is complete. +/// Note: these may become invalid if bodies and/or shapes are destroyed +typedef struct b2ContactEvents +{ + /// Array of begin touch events + b2ContactBeginTouchEvent* beginEvents; + + /// Array of end touch events + b2ContactEndTouchEvent* endEvents; + + /// Array of hit events + b2ContactHitEvent* hitEvents; + + /// Number of begin touch events + int32_t beginCount; + + /// Number of end touch events + int32_t endCount; + + /// Number of hit events + int32_t hitCount; +} b2ContactEvents; + +/// Body move events triggered when a body moves. +/// Triggered when a body moves due to simulation. Not reported for bodies moved by the user. +/// This also has a flag to indicate that the body went to sleep so the application can also +/// sleep that actor/entity/object associated with the body. +/// On the other hand if the flag does not indicate the body went to sleep then the application +/// can treat the actor/entity/object associated with the body as awake. +/// This is an efficient way for an application to update game object transforms rather than +/// calling functions such as b2Body_GetTransform() because this data is delivered as a contiguous array +/// and it is only populated with bodies that have moved. +/// @note If sleeping is disabled all dynamic and kinematic bodies will trigger move events. +typedef struct b2BodyMoveEvent +{ + b2Transform transform; + b2BodyId bodyId; + void* userData; + bool fellAsleep; +} b2BodyMoveEvent; + +/// Body events are buffered in the Box2D world and are available +/// as event arrays after the time step is complete. +/// Note: this date becomes invalid if bodies are destroyed +typedef struct b2BodyEvents +{ + /// Array of move events + b2BodyMoveEvent* moveEvents; + + /// Number of move events + int32_t moveCount; +} b2BodyEvents; + +/// The contact data for two shapes. By convention the manifold normal points +/// from shape A to shape B. +/// @see b2Shape_GetContactData() and b2Body_GetContactData() +typedef struct b2ContactData +{ + b2ShapeId shapeIdA; + b2ShapeId shapeIdB; + b2Manifold manifold; +} b2ContactData; + +/**@}*/ + +/// Prototype for a contact filter callback. +/// This is called when a contact pair is considered for collision. This allows you to +/// perform custom logic to prevent collision between shapes. This is only called if +/// one of the two shapes has custom filtering enabled. @see b2ShapeDef. +/// Notes: +/// - this function must be thread-safe +/// - this is only called if one of the two shapes has enabled custom filtering +/// - this is called only for awake dynamic bodies +/// Return false if you want to disable the collision +/// @warning Do not attempt to modify the world inside this callback +/// @ingroup world +typedef bool b2CustomFilterFcn(b2ShapeId shapeIdA, b2ShapeId shapeIdB, void* context); + +/// Prototype for a pre-solve callback. +/// This is called after a contact is updated. This allows you to inspect a +/// contact before it goes to the solver. If you are careful, you can modify the +/// contact manifold (e.g. modify the normal). +/// Notes: +/// - this function must be thread-safe +/// - this is only called if the shape has enabled presolve events +/// - this is called only for awake dynamic bodies +/// - this is not called for sensors +/// - the supplied manifold has impulse values from the previous step +/// Return false if you want to disable the contact this step +/// @warning Do not attempt to modify the world inside this callback +/// @ingroup world +typedef bool b2PreSolveFcn(b2ShapeId shapeIdA, b2ShapeId shapeIdB, b2Manifold* manifold, void* context); + +/// Prototype callback for overlap queries. +/// Called for each shape found in the query. +/// @see b2World_QueryAABB +/// @return false to terminate the query. +/// @ingroup world +typedef bool b2OverlapResultFcn(b2ShapeId shapeId, void* context); + +/// Prototype callback for ray casts. +/// Called for each shape found in the query. You control how the ray cast +/// proceeds by returning a float: +/// return -1: ignore this shape and continue +/// return 0: terminate the ray cast +/// return fraction: clip the ray to this point +/// return 1: don't clip the ray and continue +/// @param shapeId the shape hit by the ray +/// @param point the point of initial intersection +/// @param normal the normal vector at the point of intersection +/// @param fraction the fraction along the ray at the point of intersection +/// @param context the user context +/// @return -1 to filter, 0 to terminate, fraction to clip the ray for closest hit, 1 to continue +/// @see b2World_CastRay +/// @ingroup world +typedef float b2CastResultFcn(b2ShapeId shapeId, b2Vec2 point, b2Vec2 normal, float fraction, void* context); + +/// These colors are used for debug draw. +typedef enum b2HexColor +{ + b2_colorAliceBlue = 0xf0f8ff, + b2_colorAntiqueWhite = 0xfaebd7, + b2_colorAqua = 0x00ffff, + b2_colorAquamarine = 0x7fffd4, + b2_colorAzure = 0xf0ffff, + b2_colorBeige = 0xf5f5dc, + b2_colorBisque = 0xffe4c4, + b2_colorBlack = 0x000000, + b2_colorBlanchedAlmond = 0xffebcd, + b2_colorBlue = 0x0000ff, + b2_colorBlueViolet = 0x8a2be2, + b2_colorBrown = 0xa52a2a, + b2_colorBurlywood = 0xdeb887, + b2_colorCadetBlue = 0x5f9ea0, + b2_colorChartreuse = 0x7fff00, + b2_colorChocolate = 0xd2691e, + b2_colorCoral = 0xff7f50, + b2_colorCornflowerBlue = 0x6495ed, + b2_colorCornsilk = 0xfff8dc, + b2_colorCrimson = 0xdc143c, + b2_colorCyan = 0x00ffff, + b2_colorDarkBlue = 0x00008b, + b2_colorDarkCyan = 0x008b8b, + b2_colorDarkGoldenrod = 0xb8860b, + b2_colorDarkGray = 0xa9a9a9, + b2_colorDarkGreen = 0x006400, + b2_colorDarkKhaki = 0xbdb76b, + b2_colorDarkMagenta = 0x8b008b, + b2_colorDarkOliveGreen = 0x556b2f, + b2_colorDarkOrange = 0xff8c00, + b2_colorDarkOrchid = 0x9932cc, + b2_colorDarkRed = 0x8b0000, + b2_colorDarkSalmon = 0xe9967a, + b2_colorDarkSeaGreen = 0x8fbc8f, + b2_colorDarkSlateBlue = 0x483d8b, + b2_colorDarkSlateGray = 0x2f4f4f, + b2_colorDarkTurquoise = 0x00ced1, + b2_colorDarkViolet = 0x9400d3, + b2_colorDeepPink = 0xff1493, + b2_colorDeepSkyBlue = 0x00bfff, + b2_colorDimGray = 0x696969, + b2_colorDodgerBlue = 0x1e90ff, + b2_colorFirebrick = 0xb22222, + b2_colorFloralWhite = 0xfffaf0, + b2_colorForestGreen = 0x228b22, + b2_colorFuchsia = 0xff00ff, + b2_colorGainsboro = 0xdcdcdc, + b2_colorGhostWhite = 0xf8f8ff, + b2_colorGold = 0xffd700, + b2_colorGoldenrod = 0xdaa520, + b2_colorGray = 0xbebebe, + b2_colorGray1 = 0x1a1a1a, + b2_colorGray2 = 0x333333, + b2_colorGray3 = 0x4d4d4d, + b2_colorGray4 = 0x666666, + b2_colorGray5 = 0x7f7f7f, + b2_colorGray6 = 0x999999, + b2_colorGray7 = 0xb3b3b3, + b2_colorGray8 = 0xcccccc, + b2_colorGray9 = 0xe5e5e5, + b2_colorGreen = 0x00ff00, + b2_colorGreenYellow = 0xadff2f, + b2_colorHoneydew = 0xf0fff0, + b2_colorHotPink = 0xff69b4, + b2_colorIndianRed = 0xcd5c5c, + b2_colorIndigo = 0x4b0082, + b2_colorIvory = 0xfffff0, + b2_colorKhaki = 0xf0e68c, + b2_colorLavender = 0xe6e6fa, + b2_colorLavenderBlush = 0xfff0f5, + b2_colorLawnGreen = 0x7cfc00, + b2_colorLemonChiffon = 0xfffacd, + b2_colorLightBlue = 0xadd8e6, + b2_colorLightCoral = 0xf08080, + b2_colorLightCyan = 0xe0ffff, + b2_colorLightGoldenrod = 0xeedd82, + b2_colorLightGoldenrodYellow = 0xfafad2, + b2_colorLightGray = 0xd3d3d3, + b2_colorLightGreen = 0x90ee90, + b2_colorLightPink = 0xffb6c1, + b2_colorLightSalmon = 0xffa07a, + b2_colorLightSeaGreen = 0x20b2aa, + b2_colorLightSkyBlue = 0x87cefa, + b2_colorLightSlateBlue = 0x8470ff, + b2_colorLightSlateGray = 0x778899, + b2_colorLightSteelBlue = 0xb0c4de, + b2_colorLightYellow = 0xffffe0, + b2_colorLime = 0x00ff00, + b2_colorLimeGreen = 0x32cd32, + b2_colorLinen = 0xfaf0e6, + b2_colorMagenta = 0xff00ff, + b2_colorMaroon = 0xb03060, + b2_colorMediumAquamarine = 0x66cdaa, + b2_colorMediumBlue = 0x0000cd, + b2_colorMediumOrchid = 0xba55d3, + b2_colorMediumPurple = 0x9370db, + b2_colorMediumSeaGreen = 0x3cb371, + b2_colorMediumSlateBlue = 0x7b68ee, + b2_colorMediumSpringGreen = 0x00fa9a, + b2_colorMediumTurquoise = 0x48d1cc, + b2_colorMediumVioletRed = 0xc71585, + b2_colorMidnightBlue = 0x191970, + b2_colorMintCream = 0xf5fffa, + b2_colorMistyRose = 0xffe4e1, + b2_colorMoccasin = 0xffe4b5, + b2_colorNavajoWhite = 0xffdead, + b2_colorNavy = 0x000080, + b2_colorNavyBlue = 0x000080, + b2_colorOldLace = 0xfdf5e6, + b2_colorOlive = 0x808000, + b2_colorOliveDrab = 0x6b8e23, + b2_colorOrange = 0xffa500, + b2_colorOrangeRed = 0xff4500, + b2_colorOrchid = 0xda70d6, + b2_colorPaleGoldenrod = 0xeee8aa, + b2_colorPaleGreen = 0x98fb98, + b2_colorPaleTurquoise = 0xafeeee, + b2_colorPaleVioletRed = 0xdb7093, + b2_colorPapayaWhip = 0xffefd5, + b2_colorPeachPuff = 0xffdab9, + b2_colorPeru = 0xcd853f, + b2_colorPink = 0xffc0cb, + b2_colorPlum = 0xdda0dd, + b2_colorPowderBlue = 0xb0e0e6, + b2_colorPurple = 0xa020f0, + b2_colorRebeccaPurple = 0x663399, + b2_colorRed = 0xff0000, + b2_colorRosyBrown = 0xbc8f8f, + b2_colorRoyalBlue = 0x4169e1, + b2_colorSaddleBrown = 0x8b4513, + b2_colorSalmon = 0xfa8072, + b2_colorSandyBrown = 0xf4a460, + b2_colorSeaGreen = 0x2e8b57, + b2_colorSeashell = 0xfff5ee, + b2_colorSienna = 0xa0522d, + b2_colorSilver = 0xc0c0c0, + b2_colorSkyBlue = 0x87ceeb, + b2_colorSlateBlue = 0x6a5acd, + b2_colorSlateGray = 0x708090, + b2_colorSnow = 0xfffafa, + b2_colorSpringGreen = 0x00ff7f, + b2_colorSteelBlue = 0x4682b4, + b2_colorTan = 0xd2b48c, + b2_colorTeal = 0x008080, + b2_colorThistle = 0xd8bfd8, + b2_colorTomato = 0xff6347, + b2_colorTurquoise = 0x40e0d0, + b2_colorViolet = 0xee82ee, + b2_colorVioletRed = 0xd02090, + b2_colorWheat = 0xf5deb3, + b2_colorWhite = 0xffffff, + b2_colorWhiteSmoke = 0xf5f5f5, + b2_colorYellow = 0xffff00, + b2_colorYellowGreen = 0x9acd32, +} b2HexColor; + +/// This struct holds callbacks you can implement to draw a Box2D world. +/// @ingroup world +typedef struct b2DebugDraw +{ + /// Draw a closed polygon provided in CCW order. + void (*DrawPolygon)(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* context); + + /// Draw a circle. + void (*DrawCircle)(b2Vec2 center, float radius, b2HexColor color, void* context); + + /// Draw a solid circle. + void (*DrawSolidCircle)(b2Transform transform, float radius, b2HexColor color, void* context); + + /// Draw a capsule. + void (*DrawCapsule)(b2Vec2 p1, b2Vec2 p2, float radius, b2HexColor color, void* context); + + /// Draw a solid capsule. + void (*DrawSolidCapsule)(b2Vec2 p1, b2Vec2 p2, float radius, b2HexColor color, void* context); + + /// Draw a line segment. + void (*DrawSegment)(b2Vec2 p1, b2Vec2 p2, b2HexColor color, void* context); + + /// Draw a transform. Choose your own length scale. + void (*DrawTransform)(b2Transform transform, void* context); + + /// Draw a point. + void (*DrawPoint)(b2Vec2 p, float size, b2HexColor color, void* context); + + /// Draw a string. + void (*DrawString)(b2Vec2 p, const char* s, void* context); + + /// Bounds to use if restricting drawing to a rectangular region + b2AABB drawingBounds; + + /// Option to restrict drawing to a rectangular region. May suffer from unstable depth sorting. + bool useDrawingBounds; + + /// Option to draw shapes + bool drawShapes; + + /// Option to draw joints + bool drawJoints; + + /// Option to draw additional information for joints + bool drawJointExtras; + + /// Option to draw the bounding boxes for shapes + bool drawAABBs; + + /// Option to draw the mass and center of mass of dynamic bodies + bool drawMass; + + /// Option to draw contact points + bool drawContacts; + + /// Option to visualize the graph coloring used for contacts and joints + bool drawGraphColors; + + /// Option to draw contact normals + bool drawContactNormals; + + /// Option to draw contact normal impulses + bool drawContactImpulses; + + /// Option to draw contact friction impulses + bool drawFrictionImpulses; + + /// User context that is passed as an argument to drawing callback functions + void* context; +} b2DebugDraw; diff --git a/samples/car.cpp b/samples/car.cpp index 3c7127c3..c68c146c 100644 --- a/samples/car.cpp +++ b/samples/car.cpp @@ -4,7 +4,6 @@ #include "car.h" #include "box2d/box2d.h" -#include "box2d/geometry.h" #include "box2d/math_functions.h" #include diff --git a/samples/doohickey.cpp b/samples/doohickey.cpp index e66433dd..7e05650e 100644 --- a/samples/doohickey.cpp +++ b/samples/doohickey.cpp @@ -4,7 +4,6 @@ #include "doohickey.h" #include "box2d/box2d.h" -#include "box2d/geometry.h" #include "box2d/math_functions.h" #include diff --git a/samples/draw.cpp b/samples/draw.cpp index 4f8ad1c2..0c53de72 100644 --- a/samples/draw.cpp +++ b/samples/draw.cpp @@ -4,7 +4,6 @@ #include "draw.h" #include "shader.h" -#include "box2d/math_cpp.h" #include "box2d/math_functions.h" #include @@ -1213,9 +1212,9 @@ void DrawSegmentFcn(b2Vec2 p1, b2Vec2 p2, b2HexColor color, void* context) static_cast(context)->DrawSegment(p1, p2, color); } -void DrawTransformFcn(b2Transform xf, void* context) +void DrawTransformFcn(b2Transform transform, void* context) { - static_cast(context)->DrawTransform(xf); + static_cast(context)->DrawTransform(transform); } void DrawPointFcn(b2Vec2 p, float size, b2HexColor color, void* context) @@ -1350,8 +1349,7 @@ void Draw::DrawCircle(b2Vec2 center, float radius, b2HexColor color) void Draw::DrawSolidCircle(b2Transform transform, b2Vec2 center, float radius, b2HexColor color) { - b2Transform xf = transform; - xf.p = b2TransformPoint(transform, center); + transform.p = b2TransformPoint(transform, center); m_solidCircles->AddCircle(transform, radius, color); } @@ -1418,15 +1416,15 @@ void Draw::DrawSegment(b2Vec2 p1, b2Vec2 p2, b2HexColor color) m_lines->AddLine(p1, p2, color); } -void Draw::DrawTransform(b2Transform xf) +void Draw::DrawTransform(b2Transform transform) { const float k_axisScale = 0.2f; - b2Vec2 p1 = xf.p; + b2Vec2 p1 = transform.p; - b2Vec2 p2 = b2MulAdd(p1, k_axisScale, b2Rot_GetXAxis(xf.q)); + b2Vec2 p2 = b2MulAdd(p1, k_axisScale, b2Rot_GetXAxis(transform.q)); m_lines->AddLine(p1, p2, b2_colorRed); - p2 = b2MulAdd(p1, k_axisScale, b2Rot_GetYAxis(xf.q)); + p2 = b2MulAdd(p1, k_axisScale, b2Rot_GetYAxis(transform.q)); m_lines->AddLine(p1, p2, b2_colorGreen); } diff --git a/samples/draw.h b/samples/draw.h index b2de413d..521bc01e 100644 --- a/samples/draw.h +++ b/samples/draw.h @@ -3,7 +3,6 @@ #pragma once -#include "box2d/debug_draw.h" #include "box2d/types.h" // @@ -44,7 +43,7 @@ class Draw void DrawSegment(b2Vec2 p1, b2Vec2 p2, b2HexColor color); - void DrawTransform(b2Transform xf); + void DrawTransform(b2Transform transform); void DrawPoint(b2Vec2 p, float size, b2HexColor color); diff --git a/samples/main.cpp b/samples/main.cpp index 24a76149..eb0cecbc 100644 --- a/samples/main.cpp +++ b/samples/main.cpp @@ -9,10 +9,9 @@ #include "sample.h" #include "settings.h" -#include "box2d/api.h" +#include "box2d/base.h" #include "box2d/box2d.h" #include "box2d/math_functions.h" -#include "box2d/timer.h" #include // Keep glad.h before glfw3.h diff --git a/samples/sample.cpp b/samples/sample.cpp index 990a9e00..443ed0a9 100644 --- a/samples/sample.cpp +++ b/samples/sample.cpp @@ -7,10 +7,7 @@ #include "settings.h" #include "box2d/box2d.h" -#include "box2d/callbacks.h" -#include "box2d/geometry.h" #include "box2d/collision.h" -#include "box2d/math_cpp.h" #include "box2d/math_functions.h" #include diff --git a/samples/sample.h b/samples/sample.h index a4a5b45e..8539445f 100644 --- a/samples/sample.h +++ b/samples/sample.h @@ -5,9 +5,9 @@ #include "box2d/id.h" #include "box2d/collision.h" -#include "box2d/timer.h" #include "box2d/types.h" +// todo this include is slow #include "TaskScheduler.h" #include diff --git a/samples/sample_benchmark.cpp b/samples/sample_benchmark.cpp index 85daed5d..a1a50768 100644 --- a/samples/sample_benchmark.cpp +++ b/samples/sample_benchmark.cpp @@ -7,7 +7,6 @@ #include "settings.h" #include "box2d/box2d.h" -#include "box2d/geometry.h" #include "box2d/math_functions.h" #include @@ -1452,3 +1451,58 @@ class BenchmarkCompound : public Sample }; static int sampleCompound = RegisterSample("Benchmark", "Compound", BenchmarkCompound::Create); + +class BenchmarkKinematic : public Sample +{ +public: + explicit BenchmarkKinematic(Settings& settings) + : Sample(settings) + { + if (settings.restart == false) + { + g_camera.m_center = {0.0f, 0.0f}; + g_camera.m_zoom = 150.0f; + } + + float grid = 1.0f; + +#ifdef NDEBUG + int span = 100; +#else + int span = 20; +#endif + + b2BodyDef bodyDef = b2DefaultBodyDef(); + bodyDef.type = b2_kinematicBody; + bodyDef.angularVelocity = 1.0f; + // defer mass properties to avoid n-squared mass computations + bodyDef.automaticMass = false; + + b2ShapeDef shapeDef = b2DefaultShapeDef(); + shapeDef.filter.categoryBits = 1; + shapeDef.filter.maskBits = 2; + + b2BodyId bodyId = b2CreateBody(m_worldId, &bodyDef); + + for (int i = -span; i < span; ++i) + { + float y = i * grid; + for (int j = -span; j < span; ++j) + { + float x = j * grid; + b2Polygon square = b2MakeOffsetBox(0.5f * grid, 0.5f * grid, {x, y}, 0.0f); + b2CreatePolygonShape(bodyId, &shapeDef, &square); + } + } + + // All shapes have been added so I can efficiently compute the mass properties. + b2Body_ApplyMassFromShapes(bodyId); + } + + static Sample* Create(Settings& settings) + { + return new BenchmarkKinematic(settings); + } +}; + +static int sampleKinematic = RegisterSample("Benchmark", "Kinematic", BenchmarkKinematic::Create); diff --git a/samples/sample_bodies.cpp b/samples/sample_bodies.cpp index 552a0972..0f8deace 100644 --- a/samples/sample_bodies.cpp +++ b/samples/sample_bodies.cpp @@ -6,8 +6,6 @@ #include "settings.h" #include "box2d/box2d.h" -#include "box2d/color.h" -#include "box2d/geometry.h" #include diff --git a/samples/sample_collision.cpp b/samples/sample_collision.cpp index f89a1c2e..91f92322 100644 --- a/samples/sample_collision.cpp +++ b/samples/sample_collision.cpp @@ -6,10 +6,7 @@ #include "settings.h" #include "box2d/box2d.h" -#include "box2d/color.h" -#include "box2d/distance.h" -#include "box2d/dynamic_tree.h" -#include "box2d/geometry.h" +#include "box2d/collision.h" #include "box2d/math_functions.h" #include @@ -143,8 +140,8 @@ class SampleDistance : public Sample { for (int32_t i = 0; i < cache->count; ++i) { - b2Vec2 pointA = b2TransformPoint(input->transformA, input->proxyA.vertices[cache->indexA[i]]); - b2Vec2 pointB = b2TransformPoint(input->transformB, input->proxyB.vertices[cache->indexB[i]]); + b2Vec2 pointA = b2TransformPoint(input->transformA, input->proxyA.points[cache->indexA[i]]); + b2Vec2 pointB = b2TransformPoint(input->transformB, input->proxyB.points[cache->indexB[i]]); g_draw.DrawPoint(pointA, 5.0f, b2_colorGreen); g_draw.DrawPoint(pointB, 5.0f, b2_colorRed); } @@ -170,16 +167,16 @@ class SampleDistance : public Sample #if 0 // circle-circle { - b2Transform xf1 = {offset, b2Rot_identity}; - b2Transform xf2 = {b2Add(m_transform.p, offset), m_transform.q}; + b2Transform transform1 = {offset, b2Rot_identity}; + b2Transform transform2 = {b2Add(m_transform.p, offset), m_transform.q}; b2Manifold m = b2CollideCircles(&m_circle1, &m_circle2); - b2WorldManifold wm = b2ComputeWorldManifold(&m, xf1, m_circle1.radius, xf2, m_circle2.radius); + b2WorldManifold wm = b2ComputeWorldManifold(&m, transform1, m_circle1.radius, transform2, m_circle2.radius); - b2Vec2 c1 = b2TransformPoint(xf1, m_circle1.point); - b2Vec2 c2 = b2TransformPoint(xf2, m_circle2.point); - b2Vec2 axis1 = b2RotateVector(xf1.q, {1.0f, 0.0f}); - b2Vec2 axis2 = b2RotateVector(xf2.q, {1.0f, 0.0f}); + b2Vec2 c1 = b2TransformPoint(transform1, m_circle1.point); + b2Vec2 c2 = b2TransformPoint(transform2, m_circle2.point); + b2Vec2 axis1 = b2RotateVector(transform1.q, {1.0f, 0.0f}); + b2Vec2 axis2 = b2RotateVector(transform2.q, {1.0f, 0.0f}); g_draw.DrawSolidCircle(c1, m_circle1.radius, axis1, color1); g_draw.DrawSolidCircle(c2, m_circle2.radius, axis2, color2); @@ -190,18 +187,18 @@ class SampleDistance : public Sample // capsule-circle { - b2Transform xf1 = {offset, b2Rot_identity}; - b2Transform xf2 = {b2Add(m_transform.p, offset), m_transform.q}; + b2Transform transform1 = {offset, b2Rot_identity}; + b2Transform transform2 = {b2Add(m_transform.p, offset), m_transform.q}; - b2Manifold m = b2CollideCapsuleAndCircle(&m_capsule, xf1, &m_circle1, xf2); - b2WorldManifold wm = b2ComputeWorldManifold(&m, xf1, m_capsule.radius, xf2, m_circle1.radius); + b2Manifold m = b2CollideCapsuleAndCircle(&m_capsule, transform1, &m_circle1, transform2); + b2WorldManifold wm = b2ComputeWorldManifold(&m, transform1, m_capsule.radius, transform2, m_circle1.radius); - b2Vec2 v1 = b2TransformPoint(xf1, m_capsule.point1); - b2Vec2 v2 = b2TransformPoint(xf1, m_capsule.point2); + b2Vec2 v1 = b2TransformPoint(transform1, m_capsule.point1); + b2Vec2 v2 = b2TransformPoint(transform1, m_capsule.point2); g_draw.DrawSolidCapsule(v1, v2, m_capsule.radius, color1); - b2Vec2 c1 = b2TransformPoint(xf2, m_circle1.point); - b2Vec2 axis1 = b2RotateVector(xf2.q, {1.0f, 0.0f}); + b2Vec2 c1 = b2TransformPoint(transform2, m_circle1.point); + b2Vec2 axis1 = b2RotateVector(transform2.q, {1.0f, 0.0f}); g_draw.DrawSolidCircle(c1, m_circle1.radius, axis1, color2); DrawManifold(&m, &wm); @@ -211,18 +208,18 @@ class SampleDistance : public Sample // segment-circle { - b2Transform xf1 = {offset, b2Rot_identity}; - b2Transform xf2 = {b2Add(m_transform.p, offset), m_transform.q}; + b2Transform transform1 = {offset, b2Rot_identity}; + b2Transform transform2 = {b2Add(m_transform.p, offset), m_transform.q}; - b2Manifold m = b2CollideSegmentAndCircle(&m_segment, xf1, &m_circle1, xf2); - b2WorldManifold wm = b2ComputeWorldManifold(&m, xf1, 0.0f, xf2, m_circle1.radius); + b2Manifold m = b2CollideSegmentAndCircle(&m_segment, transform1, &m_circle1, transform2); + b2WorldManifold wm = b2ComputeWorldManifold(&m, transform1, 0.0f, transform2, m_circle1.radius); - b2Vec2 p1 = b2TransformPoint(xf1, m_segment.point1); - b2Vec2 p2 = b2TransformPoint(xf1, m_segment.point2); + b2Vec2 p1 = b2TransformPoint(transform1, m_segment.point1); + b2Vec2 p2 = b2TransformPoint(transform1, m_segment.point2); g_draw.DrawSegment(p1, p2, color1); - b2Vec2 c2 = b2TransformPoint(xf2, m_circle1.point); - b2Vec2 axis2 = b2RotateVector(xf2.q, {1.0f, 0.0f}); + b2Vec2 c2 = b2TransformPoint(transform2, m_circle1.point); + b2Vec2 axis2 = b2RotateVector(transform2.q, {1.0f, 0.0f}); g_draw.DrawSolidCircle(c2, m_circle1.radius, axis2, color2); DrawManifold(&m, &wm); @@ -232,26 +229,26 @@ class SampleDistance : public Sample // smooth segment-circle { - b2Transform xf1 = {offset, b2Rot_identity}; - b2Transform xf2 = {b2Add(m_transform.p, offset), m_transform.q}; + b2Transform transform1 = {offset, b2Rot_identity}; + b2Transform transform2 = {b2Add(m_transform.p, offset), m_transform.q}; - b2Manifold m = b2CollideSmoothSegmentAndCircle(&m_smoothSegment, xf1, &m_circle1, xf2); - b2WorldManifold wm = b2ComputeWorldManifold(&m, xf1, 0.0f, xf2, m_circle1.radius); + b2Manifold m = b2CollideSmoothSegmentAndCircle(&m_smoothSegment, transform1, &m_circle1, transform2); + b2WorldManifold wm = b2ComputeWorldManifold(&m, transform1, 0.0f, transform2, m_circle1.radius); - b2Vec2 p1 = b2TransformPoint(xf1, m_smoothSegment.point1); - b2Vec2 p2 = b2TransformPoint(xf1, m_smoothSegment.point2); + b2Vec2 p1 = b2TransformPoint(transform1, m_smoothSegment.point1); + b2Vec2 p2 = b2TransformPoint(transform1, m_smoothSegment.point2); g_draw.DrawSegment(p1, p2, color1); - p1 = b2TransformPoint(xf1, m_smoothSegment.ghost1); - p2 = b2TransformPoint(xf1, m_smoothSegment.point1); + p1 = b2TransformPoint(transform1, m_smoothSegment.ghost1); + p2 = b2TransformPoint(transform1, m_smoothSegment.point1); g_draw.DrawSegment(p1, p2, dim1); - p1 = b2TransformPoint(xf1, m_smoothSegment.point2); - p2 = b2TransformPoint(xf1, m_smoothSegment.ghost2); + p1 = b2TransformPoint(transform1, m_smoothSegment.point2); + p2 = b2TransformPoint(transform1, m_smoothSegment.ghost2); g_draw.DrawSegment(p1, p2, dim1); - b2Vec2 c2 = b2TransformPoint(xf2, m_circle1.point); - b2Vec2 axis2 = b2RotateVector(xf2.q, {1.0f, 0.0f}); + b2Vec2 c2 = b2TransformPoint(transform2, m_circle1.point); + b2Vec2 axis2 = b2RotateVector(transform2.q, {1.0f, 0.0f}); g_draw.DrawSolidCircle(c2, m_circle1.radius, axis2, color2); DrawManifold(&m, &wm); @@ -266,17 +263,17 @@ class SampleDistance : public Sample #if 0 // capsule-capsule { - b2Transform xf1 = {offset, b2Rot_identity}; - b2Transform xf2 = {b2Add(m_transform.p, offset), m_transform.q}; + b2Transform transform1 = {offset, b2Rot_identity}; + b2Transform transform2 = {b2Add(m_transform.p, offset), m_transform.q}; - b2Manifold m = b2CollideCapsules(&m_capsule, xf1, &m_capsule, xf2); + b2Manifold m = b2CollideCapsules(&m_capsule, transform1, &m_capsule, transform2); - b2Vec2 v1 = b2TransformPoint(xf1, m_capsule.point1); - b2Vec2 v2 = b2TransformPoint(xf1, m_capsule.point2); + b2Vec2 v1 = b2TransformPoint(transform1, m_capsule.point1); + b2Vec2 v2 = b2TransformPoint(transform1, m_capsule.point2); g_draw.DrawSolidCapsule(v1, v2, m_capsule.radius, color1); - v1 = b2TransformPoint(xf2, m_capsule.point1); - v2 = b2TransformPoint(xf2, m_capsule.point2); + v1 = b2TransformPoint(transform2, m_capsule.point1); + v2 = b2TransformPoint(transform2, m_capsule.point2); g_draw.DrawSolidCapsule(v1, v2, m_capsule.radius, color2); DrawManifold(&m); @@ -286,20 +283,20 @@ class SampleDistance : public Sample // box-circle { - b2Transform xf1 = {offset, b2Rot_identity}; - b2Transform xf2 = {b2Add(m_transform.p, offset), m_transform.q}; + b2Transform transform1 = {offset, b2Rot_identity}; + b2Transform transform2 = {b2Add(m_transform.p, offset), m_transform.q}; - b2Manifold m = b2CollidePolygonAndCircle(&m_box, xf1, &m_circle1, xf2); + b2Manifold m = b2CollidePolygonAndCircle(&m_box, transform1, &m_circle1, transform2); b2Vec2 vertices[b2_maxPolygonVertices]; for (int i = 0; i < m_box.count; ++i) { - vertices[i] = b2TransformPoint(xf1, m_box.vertices[i]); + vertices[i] = b2TransformPoint(transform1, m_box.vertices[i]); } g_draw.DrawPolygon(vertices, m_box.count, color1); - b2Vec2 c2 = b2TransformPoint(xf2, m_circle1.point); - b2Vec2 axis2 = b2RotateVector(xf2.q, {1.0f, 0.0f}); + b2Vec2 c2 = b2TransformPoint(transform2, m_circle1.point); + b2Vec2 axis2 = b2RotateVector(transform2.q, {1.0f, 0.0f}); g_draw.DrawSolidCircle(c2, m_circle1.radius, axis2, color2); DrawManifold(&m); @@ -310,14 +307,14 @@ class SampleDistance : public Sample // box-box { - b2Transform xf1 = {offset, b2Rot_identity}; - b2Transform xf2 = {b2Add(m_transform.p, offset), m_transform.q}; + b2Transform transform1 = {offset, b2Rot_identity}; + b2Transform transform2 = {b2Add(m_transform.p, offset), m_transform.q}; b2DistanceInput input; input.proxyA = b2MakeProxy(m_box.vertices, m_box.count, 0.0f); input.proxyB = b2MakeProxy(m_box.vertices, m_box.count, 0.0f); - input.transformA = xf1; - input.transformB = xf2; + input.transformA = transform1; + input.transformB = transform2; input.useRadii = false; b2DistanceCache cache = {0}; @@ -336,13 +333,13 @@ class SampleDistance : public Sample b2Vec2 vertices[b2_maxPolygonVertices]; for (int i = 0; i < m_box.count; ++i) { - vertices[i] = b2TransformPoint(xf1, m_box.vertices[i]); + vertices[i] = b2TransformPoint(transform1, m_box.vertices[i]); } g_draw.DrawPolygon(vertices, m_box.count, color1); for (int i = 0; i < m_box.count; ++i) { - vertices[i] = b2TransformPoint(xf2, m_box.vertices[i]); + vertices[i] = b2TransformPoint(transform2, m_box.vertices[i]); } g_draw.DrawPolygon(vertices, m_box.count, color2); @@ -357,20 +354,20 @@ class SampleDistance : public Sample #if 0 // segment-box { - b2Transform xf1 = {offset, b2Rot_identity}; - b2Transform xf2 = {b2Add(m_transform.p, offset), m_transform.q}; + b2Transform transform1 = {offset, b2Rot_identity}; + b2Transform transform2 = {b2Add(m_transform.p, offset), m_transform.q}; - b2Manifold m = b2CollideSegmentAndPolygon(&m_segment, xf1, &m_box, xf2); - b2WorldManifold wm = b2ComputeWorldManifold(&m, xf1, 0.0f, xf2, 0.0f); + b2Manifold m = b2CollideSegmentAndPolygon(&m_segment, transform1, &m_box, transform2); + b2WorldManifold wm = b2ComputeWorldManifold(&m, transform1, 0.0f, transform2, 0.0f); - b2Vec2 p1 = b2TransformPoint(xf1, m_segment.point1); - b2Vec2 p2 = b2TransformPoint(xf1, m_segment.point2); + b2Vec2 p1 = b2TransformPoint(transform1, m_segment.point1); + b2Vec2 p2 = b2TransformPoint(transform1, m_segment.point2); g_draw.DrawSegment(p1, p2, color1); b2Vec2 vertices[b2_maxPolygonVertices]; for (int i = 0; i < m_box.count; ++i) { - vertices[i] = b2TransformPoint(xf2, m_box.vertices[i]); + vertices[i] = b2TransformPoint(transform2, m_box.vertices[i]); } g_draw.DrawPolygon(vertices, m_box.count, color2); @@ -381,28 +378,28 @@ class SampleDistance : public Sample // smooth segment-box { - b2Transform xf1 = {offset, b2Rot_identity}; - b2Transform xf2 = {b2Add(m_transform.p, offset), m_transform.q}; + b2Transform transform1 = {offset, b2Rot_identity}; + b2Transform transform2 = {b2Add(m_transform.p, offset), m_transform.q}; - b2Manifold m = b2CollideSmoothSegmentAndPolygon(&m_smoothSegment, xf1, &m_box, xf2); - b2WorldManifold wm = b2ComputeWorldManifold(&m, xf1, 0.0f, xf2, 0.0f); + b2Manifold m = b2CollideSmoothSegmentAndPolygon(&m_smoothSegment, transform1, &m_box, transform2); + b2WorldManifold wm = b2ComputeWorldManifold(&m, transform1, 0.0f, transform2, 0.0f); - b2Vec2 p1 = b2TransformPoint(xf1, m_smoothSegment.point1); - b2Vec2 p2 = b2TransformPoint(xf1, m_smoothSegment.point2); + b2Vec2 p1 = b2TransformPoint(transform1, m_smoothSegment.point1); + b2Vec2 p2 = b2TransformPoint(transform1, m_smoothSegment.point2); g_draw.DrawSegment(p1, p2, color1); - p1 = b2TransformPoint(xf1, m_smoothSegment.ghost1); - p2 = b2TransformPoint(xf1, m_smoothSegment.point1); + p1 = b2TransformPoint(transform1, m_smoothSegment.ghost1); + p2 = b2TransformPoint(transform1, m_smoothSegment.point1); g_draw.DrawSegment(p1, p2, dim1); - p1 = b2TransformPoint(xf1, m_smoothSegment.point2); - p2 = b2TransformPoint(xf1, m_smoothSegment.ghost2); + p1 = b2TransformPoint(transform1, m_smoothSegment.point2); + p2 = b2TransformPoint(transform1, m_smoothSegment.ghost2); g_draw.DrawSegment(p1, p2, dim1); b2Vec2 vertices[b2_maxPolygonVertices]; for (int i = 0; i < m_box.count; ++i) { - vertices[i] = b2TransformPoint(xf2, m_box.vertices[i]); + vertices[i] = b2TransformPoint(transform2, m_box.vertices[i]); } g_draw.DrawPolygon(vertices, m_box.count, color2); @@ -687,7 +684,7 @@ class DynamicTree : public Sample if (m_queryDrag) { b2AABB box = {b2Min(m_startPoint, m_endPoint), b2Max(m_startPoint, m_endPoint)}; - b2DynamicTree_QueryFiltered(&m_tree, box, b2_defaultMaskBits, QueryCallback, this); + b2DynamicTree_Query(&m_tree, box, b2_defaultMaskBits, QueryCallback, this); g_draw.DrawAABB(box, b2_colorWhite); } @@ -1067,19 +1064,19 @@ class RayCast : public Sample // circle { - b2Transform xf = {b2Add(m_transform.p, offset), m_transform.q}; - g_draw.DrawSolidCircle(xf, m_circle.center, m_circle.radius, color1); + b2Transform transform = {b2Add(m_transform.p, offset), m_transform.q}; + g_draw.DrawSolidCircle(transform, m_circle.center, m_circle.radius, color1); - b2Vec2 start = b2InvTransformPoint(xf, m_rayStart); - b2Vec2 translation = b2InvRotateVector(xf.q, b2Sub(m_rayEnd, m_rayStart)); + b2Vec2 start = b2InvTransformPoint(transform, m_rayStart); + b2Vec2 translation = b2InvRotateVector(transform.q, b2Sub(m_rayEnd, m_rayStart)); b2RayCastInput input = {start, translation, maxFraction}; b2CastOutput localOutput = b2RayCastCircle(&input, &m_circle); if (localOutput.hit) { output = localOutput; - output.point = b2TransformPoint(xf, localOutput.point); - output.normal = b2RotateVector(xf.q, localOutput.normal); + output.point = b2TransformPoint(transform, localOutput.point); + output.normal = b2RotateVector(transform.q, localOutput.normal); maxFraction = localOutput.fraction; } @@ -1088,21 +1085,21 @@ class RayCast : public Sample // capsule { - b2Transform xf = {b2Add(m_transform.p, offset), m_transform.q}; - b2Vec2 v1 = b2TransformPoint(xf, m_capsule.center1); - b2Vec2 v2 = b2TransformPoint(xf, m_capsule.center2); + b2Transform transform = {b2Add(m_transform.p, offset), m_transform.q}; + b2Vec2 v1 = b2TransformPoint(transform, m_capsule.center1); + b2Vec2 v2 = b2TransformPoint(transform, m_capsule.center2); g_draw.DrawSolidCapsule(v1, v2, m_capsule.radius, color1); - b2Vec2 start = b2InvTransformPoint(xf, m_rayStart); - b2Vec2 translation = b2InvRotateVector(xf.q, b2Sub(m_rayEnd, m_rayStart)); + b2Vec2 start = b2InvTransformPoint(transform, m_rayStart); + b2Vec2 translation = b2InvRotateVector(transform.q, b2Sub(m_rayEnd, m_rayStart)); b2RayCastInput input = {start, translation, maxFraction}; b2CastOutput localOutput = b2RayCastCapsule(&input, &m_capsule); if (localOutput.hit) { output = localOutput; - output.point = b2TransformPoint(xf, localOutput.point); - output.normal = b2RotateVector(xf.q, localOutput.normal); + output.point = b2TransformPoint(transform, localOutput.point); + output.normal = b2RotateVector(transform.q, localOutput.normal); maxFraction = localOutput.fraction; } @@ -1111,19 +1108,19 @@ class RayCast : public Sample // box { - b2Transform xf = {b2Add(m_transform.p, offset), m_transform.q}; - g_draw.DrawSolidPolygon(xf, m_box.vertices, m_box.count, 0.0f, color1); + b2Transform transform = {b2Add(m_transform.p, offset), m_transform.q}; + g_draw.DrawSolidPolygon(transform, m_box.vertices, m_box.count, 0.0f, color1); - b2Vec2 start = b2InvTransformPoint(xf, m_rayStart); - b2Vec2 translation = b2InvRotateVector(xf.q, b2Sub(m_rayEnd, m_rayStart)); + b2Vec2 start = b2InvTransformPoint(transform, m_rayStart); + b2Vec2 translation = b2InvRotateVector(transform.q, b2Sub(m_rayEnd, m_rayStart)); b2RayCastInput input = {start, translation, maxFraction}; b2CastOutput localOutput = b2RayCastPolygon(&input, &m_box); if (localOutput.hit) { output = localOutput; - output.point = b2TransformPoint(xf, localOutput.point); - output.normal = b2RotateVector(xf.q, localOutput.normal); + output.point = b2TransformPoint(transform, localOutput.point); + output.normal = b2RotateVector(transform.q, localOutput.normal); maxFraction = localOutput.fraction; } @@ -1132,19 +1129,19 @@ class RayCast : public Sample // triangle { - b2Transform xf = {b2Add(m_transform.p, offset), m_transform.q}; - g_draw.DrawSolidPolygon(xf, m_triangle.vertices, m_triangle.count, 0.0f, color1); + b2Transform transform = {b2Add(m_transform.p, offset), m_transform.q}; + g_draw.DrawSolidPolygon(transform, m_triangle.vertices, m_triangle.count, 0.0f, color1); - b2Vec2 start = b2InvTransformPoint(xf, m_rayStart); - b2Vec2 translation = b2InvRotateVector(xf.q, b2Sub(m_rayEnd, m_rayStart)); + b2Vec2 start = b2InvTransformPoint(transform, m_rayStart); + b2Vec2 translation = b2InvRotateVector(transform.q, b2Sub(m_rayEnd, m_rayStart)); b2RayCastInput input = {start, translation, maxFraction}; b2CastOutput localOutput = b2RayCastPolygon(&input, &m_triangle); if (localOutput.hit) { output = localOutput; - output.point = b2TransformPoint(xf, localOutput.point); - output.normal = b2RotateVector(xf.q, localOutput.normal); + output.point = b2TransformPoint(transform, localOutput.point); + output.normal = b2RotateVector(transform.q, localOutput.normal); maxFraction = localOutput.fraction; } @@ -1153,22 +1150,22 @@ class RayCast : public Sample // segment { - b2Transform xf = {b2Add(m_transform.p, offset), m_transform.q}; + b2Transform transform = {b2Add(m_transform.p, offset), m_transform.q}; - b2Vec2 p1 = b2TransformPoint(xf, m_segment.point1); - b2Vec2 p2 = b2TransformPoint(xf, m_segment.point2); + b2Vec2 p1 = b2TransformPoint(transform, m_segment.point1); + b2Vec2 p2 = b2TransformPoint(transform, m_segment.point2); g_draw.DrawSegment(p1, p2, color1); - b2Vec2 start = b2InvTransformPoint(xf, m_rayStart); - b2Vec2 translation = b2InvRotateVector(xf.q, b2Sub(m_rayEnd, m_rayStart)); + b2Vec2 start = b2InvTransformPoint(transform, m_rayStart); + b2Vec2 translation = b2InvRotateVector(transform.q, b2Sub(m_rayEnd, m_rayStart)); b2RayCastInput input = {start, translation, maxFraction}; b2CastOutput localOutput = b2RayCastSegment(&input, &m_segment, false); if (localOutput.hit) { output = localOutput; - output.point = b2TransformPoint(xf, localOutput.point); - output.normal = b2RotateVector(xf.q, localOutput.normal); + output.point = b2TransformPoint(transform, localOutput.point); + output.normal = b2RotateVector(transform.q, localOutput.normal); maxFraction = localOutput.fraction; } @@ -1666,7 +1663,7 @@ class RayCastWorld : public Sample m_textLine += m_textIncrement; b2HexColor color1 = b2_colorGreen; - b2HexColor color2 = b2_colorGray80; + b2HexColor color2 = b2_colorGray8; b2HexColor color3 = b2_colorMagenta; b2Vec2 rayTranslation = b2Sub(m_rayEnd, m_rayStart); @@ -1779,8 +1776,8 @@ class RayCastWorld : public Sample } else if (m_castType == e_polygonCast) { - b2Transform xf = {b2Add(transform.p, t), transform.q}; - g_draw.DrawSolidPolygon(xf, box.vertices, box.count, box.radius, b2_colorYellow); + b2Transform shiftedTransform = {b2Add(transform.p, t), transform.q}; + g_draw.DrawSolidPolygon(shiftedTransform, box.vertices, box.count, box.radius, b2_colorYellow); } } } @@ -1800,8 +1797,8 @@ class RayCastWorld : public Sample } else if (m_castType == e_polygonCast) { - b2Transform xf = {b2Add(transform.p, rayTranslation), transform.q}; - g_draw.DrawSolidPolygon(xf, box.vertices, box.count, box.radius, b2_colorYellow); + b2Transform shiftedTransform = {b2Add(transform.p, rayTranslation), transform.q}; + g_draw.DrawSolidPolygon(shiftedTransform, box.vertices, box.count, box.radius, b2_colorYellow); } } } @@ -2412,15 +2409,15 @@ class Manifold : public Sample b2Circle circle1 = {{0.0f, 0.0f}, 0.5f}; b2Circle circle2 = {{0.0f, 0.0f}, 1.0f}; - b2Transform xf1 = {offset, b2Rot_identity}; - b2Transform xf2 = {b2Add(m_transform.p, offset), m_transform.q}; + b2Transform transform1 = {offset, b2Rot_identity}; + b2Transform transform2 = {b2Add(m_transform.p, offset), m_transform.q}; - b2Manifold m = b2CollideCircles(&circle1, xf1, &circle2, xf2); + b2Manifold m = b2CollideCircles(&circle1, transform1, &circle2, transform2); - g_draw.DrawSolidCircle(xf1, circle1.center, circle1.radius, color1); - g_draw.DrawSolidCircle(xf2, circle2.center, circle2.radius, color2); + g_draw.DrawSolidCircle(transform1, circle1.center, circle1.radius, color1); + g_draw.DrawSolidCircle(transform2, circle2.center, circle2.radius, color2); - DrawManifold(&m, xf1.p, xf2.p); + DrawManifold(&m, transform1.p, transform2.p); offset = b2Add(offset, increment); } @@ -2430,18 +2427,18 @@ class Manifold : public Sample b2Capsule capsule = {{-0.5f, 0.0f}, {0.5f, 0.0}, 0.25f}; b2Circle circle = {{0.0f, 0.0f}, 0.5f}; - b2Transform xf1 = {offset, b2Rot_identity}; - b2Transform xf2 = {b2Add(m_transform.p, offset), m_transform.q}; + b2Transform transform1 = {offset, b2Rot_identity}; + b2Transform transform2 = {b2Add(m_transform.p, offset), m_transform.q}; - b2Manifold m = b2CollideCapsuleAndCircle(&capsule, xf1, &circle, xf2); + b2Manifold m = b2CollideCapsuleAndCircle(&capsule, transform1, &circle, transform2); - b2Vec2 v1 = b2TransformPoint(xf1, capsule.center1); - b2Vec2 v2 = b2TransformPoint(xf1, capsule.center2); + b2Vec2 v1 = b2TransformPoint(transform1, capsule.center1); + b2Vec2 v2 = b2TransformPoint(transform1, capsule.center2); g_draw.DrawSolidCapsule(v1, v2, capsule.radius, color1); - g_draw.DrawSolidCircle(xf2, circle.center, circle.radius, color2); + g_draw.DrawSolidCircle(transform2, circle.center, circle.radius, color2); - DrawManifold(&m, xf1.p, xf2.p); + DrawManifold(&m, transform1.p, transform2.p); offset = b2Add(offset, increment); } @@ -2451,18 +2448,18 @@ class Manifold : public Sample b2Segment segment = {{-1.0f, 0.0f}, {1.0f, 0.0}}; b2Circle circle = {{0.0f, 0.0f}, 0.5f}; - b2Transform xf1 = {offset, b2Rot_identity}; - b2Transform xf2 = {b2Add(m_transform.p, offset), m_transform.q}; + b2Transform transform1 = {offset, b2Rot_identity}; + b2Transform transform2 = {b2Add(m_transform.p, offset), m_transform.q}; - b2Manifold m = b2CollideSegmentAndCircle(&segment, xf1, &circle, xf2); + b2Manifold m = b2CollideSegmentAndCircle(&segment, transform1, &circle, transform2); - b2Vec2 p1 = b2TransformPoint(xf1, segment.point1); - b2Vec2 p2 = b2TransformPoint(xf1, segment.point2); + b2Vec2 p1 = b2TransformPoint(transform1, segment.point1); + b2Vec2 p2 = b2TransformPoint(transform1, segment.point2); g_draw.DrawSegment(p1, p2, color1); - g_draw.DrawSolidCircle(xf2, circle.center, circle.radius, color2); + g_draw.DrawSolidCircle(transform2, circle.center, circle.radius, color2); - DrawManifold(&m, xf1.p, xf2.p); + DrawManifold(&m, transform1.p, transform2.p); offset = b2Add(offset, increment); } @@ -2473,15 +2470,15 @@ class Manifold : public Sample b2Polygon box = b2MakeSquare(0.5f); box.radius = m_round; - b2Transform xf1 = {offset, b2Rot_identity}; - b2Transform xf2 = {b2Add(m_transform.p, offset), m_transform.q}; + b2Transform transform1 = {offset, b2Rot_identity}; + b2Transform transform2 = {b2Add(m_transform.p, offset), m_transform.q}; - b2Manifold m = b2CollidePolygonAndCircle(&box, xf1, &circle, xf2); + b2Manifold m = b2CollidePolygonAndCircle(&box, transform1, &circle, transform2); - g_draw.DrawSolidPolygon(xf1, box.vertices, box.count, m_round, color1); - g_draw.DrawSolidCircle(xf2, circle.center, circle.radius, color2); + g_draw.DrawSolidPolygon(transform1, box.vertices, box.count, m_round, color1); + g_draw.DrawSolidCircle(transform2, circle.center, circle.radius, color2); - DrawManifold(&m, xf1.p, xf2.p); + DrawManifold(&m, transform1.p, transform2.p); offset = b2Add(offset, increment); } @@ -2490,25 +2487,25 @@ class Manifold : public Sample { b2Capsule capsule = {{-0.5f, 0.0f}, {0.5f, 0.0}, 0.25f}; - b2Transform xf1 = {offset, b2Rot_identity}; - b2Transform xf2 = {b2Add(m_transform.p, offset), m_transform.q}; + b2Transform transform1 = {offset, b2Rot_identity}; + b2Transform transform2 = {b2Add(m_transform.p, offset), m_transform.q}; if (m_enableCaching == false) { m_capcapCache = b2_emptyDistanceCache; } - b2Manifold m = b2CollideCapsules(&capsule, xf1, &capsule, xf2, &m_capcapCache); + b2Manifold m = b2CollideCapsules(&capsule, transform1, &capsule, transform2, &m_capcapCache); - b2Vec2 v1 = b2TransformPoint(xf1, capsule.center1); - b2Vec2 v2 = b2TransformPoint(xf1, capsule.center2); + b2Vec2 v1 = b2TransformPoint(transform1, capsule.center1); + b2Vec2 v2 = b2TransformPoint(transform1, capsule.center2); g_draw.DrawSolidCapsule(v1, v2, capsule.radius, color1); - v1 = b2TransformPoint(xf2, capsule.center1); - v2 = b2TransformPoint(xf2, capsule.center2); + v1 = b2TransformPoint(transform2, capsule.center1); + v2 = b2TransformPoint(transform2, capsule.center2); g_draw.DrawSolidCapsule(v1, v2, capsule.radius, color2); - DrawManifold(&m, xf1.p, xf2.p); + DrawManifold(&m, transform1.p, transform2.p); offset = b2Add(offset, increment); } @@ -2518,19 +2515,19 @@ class Manifold : public Sample b2Capsule capsule = {{-0.4f, 0.0f}, {-0.1f, 0.0f}, 0.1f}; b2Polygon box = b2MakeOffsetBox(0.25f, 1.0f, {1.0f, -1.0f}, 0.25f * b2_pi); - b2Transform xf1 = {offset, b2Rot_identity}; - b2Transform xf2 = {b2Add(m_transform.p, offset), m_transform.q}; + b2Transform transform1 = {offset, b2Rot_identity}; + b2Transform transform2 = {b2Add(m_transform.p, offset), m_transform.q}; b2DistanceCache cache = b2_emptyDistanceCache; - b2Manifold m = b2CollidePolygonAndCapsule(&box, xf1, &capsule, xf2, &cache); + b2Manifold m = b2CollidePolygonAndCapsule(&box, transform1, &capsule, transform2, &cache); - g_draw.DrawSolidPolygon(xf1, box.vertices, box.count, box.radius, color1); + g_draw.DrawSolidPolygon(transform1, box.vertices, box.count, box.radius, color1); - b2Vec2 v1 = b2TransformPoint(xf2, capsule.center1); - b2Vec2 v2 = b2TransformPoint(xf2, capsule.center2); + b2Vec2 v1 = b2TransformPoint(transform2, capsule.center1); + b2Vec2 v2 = b2TransformPoint(transform2, capsule.center2); g_draw.DrawSolidCapsule(v1, v2, capsule.radius, color2); - DrawManifold(&m, xf1.p, xf2.p); + DrawManifold(&m, transform1.p, transform2.p); offset = b2Add(offset, increment); } @@ -2540,20 +2537,20 @@ class Manifold : public Sample b2Segment segment = {{-1.0f, 0.0f}, {1.0f, 0.0}}; b2Capsule capsule = {{-0.5f, 0.0f}, {0.5f, 0.0}, 0.25f}; - b2Transform xf1 = {offset, b2Rot_identity}; - b2Transform xf2 = {b2Add(m_transform.p, offset), m_transform.q}; + b2Transform transform1 = {offset, b2Rot_identity}; + b2Transform transform2 = {b2Add(m_transform.p, offset), m_transform.q}; - b2Manifold m = b2CollideSegmentAndCapsule(&segment, xf1, &capsule, xf2, &m_segcapCache); + b2Manifold m = b2CollideSegmentAndCapsule(&segment, transform1, &capsule, transform2, &m_segcapCache); - b2Vec2 p1 = b2TransformPoint(xf1, segment.point1); - b2Vec2 p2 = b2TransformPoint(xf1, segment.point2); + b2Vec2 p1 = b2TransformPoint(transform1, segment.point1); + b2Vec2 p2 = b2TransformPoint(transform1, segment.point2); g_draw.DrawSegment(p1, p2, color1); - p1 = b2TransformPoint(xf2, capsule.center1); - p2 = b2TransformPoint(xf2, capsule.center2); + p1 = b2TransformPoint(transform2, capsule.center1); + p2 = b2TransformPoint(transform2, capsule.center2); g_draw.DrawSolidCapsule(p1, p2, capsule.radius, color2); - DrawManifold(&m, xf1.p, xf2.p); + DrawManifold(&m, transform1.p, transform2.p); offset = b2Add(offset, increment); } @@ -2564,16 +2561,16 @@ class Manifold : public Sample { b2Polygon box = b2MakeSquare(0.5f); - b2Transform xf1 = {offset, b2Rot_identity}; - b2Transform xf2 = {b2Add(m_transform.p, offset), m_transform.q}; - // b2Transform xf2 = {b2Add({0.0f, -0.1f}, offset), {0.0f, 1.0f}}; + b2Transform transform1 = {offset, b2Rot_identity}; + b2Transform transform2 = {b2Add(m_transform.p, offset), m_transform.q}; + // b2Transform transform2 = {b2Add({0.0f, -0.1f}, offset), {0.0f, 1.0f}}; - b2Manifold m = b2CollidePolygons(&box, xf1, &box, xf2, &m_boxboxCache); + b2Manifold m = b2CollidePolygons(&box, transform1, &box, transform2, &m_boxboxCache); - g_draw.DrawSolidPolygon(xf1, box.vertices, box.count, box.radius, color1); - g_draw.DrawSolidPolygon(xf2, box.vertices, box.count, box.radius, color2); + g_draw.DrawSolidPolygon(transform1, box.vertices, box.count, box.radius, color1); + g_draw.DrawSolidPolygon(transform2, box.vertices, box.count, box.radius, color2); - DrawManifold(&m, xf1.p, xf2.p); + DrawManifold(&m, transform1.p, transform2.p); offset = b2Add(offset, increment); } @@ -2584,16 +2581,16 @@ class Manifold : public Sample float h = 0.5f - m_round; b2Polygon rox = b2MakeRoundedBox(h, h, m_round); - b2Transform xf1 = {offset, b2Rot_identity}; - b2Transform xf2 = {b2Add(m_transform.p, offset), m_transform.q}; - // b2Transform xf2 = {b2Add({0.0f, -0.1f}, offset), {0.0f, 1.0f}}; + b2Transform transform1 = {offset, b2Rot_identity}; + b2Transform transform2 = {b2Add(m_transform.p, offset), m_transform.q}; + // b2Transform transform2 = {b2Add({0.0f, -0.1f}, offset), {0.0f, 1.0f}}; - b2Manifold m = b2CollidePolygons(&box, xf1, &rox, xf2, &m_boxroxCache); + b2Manifold m = b2CollidePolygons(&box, transform1, &rox, transform2, &m_boxroxCache); - g_draw.DrawSolidPolygon(xf1, box.vertices, box.count, box.radius, color1); - g_draw.DrawSolidPolygon(xf2, rox.vertices, rox.count, rox.radius, color2); + g_draw.DrawSolidPolygon(transform1, box.vertices, box.count, box.radius, color1); + g_draw.DrawSolidPolygon(transform2, rox.vertices, rox.count, rox.radius, color2); - DrawManifold(&m, xf1.p, xf2.p); + DrawManifold(&m, transform1.p, transform2.p); offset = b2Add(offset, increment); } @@ -2603,17 +2600,17 @@ class Manifold : public Sample float h = 0.5f - m_round; b2Polygon rox = b2MakeRoundedBox(h, h, m_round); - b2Transform xf1 = {offset, b2Rot_identity}; - b2Transform xf2 = {b2Add(m_transform.p, offset), m_transform.q}; - // b2Transform xf1 = {{6.48024225f, 2.07872653f}, {-0.938356698f, 0.345668465f}}; - // b2Transform xf2 = {{5.52862263f, 2.51146317f}, {-0.859374702f, -0.511346340f}}; + b2Transform transform1 = {offset, b2Rot_identity}; + b2Transform transform2 = {b2Add(m_transform.p, offset), m_transform.q}; + // b2Transform transform1 = {{6.48024225f, 2.07872653f}, {-0.938356698f, 0.345668465f}}; + // b2Transform transform2 = {{5.52862263f, 2.51146317f}, {-0.859374702f, -0.511346340f}}; - b2Manifold m = b2CollidePolygons(&rox, xf1, &rox, xf2, &m_roxroxCache); + b2Manifold m = b2CollidePolygons(&rox, transform1, &rox, transform2, &m_roxroxCache); - g_draw.DrawSolidPolygon(xf1, rox.vertices, rox.count, rox.radius, color1); - g_draw.DrawSolidPolygon(xf2, rox.vertices, rox.count, rox.radius, color2); + g_draw.DrawSolidPolygon(transform1, rox.vertices, rox.count, rox.radius, color1); + g_draw.DrawSolidPolygon(transform2, rox.vertices, rox.count, rox.radius, color2); - DrawManifold(&m, xf1.p, xf2.p); + DrawManifold(&m, transform1.p, transform2.p); offset = b2Add(offset, increment); } @@ -2624,18 +2621,18 @@ class Manifold : public Sample float h = 0.5f - m_round; b2Polygon rox = b2MakeRoundedBox(h, h, m_round); - b2Transform xf1 = {offset, b2Rot_identity}; - b2Transform xf2 = {b2Add(m_transform.p, offset), m_transform.q}; - // b2Transform xf2 = {b2Add({-1.44583416f, 0.397352695f}, offset), m_transform.q}; + b2Transform transform1 = {offset, b2Rot_identity}; + b2Transform transform2 = {b2Add(m_transform.p, offset), m_transform.q}; + // b2Transform transform2 = {b2Add({-1.44583416f, 0.397352695f}, offset), m_transform.q}; - b2Manifold m = b2CollideSegmentAndPolygon(&segment, xf1, &rox, xf2, &m_segroxCache); + b2Manifold m = b2CollideSegmentAndPolygon(&segment, transform1, &rox, transform2, &m_segroxCache); - b2Vec2 p1 = b2TransformPoint(xf1, segment.point1); - b2Vec2 p2 = b2TransformPoint(xf1, segment.point2); + b2Vec2 p1 = b2TransformPoint(transform1, segment.point1); + b2Vec2 p2 = b2TransformPoint(transform1, segment.point2); g_draw.DrawSegment(p1, p2, color1); - g_draw.DrawSolidPolygon(xf2, rox.vertices, rox.count, rox.radius, color2); + g_draw.DrawSolidPolygon(transform2, rox.vertices, rox.count, rox.radius, color2); - DrawManifold(&m, xf1.p, xf2.p); + DrawManifold(&m, transform1.p, transform2.p); offset = b2Add(offset, increment); } @@ -2644,16 +2641,16 @@ class Manifold : public Sample { b2Polygon wox = b2MakePolygon(&m_wedge, m_round); - b2Transform xf1 = {offset, b2Rot_identity}; - b2Transform xf2 = {b2Add(m_transform.p, offset), m_transform.q}; - // b2Transform xf2 = {b2Add({0.0f, -0.1f}, offset), {0.0f, 1.0f}}; + b2Transform transform1 = {offset, b2Rot_identity}; + b2Transform transform2 = {b2Add(m_transform.p, offset), m_transform.q}; + // b2Transform transform2 = {b2Add({0.0f, -0.1f}, offset), {0.0f, 1.0f}}; - b2Manifold m = b2CollidePolygons(&wox, xf1, &wox, xf2, &m_woxwoxCache); + b2Manifold m = b2CollidePolygons(&wox, transform1, &wox, transform2, &m_woxwoxCache); - g_draw.DrawSolidPolygon(xf1, wox.vertices, wox.count, wox.radius, color1); - g_draw.DrawSolidPolygon(xf2, wox.vertices, wox.count, wox.radius, color2); + g_draw.DrawSolidPolygon(transform1, wox.vertices, wox.count, wox.radius, color1); + g_draw.DrawSolidPolygon(transform2, wox.vertices, wox.count, wox.radius, color2); - DrawManifold(&m, xf1.p, xf2.p); + DrawManifold(&m, transform1.p, transform2.p); offset = b2Add(offset, increment); } @@ -2665,21 +2662,21 @@ class Manifold : public Sample b2SmoothSegment segment = {{2.0f, 1.0f}, {{1.0f, 1.0f}, {-1.0f, 0.0f}}, {-2.0f, 0.0f}, -1}; b2Circle circle = {{0.0f, 0.0f}, 0.5f}; - b2Transform xf1 = {offset, b2Rot_identity}; - b2Transform xf2 = {b2Add(m_transform.p, offset), m_transform.q}; + b2Transform transform1 = {offset, b2Rot_identity}; + b2Transform transform2 = {b2Add(m_transform.p, offset), m_transform.q}; - b2Manifold m = b2CollideSmoothSegmentAndCircle(&segment, xf1, &circle, xf2); + b2Manifold m = b2CollideSmoothSegmentAndCircle(&segment, transform1, &circle, transform2); - b2Vec2 g1 = b2TransformPoint(xf1, segment.ghost1); - b2Vec2 g2 = b2TransformPoint(xf1, segment.ghost2); - b2Vec2 p1 = b2TransformPoint(xf1, segment.segment.point1); - b2Vec2 p2 = b2TransformPoint(xf1, segment.segment.point2); + b2Vec2 g1 = b2TransformPoint(transform1, segment.ghost1); + b2Vec2 g2 = b2TransformPoint(transform1, segment.ghost2); + b2Vec2 p1 = b2TransformPoint(transform1, segment.segment.point1); + b2Vec2 p2 = b2TransformPoint(transform1, segment.segment.point2); g_draw.DrawSegment(g1, p1, b2_colorLightGray); g_draw.DrawSegment(p1, p2, color1); g_draw.DrawSegment(p2, g2, b2_colorLightGray); - g_draw.DrawSolidCircle(xf2, circle.center, circle.radius, color2); + g_draw.DrawSolidCircle(transform2, circle.center, circle.radius, color2); - DrawManifold(&m, xf1.p, xf2.p); + DrawManifold(&m, transform1.p, transform2.p); offset.x += 2.0f * increment.x; } @@ -2695,17 +2692,17 @@ class Manifold : public Sample float h = 0.5f - m_round; b2Polygon rox = b2MakeRoundedBox(h, h, m_round); - b2Transform xf1 = {offset, b2Rot_identity}; - b2Transform xf2 = {b2Add(m_transform.p, offset), m_transform.q}; + b2Transform transform1 = {offset, b2Rot_identity}; + b2Transform transform2 = {b2Add(m_transform.p, offset), m_transform.q}; - b2Manifold m1 = b2CollideSmoothSegmentAndPolygon(&segment1, xf1, &rox, xf2, &m_smgroxCache1); - b2Manifold m2 = b2CollideSmoothSegmentAndPolygon(&segment2, xf1, &rox, xf2, &m_smgroxCache2); + b2Manifold m1 = b2CollideSmoothSegmentAndPolygon(&segment1, transform1, &rox, transform2, &m_smgroxCache1); + b2Manifold m2 = b2CollideSmoothSegmentAndPolygon(&segment2, transform1, &rox, transform2, &m_smgroxCache2); { - b2Vec2 g1 = b2TransformPoint(xf1, segment1.ghost1); - b2Vec2 g2 = b2TransformPoint(xf1, segment1.ghost2); - b2Vec2 p1 = b2TransformPoint(xf1, segment1.segment.point1); - b2Vec2 p2 = b2TransformPoint(xf1, segment1.segment.point2); + b2Vec2 g1 = b2TransformPoint(transform1, segment1.ghost1); + b2Vec2 g2 = b2TransformPoint(transform1, segment1.ghost2); + b2Vec2 p1 = b2TransformPoint(transform1, segment1.segment.point1); + b2Vec2 p2 = b2TransformPoint(transform1, segment1.segment.point2); g_draw.DrawSegment(p1, p2, color1); g_draw.DrawPoint(p1, 4.0f, color1); g_draw.DrawPoint(p2, 4.0f, color1); @@ -2713,21 +2710,21 @@ class Manifold : public Sample } { - b2Vec2 g1 = b2TransformPoint(xf1, segment2.ghost1); - b2Vec2 g2 = b2TransformPoint(xf1, segment2.ghost2); - b2Vec2 p1 = b2TransformPoint(xf1, segment2.segment.point1); - b2Vec2 p2 = b2TransformPoint(xf1, segment2.segment.point2); + b2Vec2 g1 = b2TransformPoint(transform1, segment2.ghost1); + b2Vec2 g2 = b2TransformPoint(transform1, segment2.ghost2); + b2Vec2 p1 = b2TransformPoint(transform1, segment2.segment.point1); + b2Vec2 p2 = b2TransformPoint(transform1, segment2.segment.point2); g_draw.DrawSegment(g1, p1, b2_colorLightGray); g_draw.DrawSegment(p1, p2, color1); g_draw.DrawPoint(p1, 4.0f, color1); g_draw.DrawPoint(p2, 4.0f, color1); } - g_draw.DrawSolidPolygon(xf2, rox.vertices, rox.count, rox.radius, color2); - g_draw.DrawPoint(b2TransformPoint(xf2, rox.centroid), 5.0f, b2_colorGainsboro); + g_draw.DrawSolidPolygon(transform2, rox.vertices, rox.count, rox.radius, color2); + g_draw.DrawPoint(b2TransformPoint(transform2, rox.centroid), 5.0f, b2_colorGainsboro); - DrawManifold(&m1, xf1.p, xf2.p); - DrawManifold(&m2, xf1.p, xf2.p); + DrawManifold(&m1, transform1.p, transform2.p); + DrawManifold(&m2, transform1.p, transform2.p); offset.x += 2.0f * increment.x; } @@ -2738,17 +2735,17 @@ class Manifold : public Sample b2SmoothSegment segment2 = {{3.0f, 1.0f}, {{2.0f, 1.0f}, {1.0f, 1.0f}}, {-1.0f, 0.0f}, -1}; b2Capsule capsule = {{-0.5f, 0.0f}, {0.5f, 0.0}, 0.25f}; - b2Transform xf1 = {offset, b2Rot_identity}; - b2Transform xf2 = {b2Add(m_transform.p, offset), m_transform.q}; + b2Transform transform1 = {offset, b2Rot_identity}; + b2Transform transform2 = {b2Add(m_transform.p, offset), m_transform.q}; - b2Manifold m1 = b2CollideSmoothSegmentAndCapsule(&segment1, xf1, &capsule, xf2, &m_smgcapCache1); - b2Manifold m2 = b2CollideSmoothSegmentAndCapsule(&segment2, xf1, &capsule, xf2, &m_smgcapCache2); + b2Manifold m1 = b2CollideSmoothSegmentAndCapsule(&segment1, transform1, &capsule, transform2, &m_smgcapCache1); + b2Manifold m2 = b2CollideSmoothSegmentAndCapsule(&segment2, transform1, &capsule, transform2, &m_smgcapCache2); { - b2Vec2 g1 = b2TransformPoint(xf1, segment1.ghost1); - b2Vec2 g2 = b2TransformPoint(xf1, segment1.ghost2); - b2Vec2 p1 = b2TransformPoint(xf1, segment1.segment.point1); - b2Vec2 p2 = b2TransformPoint(xf1, segment1.segment.point2); + b2Vec2 g1 = b2TransformPoint(transform1, segment1.ghost1); + b2Vec2 g2 = b2TransformPoint(transform1, segment1.ghost2); + b2Vec2 p1 = b2TransformPoint(transform1, segment1.segment.point1); + b2Vec2 p2 = b2TransformPoint(transform1, segment1.segment.point2); // g_draw.DrawSegment(g1, p1, b2_colorLightGray); g_draw.DrawSegment(p1, p2, color1); g_draw.DrawPoint(p1, 4.0f, color1); @@ -2757,10 +2754,10 @@ class Manifold : public Sample } { - b2Vec2 g1 = b2TransformPoint(xf1, segment2.ghost1); - b2Vec2 g2 = b2TransformPoint(xf1, segment2.ghost2); - b2Vec2 p1 = b2TransformPoint(xf1, segment2.segment.point1); - b2Vec2 p2 = b2TransformPoint(xf1, segment2.segment.point2); + b2Vec2 g1 = b2TransformPoint(transform1, segment2.ghost1); + b2Vec2 g2 = b2TransformPoint(transform1, segment2.ghost2); + b2Vec2 p1 = b2TransformPoint(transform1, segment2.segment.point1); + b2Vec2 p2 = b2TransformPoint(transform1, segment2.segment.point2); g_draw.DrawSegment(g1, p1, b2_colorLightGray); g_draw.DrawSegment(p1, p2, color1); g_draw.DrawPoint(p1, 4.0f, color1); @@ -2768,14 +2765,14 @@ class Manifold : public Sample // g_draw.DrawSegment(p2, g2, b2_colorLightGray); } - b2Vec2 p1 = b2TransformPoint(xf2, capsule.center1); - b2Vec2 p2 = b2TransformPoint(xf2, capsule.center2); + b2Vec2 p1 = b2TransformPoint(transform2, capsule.center1); + b2Vec2 p2 = b2TransformPoint(transform2, capsule.center2); g_draw.DrawSolidCapsule(p1, p2, capsule.radius, color2); g_draw.DrawPoint(b2Lerp(p1, p2, 0.5f), 5.0f, b2_colorGainsboro); - DrawManifold(&m1, xf1.p, xf2.p); - DrawManifold(&m2, xf1.p, xf2.p); + DrawManifold(&m1, transform1.p, transform2.p); + DrawManifold(&m2, transform1.p, transform2.p); offset.x += 2.0f * increment.x; } @@ -3043,14 +3040,14 @@ class SmoothManifold : public Sample b2HexColor color1 = b2_colorYellow; b2HexColor color2 = b2_colorMagenta; - b2Transform xf1 = b2Transform_identity; - b2Transform xf2 = m_transform; + b2Transform transform1 = b2Transform_identity; + b2Transform transform2 = m_transform; for (int i = 0; i < m_count; ++i) { const b2SmoothSegment* segment = m_segments + i; - b2Vec2 p1 = b2TransformPoint(xf1, segment->segment.point1); - b2Vec2 p2 = b2TransformPoint(xf1, segment->segment.point2); + b2Vec2 p1 = b2TransformPoint(transform1, segment->segment.point1); + b2Vec2 p2 = b2TransformPoint(transform1, segment->segment.point2); g_draw.DrawSegment(p1, p2, color1); g_draw.DrawPoint(p1, 4.0f, color1); } @@ -3059,12 +3056,12 @@ class SmoothManifold : public Sample if (m_shapeType == e_circleShape) { b2Circle circle = {{0.0f, 0.0f}, 0.5f}; - g_draw.DrawSolidCircle(xf2, circle.center, circle.radius, color2); + g_draw.DrawSolidCircle(transform2, circle.center, circle.radius, color2); for (int i = 0; i < m_count; ++i) { const b2SmoothSegment* segment = m_segments + i; - b2Manifold m = b2CollideSmoothSegmentAndCircle(segment, xf1, &circle, xf2); + b2Manifold m = b2CollideSmoothSegmentAndCircle(segment, transform1, &circle, transform2); DrawManifold(&m); } } @@ -3072,13 +3069,13 @@ class SmoothManifold : public Sample { float h = 0.5f - m_round; b2Polygon rox = b2MakeRoundedBox(h, h, m_round); - g_draw.DrawSolidPolygon(xf2, rox.vertices, rox.count, rox.radius, color2); + g_draw.DrawSolidPolygon(transform2, rox.vertices, rox.count, rox.radius, color2); for (int i = 0; i < m_count; ++i) { const b2SmoothSegment* segment = m_segments + i; b2DistanceCache cache = {}; - b2Manifold m = b2CollideSmoothSegmentAndPolygon(segment, xf1, &rox, xf2, &cache); + b2Manifold m = b2CollideSmoothSegmentAndPolygon(segment, transform1, &rox, transform2, &cache); DrawManifold(&m); } } @@ -3128,7 +3125,7 @@ class ShapeCast : public Sample g_camera.m_zoom = 25.0f * 0.2f; } -#if 1 +#if 0 // box swept against a triangle m_vAs[0] = {-0.5f, 1.0f}; m_vAs[1] = {0.5f, 1.0f}; @@ -3148,6 +3145,25 @@ class ShapeCast : public Sample m_transformB.p = {-4.0f, 0.0f}; m_transformB.q = b2Rot_identity; m_translationB = {8.0f, 0.0f}; +#elif 1 + // box swept against a segment + m_vAs[0] = {-2.0f, 0.0f}; + m_vAs[1] = {2.0f, 0.0f}; + m_countA = 2; + m_radiusA = 0.0f; + + m_vBs[0] = {-0.25f, -0.25f}; + m_vBs[1] = {0.25f, -0.25f}; + m_vBs[2] = {0.25f, 0.25f}; + m_vBs[3] = {-0.25f, 0.25f}; + m_countB = 4; + m_radiusB = 0.25f; + + m_transformA.p = {0.0f, 0.0}; + m_transformA.q = b2MakeRot(0.25f * b2_pi); + m_transformB.p = {-8.0f, 0.0f}; + m_transformB.q = b2Rot_identity; + m_translationB = {8.0f, 0.0f}; #elif 0 // A point swept against a box m_vAs[0] = {-0.5f, -0.5f}; @@ -3274,16 +3290,16 @@ class ShapeCast : public Sample { if (m_radiusA > 0.0f) { - g_draw.DrawCircle(vertices[0], m_radiusA, b2_colorGray90); + g_draw.DrawCircle(vertices[0], m_radiusA, b2_colorGray9); } else { - g_draw.DrawPoint(vertices[0], 5.0f, b2_colorGray90); + g_draw.DrawPoint(vertices[0], 5.0f, b2_colorGray9); } } else { - g_draw.DrawPolygon(vertices, m_countA, b2_colorGray90); + g_draw.DrawSolidPolygon(b2Transform_identity, vertices, m_countA, m_radiusA, b2_colorGray9); } for (int32_t i = 0; i < m_countB; ++i) @@ -3304,7 +3320,7 @@ class ShapeCast : public Sample } else { - g_draw.DrawPolygon(vertices, m_countB, b2_colorGreen); + g_draw.DrawSolidPolygon(b2Transform_identity, vertices, m_countB, m_radiusB, b2_colorGreen); } for (int32_t i = 0; i < m_countB; ++i) @@ -3325,7 +3341,7 @@ class ShapeCast : public Sample } else { - g_draw.DrawPolygon(vertices, m_countB, b2_colorOrange); + g_draw.DrawSolidPolygon(b2Transform_identity, vertices, m_countB, m_radiusB, b2_colorOrange); } if (output.hit) diff --git a/samples/sample_continuous.cpp b/samples/sample_continuous.cpp index 4937c549..893c20b4 100644 --- a/samples/sample_continuous.cpp +++ b/samples/sample_continuous.cpp @@ -6,7 +6,6 @@ #include "settings.h" #include "box2d/box2d.h" -#include "box2d/geometry.h" #include "box2d/math_functions.h" #include diff --git a/samples/sample_events.cpp b/samples/sample_events.cpp index ad0bbbb6..65f99b9b 100644 --- a/samples/sample_events.cpp +++ b/samples/sample_events.cpp @@ -8,8 +8,6 @@ #include "settings.h" #include "box2d/box2d.h" -#include "box2d/color.h" -#include "box2d/geometry.h" #include "box2d/math_functions.h" #include diff --git a/samples/sample_geometry.cpp b/samples/sample_geometry.cpp index 3ef2c73b..b290accc 100644 --- a/samples/sample_geometry.cpp +++ b/samples/sample_geometry.cpp @@ -5,7 +5,6 @@ #include "sample.h" #include "settings.h" -#include "box2d/geometry.h" #include "box2d/math_functions.h" #include diff --git a/samples/sample_joints.cpp b/samples/sample_joints.cpp index 4e1afb34..05869d89 100644 --- a/samples/sample_joints.cpp +++ b/samples/sample_joints.cpp @@ -10,8 +10,6 @@ #include "settings.h" #include "box2d/box2d.h" -#include "box2d/color.h" -#include "box2d/geometry.h" #include "box2d/math_functions.h" #include @@ -1475,7 +1473,7 @@ class FixedRotation : public Sample static int sampleFixedRotation = RegisterSample("Joints", "Fixed Rotation", FixedRotation::Create); // This sample shows how to break joints when the internal reaction force becomes large. -class ReactionForce : public Sample +class BreakableJoint : public Sample { public: enum @@ -1483,7 +1481,7 @@ class ReactionForce : public Sample e_count = 6 }; - explicit ReactionForce(Settings& settings) + explicit BreakableJoint(Settings& settings) : Sample(settings) { if (settings.restart == false) @@ -1704,7 +1702,7 @@ class ReactionForce : public Sample static Sample* Create(Settings& settings) { - return new ReactionForce(settings); + return new BreakableJoint(settings); } @@ -1712,7 +1710,7 @@ class ReactionForce : public Sample float m_breakForce; }; -static int sampleReactionForce = RegisterSample("Joints", "Reaction Force", ReactionForce::Create); +static int sampleBreakableJoint = RegisterSample("Joints", "Breakable", BreakableJoint::Create); // This shows how you can implement a constraint outside of Box2D class UserConstraint : public Sample diff --git a/samples/sample_robustness.cpp b/samples/sample_robustness.cpp index f3bcf822..0991d499 100644 --- a/samples/sample_robustness.cpp +++ b/samples/sample_robustness.cpp @@ -6,7 +6,6 @@ #include "settings.h" #include "box2d/box2d.h" -#include "box2d/geometry.h" #include #include diff --git a/samples/sample_shapes.cpp b/samples/sample_shapes.cpp index 19fe0f2e..3662d7c4 100644 --- a/samples/sample_shapes.cpp +++ b/samples/sample_shapes.cpp @@ -6,8 +6,6 @@ #include "settings.h" #include "box2d/box2d.h" -#include "box2d/color.h" -#include "box2d/geometry.h" #include "box2d/math_functions.h" #include diff --git a/samples/sample_stacking.cpp b/samples/sample_stacking.cpp index a3deabb2..bbc2196c 100644 --- a/samples/sample_stacking.cpp +++ b/samples/sample_stacking.cpp @@ -6,7 +6,6 @@ #include "settings.h" #include "box2d/box2d.h" -#include "box2d/geometry.h" #include "box2d/math_functions.h" #include diff --git a/samples/sample_world.cpp b/samples/sample_world.cpp index 79868e1c..e582d88a 100644 --- a/samples/sample_world.cpp +++ b/samples/sample_world.cpp @@ -9,8 +9,6 @@ #include "settings.h" #include "box2d/box2d.h" -#include "box2d/color.h" -#include "box2d/geometry.h" #include "box2d/math_functions.h" #include diff --git a/samples/settings.h b/samples/settings.h index 90cc952e..47524664 100644 --- a/samples/settings.h +++ b/samples/settings.h @@ -12,6 +12,10 @@ struct Settings int sampleIndex = 0; int windowWidth = 1920; int windowHeight = 1080; + //int windowWidth = 1280; + //int windowHeight = 720; + //int windowWidth = 800; + //int windowHeight = 600; float hertz = 60.0f; int subStepCount = 4; int workerCount = 1; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0c0d7bf5..5078d148 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,8 +7,6 @@ set(BOX2D_SOURCE_FILES array.h bitset.c bitset.h - block_allocator.c - block_allocator.h block_array.c block_array.h body.c @@ -53,7 +51,6 @@ set(BOX2D_SOURCE_FILES table.h timer.c types.c - util.h weld_joint.c wheel_joint.c world.c @@ -61,22 +58,11 @@ set(BOX2D_SOURCE_FILES ) set(BOX2D_API_FILES - ../include/box2d/api.h + ../include/box2d/base.h ../include/box2d/box2d.h - ../include/box2d/callbacks.h - ../include/box2d/color.h ../include/box2d/collision.h - ../include/box2d/debug_draw.h - ../include/box2d/distance.h - ../include/box2d/dynamic_tree.h - ../include/box2d/event_types.h - ../include/box2d/geometry.h ../include/box2d/id.h - ../include/box2d/joint_types.h ../include/box2d/math_functions.h - ../include/box2d/math_cpp.h - ../include/box2d/math_types.h - ../include/box2d/timer.h ../include/box2d/types.h ) diff --git a/src/aabb.c b/src/aabb.c index 6d0befe7..a887a4a0 100644 --- a/src/aabb.c +++ b/src/aabb.c @@ -3,7 +3,6 @@ #include "aabb.h" -#include "box2d/geometry.h" #include "box2d/math_functions.h" #include diff --git a/src/aabb.h b/src/aabb.h index 0c43020e..30671d6f 100644 --- a/src/aabb.h +++ b/src/aabb.h @@ -3,7 +3,7 @@ #pragma once -#include "box2d/geometry.h" +#include "box2d/types.h" // Ray cast an AABB b2CastOutput b2AABB_RayCast(b2AABB a, b2Vec2 p1, b2Vec2 p2); diff --git a/src/allocate.c b/src/allocate.c index ded0463a..2c6c7f5e 100644 --- a/src/allocate.c +++ b/src/allocate.c @@ -5,7 +5,7 @@ #include "core.h" -#include "box2d/api.h" +#include "box2d/base.h" #if defined(B2_COMPILER_MSVC) #define _CRTDBG_MAP_ALLOC diff --git a/src/block_allocator.c b/src/block_allocator.c deleted file mode 100644 index 3e5f8ede..00000000 --- a/src/block_allocator.c +++ /dev/null @@ -1,219 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Erin Catto -// SPDX-License-Identifier: MIT - -#include "block_allocator.h" -#include "allocate.h" -#include "array.h" -#include "core.h" -#include "ctz.h" - -#include -#include -#include - -#define b2_targetChunkSize (1 << 18) -#define b2_maxBlockSize (1 << b2_maxBlockPower) - -// This is a helper struct for building a linked list of memory blocks -typedef struct b2Block -{ - struct b2Block* next; -} b2Block; - -// This holds a contiguous block list from a single malloc call. The blocks are all of a the same size (a power of 2). -// When a chunk is allocated, its blocks are installed in the block allocator free list for the specific power of 2 size. -typedef struct b2Chunk -{ - b2Block* blockList; - - // size of each block - int blockSize; - - // size of the entire list - int totalSize; -} b2Chunk; - -b2BlockAllocator b2CreateBlockAllocator(void) -{ - _Static_assert(b2_blockPowerCount == b2_maxBlockPower - b2_minBlockPower + 1, "wrong size"); - _Static_assert(b2_targetChunkSize <= (1 << b2_maxBlockPower), "wrong size"); - _Static_assert(b2_targetChunkSize >= (1 << b2_minBlockPower), "wrong size"); - - b2BlockAllocator allocator = {0}; - allocator.chunkArray = b2CreateArray(sizeof(b2Chunk), 128); - memset(allocator.chunkArray, 0, b2Array(allocator.chunkArray).capacity * sizeof(b2Chunk)); - return allocator; -} - -void b2DestroyBlockAllocator(b2BlockAllocator* allocator) -{ - int chunkCount = b2Array(allocator->chunkArray).count; - for (int32_t i = 0; i < chunkCount; ++i) - { - b2Chunk* chunk = allocator->chunkArray + i; - b2Free(chunk->blockList, chunk->totalSize); - } - - b2DestroyArray(allocator->chunkArray, sizeof(b2Chunk)); -} - -// todo should probably get rid of this allocator -// large world sample: -// - block allocator 245 MB -// - system allocator 206 MB -// no significant performance difference -#define B2_USE_SYSTEM_ALLOC 1 - -#if B2_USE_SYSTEM_ALLOC -void* b2AllocBlock(b2BlockAllocator* allocator, int size) -{ - ((void)allocator); - return b2Alloc(size); -} - -void b2FreeBlock(b2BlockAllocator* allocator, void* memory, int size) -{ - ((void)allocator); - b2Free(memory, size); -} - -#else - -void* b2AllocBlock(b2BlockAllocator* allocator, int size) -{ - B2_ASSERT(size >= 0); - - if (size == 0) - { - return NULL; - } - - if (size > b2_maxBlockSize) - { - B2_ASSERT(false); - return NULL; - } - - int power = b2BoundingPowerOf2(size); - power = power < b2_minBlockPower ? b2_minBlockPower : power; - - int index = power - b2_minBlockPower; - B2_ASSERT(0 <= index && index < b2_blockPowerCount); - - if (allocator->freeLists[index] != NULL) - { - b2Block* block = allocator->freeLists[index]; - allocator->freeLists[index] = block->next; - return block; - } - - // free list is empty, allocate more blocks for this power - b2Chunk chunk; - chunk.blockSize = (1 << power); - // if the block size is very large then the chunk will hold a single block - if (chunk.blockSize > b2_targetChunkSize) - { - chunk.totalSize = chunk.blockSize; - } - else - { - chunk.totalSize = b2_targetChunkSize; - } - chunk.blockList = b2Alloc(chunk.totalSize); - -#if B2_DEBUG - memset(chunk.blockList, 0xcd, chunk.totalSize); -#endif - - // build linked list - int blockCount = chunk.totalSize / chunk.blockSize; - B2_ASSERT(blockCount * chunk.blockSize == chunk.totalSize); - for (int i = 0; i < blockCount - 1; ++i) - { - b2Block* block = (b2Block*)((int8_t*)chunk.blockList + chunk.blockSize * i); - b2Block* next = (b2Block*)((int8_t*)chunk.blockList + chunk.blockSize * (i + 1)); - block->next = next; - } - b2Block* last = (b2Block*)((int8_t*)chunk.blockList + chunk.blockSize * (blockCount - 1)); - last->next = NULL; - - allocator->freeLists[index] = chunk.blockList->next; - - b2Array_Push(allocator->chunkArray, chunk); - - return chunk.blockList; -} - -void b2FreeBlock(b2BlockAllocator* allocator, void* memory, int size) -{ - B2_ASSERT(size >= 0); - - if (size == 0) - { - return; - } - - B2_ASSERT(size <= b2_maxBlockSize); - - int power = b2BoundingPowerOf2(size); - power = power < b2_minBlockPower ? b2_minBlockPower : power; - - int index = power - b2_minBlockPower; - B2_ASSERT(0 <= index && index < b2_blockPowerCount); - -#if B2_VALIDATE - // verify the memory address and size are valid - int blockSize = (1 << power); - bool found = false; - int chunkCount = b2Array(allocator->chunkArray).count; - for (int i = 0; i < chunkCount; ++i) - { - b2Chunk* chunk = allocator->chunkArray + i; - if (chunk->blockSize != blockSize) - { - // this chunk is not the right size, so make sure it does not overlap the freed memory - B2_ASSERT((int8_t*)memory + blockSize <= (int8_t*)chunk->blockList || - (int8_t*)chunk->blockList + chunk->totalSize <= (int8_t*)memory); - } - else - { - // does the freed memory overlap this chunk's block list? - if ((int8_t*)chunk->blockList <= (int8_t*)memory && - (int8_t*)memory + blockSize <= (int8_t*)chunk->blockList + chunk->totalSize) - { - found = true; - } - } - } - - B2_ASSERT(found); - memset(memory, 0xfd, blockSize); -#endif - - // add to free list - b2Block* block = memory; - block->next = allocator->freeLists[index]; - allocator->freeLists[index] = block; -} -#endif - -bool b2ValidateBlockAllocator(b2BlockAllocator* allocator) -{ - for (int i = 0; i < b2_blockPowerCount; ++i) - { - int count = 0; - b2Block* block = allocator->freeLists[i]; - while (block != NULL) - { - block = block->next; - count += 1; - - if (count == 10000000) - { - return false; - } - } - } - - return true; -} diff --git a/src/block_allocator.h b/src/block_allocator.h deleted file mode 100644 index 267f6422..00000000 --- a/src/block_allocator.h +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Erin Catto -// SPDX-License-Identifier: MIT - -#pragma once - -#include - -#define b2_minBlockPower 8 -#define b2_maxBlockPower 30 -#define b2_blockPowerCount (b2_maxBlockPower - b2_minBlockPower + 1) - -// todo current benchmarking shows very small gains. like 0.1% but large memory overhead - -// This is a block allocator used for allocating objects that persist for more than one time step. Allocations -// rounded up to a power of 2. Free lists are maintained for a range of powers of two. Not thread-safe. -typedef struct b2BlockAllocator -{ - // Array of all the chunks of memory from malloc - struct b2Chunk* chunkArray; - - // List of free blocks, one list for each power of 2 - struct b2Block* freeLists[b2_blockPowerCount]; -} b2BlockAllocator; - -// Create an allocator suitable for allocating and freeing objects quickly. -// Does not return memory to the heap. -b2BlockAllocator b2CreateBlockAllocator(void); - -// Destroy a block allocator instance -void b2DestroyBlockAllocator(b2BlockAllocator* allocator); - -// Allocate memory. This will use b2Alloc if the size is larger than b2_maxBlockSize. -// Allocates memory in power of two sizes, so the actual allocation may be larger than requested. -// Returns the memory pointer along with the actual capacity. -void* b2AllocBlock(b2BlockAllocator* allocator, int size); - -// Free memory. This will use b2Free if the size is larger than b2_maxBlockSize. -void b2FreeBlock(b2BlockAllocator* allocator, void* memory, int size); - -bool b2ValidateBlockAllocator(b2BlockAllocator* allocator); diff --git a/src/block_array.c b/src/block_array.c index a11a0383..17af59f6 100644 --- a/src/block_array.c +++ b/src/block_array.c @@ -3,7 +3,7 @@ #include "block_array.h" -#include "block_allocator.h" +#include "allocate.h" #include "body.h" #include "contact.h" #include "core.h" @@ -14,96 +14,96 @@ #define B2_INITIAL_CAPACITY 16 -b2BodySimArray b2CreateBodySimArray(b2BlockAllocator* allocator, int capacity) +b2BodySimArray b2CreateBodySimArray(int capacity) { if (capacity > 0) { - return (b2BodySimArray){b2AllocBlock(allocator, capacity * sizeof(b2BodySim)), 0, capacity}; + return (b2BodySimArray){b2Alloc(capacity * sizeof(b2BodySim)), 0, capacity}; } return (b2BodySimArray){0}; } -b2BodyStateArray b2CreateBodyStateArray(b2BlockAllocator* allocator, int capacity) +b2BodyStateArray b2CreateBodyStateArray(int capacity) { if (capacity > 0) { - return (b2BodyStateArray){b2AllocBlock(allocator, capacity * sizeof(b2BodyState)), 0, capacity}; + return (b2BodyStateArray){b2Alloc(capacity * sizeof(b2BodyState)), 0, capacity}; } return (b2BodyStateArray){0}; } -b2ContactArray b2CreateContactArray(b2BlockAllocator* allocator, int capacity) +b2ContactArray b2CreateContactArray(int capacity) { if (capacity > 0) { - return (b2ContactArray){b2AllocBlock(allocator, capacity * sizeof(b2ContactSim)), 0, capacity}; + return (b2ContactArray){b2Alloc(capacity * sizeof(b2ContactSim)), 0, capacity}; } return (b2ContactArray){0}; } -b2JointArray b2CreateJointArray(b2BlockAllocator* allocator, int capacity) +b2JointArray b2CreateJointArray(int capacity) { if (capacity > 0) { - return (b2JointArray){b2AllocBlock(allocator, capacity * sizeof(b2JointSim)), 0, capacity}; + return (b2JointArray){b2Alloc(capacity * sizeof(b2JointSim)), 0, capacity}; } return (b2JointArray){0}; } -b2IslandArray b2CreateIslandArray(b2BlockAllocator* allocator, int capacity) +b2IslandArray b2CreateIslandArray(int capacity) { if (capacity > 0) { - return (b2IslandArray){b2AllocBlock(allocator, capacity * sizeof(b2IslandSim)), 0, capacity}; + return (b2IslandArray){b2Alloc(capacity * sizeof(b2IslandSim)), 0, capacity}; } return (b2IslandArray){0}; } -void b2DestroyBodySimArray(b2BlockAllocator* allocator, b2BodySimArray* array) +void b2DestroyBodySimArray(b2BodySimArray* array) { - b2FreeBlock(allocator, array->data, array->capacity * sizeof(b2BodySim)); + b2Free(array->data, array->capacity * sizeof(b2BodySim)); } -void b2DestroyBodyStateArray(b2BlockAllocator* allocator, b2BodyStateArray* array) +void b2DestroyBodyStateArray(b2BodyStateArray* array) { - b2FreeBlock(allocator, array->data, array->capacity * sizeof(b2BodyState)); + b2Free(array->data, array->capacity * sizeof(b2BodyState)); } -void b2DestroyContactArray(b2BlockAllocator* allocator, b2ContactArray* array) +void b2DestroyContactArray(b2ContactArray* array) { - b2FreeBlock(allocator, array->data, array->capacity * sizeof(b2ContactSim)); + b2Free(array->data, array->capacity * sizeof(b2ContactSim)); } -void b2DestroyJointArray(b2BlockAllocator* allocator, b2JointArray* array) +void b2DestroyJointArray(b2JointArray* array) { - b2FreeBlock(allocator, array->data, array->capacity * sizeof(b2JointSim)); + b2Free(array->data, array->capacity * sizeof(b2JointSim)); } -void b2DestroyIslandArray(b2BlockAllocator* allocator, b2IslandArray* array) +void b2DestroyIslandArray(b2IslandArray* array) { - b2FreeBlock(allocator, array->data, array->capacity * sizeof(b2IslandSim)); + b2Free(array->data, array->capacity * sizeof(b2IslandSim)); } -b2BodySim* b2AddBodySim(b2BlockAllocator* allocator, b2BodySimArray* array) +b2BodySim* b2AddBodySim(b2BodySimArray* array) { int elementSize = sizeof(b2BodySim); if (array->capacity == 0) { B2_ASSERT(array->count == 0); - array->data = b2AllocBlock(allocator, B2_INITIAL_CAPACITY * elementSize); + array->data = b2Alloc(B2_INITIAL_CAPACITY * elementSize); array->capacity = B2_INITIAL_CAPACITY; } else if (array->count == array->capacity) { int newCapacity = 2 * array->capacity; - b2BodySim* newElements = b2AllocBlock(allocator, newCapacity * elementSize); + b2BodySim* newElements = b2Alloc(newCapacity * elementSize); memcpy(newElements, array->data, array->capacity * elementSize); - b2FreeBlock(allocator, array->data, array->capacity * elementSize); + b2Free(array->data, array->capacity * elementSize); array->data = newElements; array->capacity = newCapacity; } @@ -113,21 +113,21 @@ b2BodySim* b2AddBodySim(b2BlockAllocator* allocator, b2BodySimArray* array) return element; } -b2BodyState* b2AddBodyState(b2BlockAllocator* allocator, b2BodyStateArray* array) +b2BodyState* b2AddBodyState(b2BodyStateArray* array) { int elementSize = sizeof(b2BodyState); if (array->capacity == 0) { B2_ASSERT(array->count == 0); - array->data = b2AllocBlock(allocator, B2_INITIAL_CAPACITY * elementSize); + array->data = b2Alloc(B2_INITIAL_CAPACITY * elementSize); array->capacity = B2_INITIAL_CAPACITY; } else if (array->count == array->capacity) { int newCapacity = 2 * array->capacity; - b2BodyState* newElements = b2AllocBlock(allocator, newCapacity * elementSize); + b2BodyState* newElements = b2Alloc(newCapacity * elementSize); memcpy(newElements, array->data, array->capacity * elementSize); - b2FreeBlock(allocator, array->data, array->capacity * elementSize); + b2Free(array->data, array->capacity * elementSize); array->data = newElements; array->capacity = newCapacity; } @@ -137,21 +137,21 @@ b2BodyState* b2AddBodyState(b2BlockAllocator* allocator, b2BodyStateArray* array return element; } -b2ContactSim* b2AddContact(b2BlockAllocator* allocator, b2ContactArray* array) +b2ContactSim* b2AddContact(b2ContactArray* array) { int elementSize = sizeof(b2ContactSim); if (array->capacity == 0) { B2_ASSERT(array->count == 0); - array->data = b2AllocBlock(allocator, B2_INITIAL_CAPACITY * elementSize); + array->data = b2Alloc(B2_INITIAL_CAPACITY * elementSize); array->capacity = B2_INITIAL_CAPACITY; } else if (array->count == array->capacity) { int newCapacity = 2 * array->capacity; - b2ContactSim* newElements = b2AllocBlock(allocator, newCapacity * elementSize); + b2ContactSim* newElements = b2Alloc(newCapacity * elementSize); memcpy(newElements, array->data, array->capacity * elementSize); - b2FreeBlock(allocator, array->data, array->capacity * elementSize); + b2Free(array->data, array->capacity * elementSize); array->data = newElements; array->capacity = newCapacity; } @@ -161,21 +161,21 @@ b2ContactSim* b2AddContact(b2BlockAllocator* allocator, b2ContactArray* array) return element; } -b2JointSim* b2AddJoint(b2BlockAllocator* allocator, b2JointArray* array) +b2JointSim* b2AddJoint(b2JointArray* array) { int elementSize = sizeof(b2JointSim); if (array->capacity == 0) { B2_ASSERT(array->count == 0); - array->data = b2AllocBlock(allocator, B2_INITIAL_CAPACITY * elementSize); + array->data = b2Alloc(B2_INITIAL_CAPACITY * elementSize); array->capacity = B2_INITIAL_CAPACITY; } else if (array->count == array->capacity) { int newCapacity = 2 * array->capacity; - b2JointSim* newElements = b2AllocBlock(allocator, newCapacity * elementSize); + b2JointSim* newElements = b2Alloc(newCapacity * elementSize); memcpy(newElements, array->data, array->capacity * elementSize); - b2FreeBlock(allocator, array->data, array->capacity * elementSize); + b2Free(array->data, array->capacity * elementSize); array->data = newElements; array->capacity = newCapacity; } @@ -185,21 +185,21 @@ b2JointSim* b2AddJoint(b2BlockAllocator* allocator, b2JointArray* array) return element; } -b2IslandSim* b2AddIsland(b2BlockAllocator* allocator, b2IslandArray* array) +b2IslandSim* b2AddIsland(b2IslandArray* array) { int elementSize = sizeof(b2IslandSim); if (array->capacity == 0) { B2_ASSERT(array->count == 0); - array->data = b2AllocBlock(allocator, B2_INITIAL_CAPACITY * elementSize); + array->data = b2Alloc(B2_INITIAL_CAPACITY * elementSize); array->capacity = B2_INITIAL_CAPACITY; } else if (array->count == array->capacity) { int newCapacity = 2 * array->capacity; - b2IslandSim* newElements = b2AllocBlock(allocator, newCapacity * elementSize); + b2IslandSim* newElements = b2Alloc(newCapacity * elementSize); memcpy(newElements, array->data, array->capacity * elementSize); - b2FreeBlock(allocator, array->data, array->capacity * elementSize); + b2Free(array->data, array->capacity * elementSize); array->data = newElements; array->capacity = newCapacity; } diff --git a/src/block_array.h b/src/block_array.h index b6153bf9..a4b31ca5 100644 --- a/src/block_array.h +++ b/src/block_array.h @@ -3,7 +3,6 @@ #pragma once -typedef struct b2BlockAllocator b2BlockAllocator; typedef struct b2BodySim b2BodySim; typedef struct b2BodyState b2BodyState; typedef struct b2ContactSim b2ContactSim; @@ -47,23 +46,23 @@ typedef struct b2JointArray // These provide a way to create an array with a specified capacity. If the capacity is not // known, you may use zero initialization. -b2BodySimArray b2CreateBodySimArray(b2BlockAllocator* allocator, int capacity); -b2BodyStateArray b2CreateBodyStateArray(b2BlockAllocator* allocator, int capacity); -b2ContactArray b2CreateContactArray(b2BlockAllocator* allocator, int capacity); -b2IslandArray b2CreateIslandArray(b2BlockAllocator* allocator, int capacity); -b2JointArray b2CreateJointArray(b2BlockAllocator* allocator, int capacity); +b2BodySimArray b2CreateBodySimArray(int capacity); +b2BodyStateArray b2CreateBodyStateArray(int capacity); +b2ContactArray b2CreateContactArray(int capacity); +b2IslandArray b2CreateIslandArray(int capacity); +b2JointArray b2CreateJointArray(int capacity); -void b2DestroyBodySimArray(b2BlockAllocator* allocator, b2BodySimArray* array); -void b2DestroyBodyStateArray(b2BlockAllocator* allocator, b2BodyStateArray* array); -void b2DestroyContactArray(b2BlockAllocator* allocator, b2ContactArray* array); -void b2DestroyIslandArray(b2BlockAllocator* allocator, b2IslandArray* array); -void b2DestroyJointArray(b2BlockAllocator* allocator, b2JointArray* array); +void b2DestroyBodySimArray(b2BodySimArray* array); +void b2DestroyBodyStateArray(b2BodyStateArray* array); +void b2DestroyContactArray(b2ContactArray* array); +void b2DestroyIslandArray(b2IslandArray* array); +void b2DestroyJointArray(b2JointArray* array); -b2BodySim* b2AddBodySim(b2BlockAllocator* allocator, b2BodySimArray* array); -b2BodyState* b2AddBodyState(b2BlockAllocator* allocator, b2BodyStateArray* array); -b2ContactSim* b2AddContact(b2BlockAllocator* allocator, b2ContactArray* array); -b2IslandSim* b2AddIsland(b2BlockAllocator* allocator, b2IslandArray* array); -b2JointSim* b2AddJoint(b2BlockAllocator* allocator, b2JointArray* array); +b2BodySim* b2AddBodySim(b2BodySimArray* array); +b2BodyState* b2AddBodyState(b2BodyStateArray* array); +b2ContactSim* b2AddContact(b2ContactArray* array); +b2IslandSim* b2AddIsland(b2IslandArray* array); +b2JointSim* b2AddJoint(b2JointArray* array); // Returns the index of the element moved into the empty slot (or B2_NULL_INDEX) // todo have these return the id directly? diff --git a/src/body.c b/src/body.c index acfb4f18..bba7b534 100644 --- a/src/body.c +++ b/src/body.c @@ -13,12 +13,10 @@ #include "joint.h" #include "shape.h" #include "solver_set.h" -#include "util.h" #include "world.h" // needed for dll export #include "box2d/box2d.h" -#include "box2d/event_types.h" #include "box2d/id.h" #include @@ -179,6 +177,7 @@ static void b2DestroyBodyContacts(b2World* world, b2Body* body, bool wakeBodies) b2BodyId b2CreateBody(b2WorldId worldId, const b2BodyDef* def) { + b2CheckDef(def); B2_ASSERT(b2Vec2_IsValid(def->position)); B2_ASSERT(b2IsValid(def->angle)); B2_ASSERT(b2Vec2_IsValid(def->linearVelocity)); @@ -234,7 +233,7 @@ b2BodyId b2CreateBody(b2WorldId worldId, const b2BodyDef* def) int bodyId = b2AllocId(&world->bodyIdPool); b2SolverSet* set = world->solverSetArray + setId; - b2BodySim* bodySim = b2AddBodySim(&world->blockAllocator, &set->sims); + b2BodySim* bodySim = b2AddBodySim(&set->sims); *bodySim = (b2BodySim){0}; bodySim->transform.p = def->position; bodySim->transform.q = b2MakeRot(def->angle); @@ -261,7 +260,7 @@ b2BodyId b2CreateBody(b2WorldId worldId, const b2BodyDef* def) if (setId == b2_awakeSet) { - b2BodyState* bodyState = b2AddBodyState(&world->blockAllocator, &set->states); + b2BodyState* bodyState = b2AddBodyState(&set->states); B2_ASSERT(((uintptr_t)bodyState & 0x1F) == 0); *bodyState = (b2BodyState){0}; @@ -726,7 +725,7 @@ void b2Body_SetTransform(b2BodyId bodyId, b2Vec2 position, float angle) // They body could be disabled if (shape->proxyKey != B2_NULL_INDEX) { - b2BroadPhase_MoveProxy(broadPhase, shape->proxyKey, fatAABB); + b2BroadPhase_MoveProxy(broadPhase, shape->proxyKey, fatAABB, shape->filter.maskBits); } } @@ -1130,7 +1129,7 @@ void b2Body_SetType(b2BodyId bodyId, b2BodyType type) while (shapeId != B2_NULL_INDEX) { b2Shape* shape = world->shapeArray + shapeId; - b2BufferMove(&world->broadPhase, shape->proxyKey); + b2BufferMove(&world->broadPhase, (b2MovedProxy){shape->proxyKey, shape->filter.maskBits}); shapeId = shape->nextShapeId; } } diff --git a/src/body.h b/src/body.h index fac3ec84..9916b1ef 100644 --- a/src/body.h +++ b/src/body.h @@ -3,8 +3,8 @@ #pragma once -#include "box2d/distance.h" #include "box2d/math_functions.h" +#include "box2d/types.h" typedef struct b2Polygon b2Polygon; typedef struct b2World b2World; diff --git a/src/broad_phase.c b/src/broad_phase.c index abae765b..ece81472 100644 --- a/src/broad_phase.c +++ b/src/broad_phase.c @@ -13,11 +13,8 @@ #include "contact.h" #include "core.h" #include "shape.h" -#include "util.h" #include "world.h" -#include "box2d/color.h" - #include #include #include @@ -40,7 +37,7 @@ void b2CreateBroadPhase(b2BroadPhase* bp) // TODO_ERIN initial size in b2WorldDef? bp->moveSet = b2CreateSet(16); - bp->moveArray = b2CreateArray(sizeof(int), 16); + bp->moveArray = b2CreateArray(sizeof(b2MovedProxy), 16); bp->moveResults = NULL; bp->movePairs = NULL; @@ -64,7 +61,7 @@ void b2DestroyBroadPhase(b2BroadPhase* bp) } b2DestroySet(&bp->moveSet); - b2DestroyArray(bp->moveArray, sizeof(int)); + b2DestroyArray(bp->moveArray, sizeof(b2MovedProxy)); b2DestroySet(&bp->pairSet); memset(bp, 0, sizeof(b2BroadPhase)); @@ -87,7 +84,7 @@ static inline void b2UnBufferMove(b2BroadPhase* bp, int proxyKey) int count = b2Array(bp->moveArray).count; for (int i = 0; i < count; ++i) { - if (bp->moveArray[i] == proxyKey) + if (bp->moveArray[i].proxyKey == proxyKey) { b2Array_RemoveSwap(bp->moveArray, i); break; @@ -96,7 +93,7 @@ static inline void b2UnBufferMove(b2BroadPhase* bp, int proxyKey) } } -int b2BroadPhase_CreateProxy(b2BroadPhase* bp, b2ProxyType proxyType, b2AABB aabb, uint32_t categoryBits, int shapeIndex, +int b2BroadPhase_CreateProxy(b2BroadPhase* bp, b2ProxyType proxyType, b2AABB aabb, uint32_t categoryBits, uint32_t maskBits, int shapeIndex, bool forcePairCreation) { B2_ASSERT(0 <= proxyType && proxyType < b2_proxyTypeCount); @@ -104,7 +101,7 @@ int b2BroadPhase_CreateProxy(b2BroadPhase* bp, b2ProxyType proxyType, b2AABB aab int proxyKey = B2_PROXY_KEY(proxyId, proxyType); if (proxyType != b2_staticProxy || forcePairCreation) { - b2BufferMove(bp, proxyKey); + b2BufferMove(bp, (b2MovedProxy){proxyKey, maskBits}); } return proxyKey; } @@ -123,16 +120,16 @@ void b2BroadPhase_DestroyProxy(b2BroadPhase* bp, int proxyKey) b2DynamicTree_DestroyProxy(bp->trees + proxyType, proxyId); } -void b2BroadPhase_MoveProxy(b2BroadPhase* bp, int proxyKey, b2AABB aabb) +void b2BroadPhase_MoveProxy(b2BroadPhase* bp, int proxyKey, b2AABB aabb, uint32_t maskBits) { b2ProxyType proxyType = B2_PROXY_TYPE(proxyKey); int proxyId = B2_PROXY_ID(proxyKey); b2DynamicTree_MoveProxy(bp->trees + proxyType, proxyId, aabb); - b2BufferMove(bp, proxyKey); + b2BufferMove(bp, (b2MovedProxy){proxyKey, maskBits}); } -void b2BroadPhase_EnlargeProxy(b2BroadPhase* bp, int proxyKey, b2AABB aabb) +void b2BroadPhase_EnlargeProxy(b2BroadPhase* bp, int proxyKey, b2AABB aabb, uint32_t maskBits) { B2_ASSERT(proxyKey != B2_NULL_INDEX); int typeIndex = B2_PROXY_TYPE(proxyKey); @@ -141,7 +138,7 @@ void b2BroadPhase_EnlargeProxy(b2BroadPhase* bp, int proxyKey, b2AABB aabb) B2_ASSERT(typeIndex == b2_movableProxy); b2DynamicTree_EnlargeProxy(bp->trees + typeIndex, proxyId, aabb); - b2BufferMove(bp, proxyKey); + b2BufferMove(bp, (b2MovedProxy){proxyKey, maskBits}); } typedef struct b2MovePair @@ -282,7 +279,7 @@ void b2FindPairsTask(int startIndex, int endIndex, uint32_t threadIndex, void* c queryContext.moveResult = bp->moveResults + i; queryContext.moveResult->pairList = NULL; - int proxyKey = bp->moveArray[i]; + int proxyKey = bp->moveArray[i].proxyKey; if (proxyKey == B2_NULL_INDEX) { // proxy was destroyed after it moved @@ -290,6 +287,7 @@ void b2FindPairsTask(int startIndex, int endIndex, uint32_t threadIndex, void* c } b2ProxyType proxyType = B2_PROXY_TYPE(proxyKey); + uint32_t maskBits = bp->moveArray[i].maskBits; // todo moving this choice to a higher level //B2_ASSERT(proxyType == b2_movableProxy); @@ -308,10 +306,10 @@ void b2FindPairsTask(int startIndex, int endIndex, uint32_t threadIndex, void* c if (proxyType == b2_movableProxy) { queryContext.queryTreeType = b2_staticProxy; - b2DynamicTree_Query(bp->trees + b2_staticProxy, fatAABB, b2PairQueryCallback, &queryContext); + b2DynamicTree_Query(bp->trees + b2_staticProxy, fatAABB, maskBits, b2PairQueryCallback, &queryContext); } queryContext.queryTreeType = b2_movableProxy; - b2DynamicTree_Query(bp->trees + b2_movableProxy, fatAABB, b2PairQueryCallback, &queryContext); + b2DynamicTree_Query(bp->trees + b2_movableProxy, fatAABB, maskBits, b2PairQueryCallback, &queryContext); } b2TracyCZoneEnd(pair_task); diff --git a/src/broad_phase.h b/src/broad_phase.h index 9c527fe6..065ce93a 100644 --- a/src/broad_phase.h +++ b/src/broad_phase.h @@ -6,7 +6,7 @@ #include "array.h" #include "table.h" -#include "box2d/dynamic_tree.h" +#include "box2d/collision.h" #include "box2d/types.h" typedef struct b2Shape b2Shape; @@ -15,6 +15,7 @@ typedef struct b2MoveResult b2MoveResult; typedef struct b2StackAllocator b2StackAllocator; typedef struct b2World b2World; +// todo kinematic tree to support large kinematic compounds typedef enum b2ProxyType { b2_staticProxy = 0, @@ -27,6 +28,12 @@ typedef enum b2ProxyType #define B2_PROXY_ID(KEY) ((KEY) >> 1) #define B2_PROXY_KEY(ID, TYPE) (((ID) << 1) | (TYPE)) +typedef struct b2MovedProxy +{ + int proxyKey; + uint32_t maskBits; +} b2MovedProxy; + /// The broad-phase is used for computing pairs and performing volume queries and ray casts. /// This broad-phase does not persist pairs. Instead, this reports potentially new pairs. /// It is up to the client to consume the new pairs and to track subsequent overlap. @@ -41,7 +48,7 @@ typedef struct b2BroadPhase // todo implement a 32bit hash set for faster lookup // todo moveSet can grow quite large on the first time step and remain large b2HashSet moveSet; - int* moveArray; + b2MovedProxy* moveArray; // These are the results from the pair query and are used to create new contacts // in deterministic order. @@ -59,11 +66,13 @@ typedef struct b2BroadPhase void b2CreateBroadPhase(b2BroadPhase* bp); void b2DestroyBroadPhase(b2BroadPhase* bp); -int b2BroadPhase_CreateProxy(b2BroadPhase* bp, b2ProxyType proxyType, b2AABB aabb, uint32_t categoryBits, int shapeIndex, bool forcePairCreation); + +// todo get rid of mask bits for proxies because this breaks groups +int b2BroadPhase_CreateProxy(b2BroadPhase* bp, b2ProxyType proxyType, b2AABB aabb, uint32_t categoryBits, uint32_t maskBits, int shapeIndex, bool forcePairCreation); void b2BroadPhase_DestroyProxy(b2BroadPhase* bp, int proxyKey); -void b2BroadPhase_MoveProxy(b2BroadPhase* bp, int proxyKey, b2AABB aabb); -void b2BroadPhase_EnlargeProxy(b2BroadPhase* bp, int proxyKey, b2AABB aabb); +void b2BroadPhase_MoveProxy(b2BroadPhase* bp, int proxyKey, b2AABB aabb, uint32_t maskBits); +void b2BroadPhase_EnlargeProxy(b2BroadPhase* bp, int proxyKey, b2AABB aabb, uint32_t maskBits); void b2BroadPhase_RebuildTrees(b2BroadPhase* bp); @@ -77,7 +86,7 @@ void b2ValidateNoEnlarged(const b2BroadPhase* bp); // This is what triggers new contact pairs to be created // Warning: this must be called in deterministic order -static inline void b2BufferMove(b2BroadPhase* bp, int proxyKey) +static inline void b2BufferMove(b2BroadPhase* bp, b2MovedProxy queryProxy) { // todo moving this choice to a higher level // Why only mobile proxies? Because we need to be able insert a large number of static shapes @@ -91,9 +100,9 @@ static inline void b2BufferMove(b2BroadPhase* bp, int proxyKey) //} // Adding 1 because 0 is the sentinel - bool alreadyAdded = b2AddKey(&bp->moveSet, proxyKey + 1); + bool alreadyAdded = b2AddKey(&bp->moveSet, queryProxy.proxyKey + 1); if (alreadyAdded == false) { - b2Array_Push(bp->moveArray, proxyKey); + b2Array_Push(bp->moveArray, queryProxy); } } diff --git a/src/constraint_graph.c b/src/constraint_graph.c index fffaf120..28d0a22b 100644 --- a/src/constraint_graph.c +++ b/src/constraint_graph.c @@ -46,15 +46,15 @@ void b2CreateGraph(b2ConstraintGraph* graph, int bodyCapacity) } } -void b2DestroyGraph(b2ConstraintGraph* graph, b2BlockAllocator* allocator) +void b2DestroyGraph(b2ConstraintGraph* graph) { for (int i = 0; i < b2_graphColorCount; ++i) { b2GraphColor* color = graph->colors + i; b2DestroyBitSet(&color->bodySet); - b2DestroyContactArray(allocator, &color->contacts); - b2DestroyJointArray(allocator, &color->joints); + b2DestroyContactArray(&color->contacts); + b2DestroyJointArray(&color->joints); } } @@ -136,7 +136,7 @@ void b2AddContactToGraph(b2World* world, b2ContactSim* contactSim, b2Contact* co contact->colorIndex = colorIndex; contact->localIndex = color->contacts.count; - b2ContactSim* newContact = b2AddContact(&world->blockAllocator, &color->contacts); + b2ContactSim* newContact = b2AddContact(&color->contacts); memcpy(newContact, contactSim, sizeof(b2ContactSim)); // todo perhaps skip this if the contact is already awake @@ -282,7 +282,7 @@ b2JointSim* b2CreateJointInGraph(b2World* world, b2Joint* joint) int colorIndex = b2AssignJointColor(graph, bodyIdA, bodyIdB, staticA, staticB); - b2JointSim* jointSim = b2AddJoint(&world->blockAllocator, &graph->colors[colorIndex].joints); + b2JointSim* jointSim = b2AddJoint(&graph->colors[colorIndex].joints); joint->colorIndex = colorIndex; joint->localIndex = graph->colors[colorIndex].joints.count - 1; return jointSim; diff --git a/src/constraint_graph.h b/src/constraint_graph.h index db3e2b58..e02256f5 100644 --- a/src/constraint_graph.h +++ b/src/constraint_graph.h @@ -45,7 +45,7 @@ typedef struct b2ConstraintGraph } b2ConstraintGraph; void b2CreateGraph(b2ConstraintGraph* graph, int bodyCapacity); -void b2DestroyGraph(b2ConstraintGraph* graph, b2BlockAllocator* allocator); +void b2DestroyGraph(b2ConstraintGraph* graph); void b2AddContactToGraph(b2World* world, b2ContactSim* contactSim, b2Contact* contact); void b2RemoveContactFromGraph(b2World* world, int bodyIdA, int bodyIdB, int colorIndex, int localIndex); diff --git a/src/contact.c b/src/contact.c index 0d8d0617..f44db700 100644 --- a/src/contact.c +++ b/src/contact.c @@ -10,15 +10,13 @@ #include "shape.h" #include "solver_set.h" #include "table.h" -#include "util.h" #include "world.h" -#include "box2d/distance.h" -#include "box2d/event_types.h" #include "box2d/collision.h" #include #include +#include // Contacts and determinism // A deterministic simulation requires contacts to exist in the same order in b2Island no matter the thread count. @@ -299,7 +297,7 @@ void b2CreateContact(b2World* world, b2Shape* shapeA, b2Shape* shapeB) // Contacts are created as non-touching. Later if they are found to be touching // they will link islands and be moved into the constraint graph. - b2ContactSim* contactSim = b2AddContact(&world->blockAllocator, &set->contacts); + b2ContactSim* contactSim = b2AddContact(&set->contacts); contactSim->contactId = contactId; #if B2_VALIDATE @@ -470,7 +468,7 @@ bool b2ShouldShapesCollide(b2Filter filterA, b2Filter filterB) return collide; } -static bool b2TestShapeOverlap(const b2Shape* shapeA, b2Transform xfA, const b2Shape* shapeB, b2Transform xfB) +static bool b2TestShapeOverlap(const b2Shape* shapeA, b2Transform xfA, const b2Shape* shapeB, b2Transform xfB, b2DistanceCache* cache) { b2DistanceInput input; input.proxyA = b2MakeShapeDistanceProxy(shapeA); @@ -479,8 +477,7 @@ static bool b2TestShapeOverlap(const b2Shape* shapeA, b2Transform xfA, const b2S input.transformB = xfB; input.useRadii = true; - b2DistanceCache cache = {0}; - b2DistanceOutput output = b2ShapeDistance(&cache, &input); + b2DistanceOutput output = b2ShapeDistance(cache, &input); return output.distance < 10.0f * FLT_EPSILON; } @@ -496,7 +493,7 @@ bool b2UpdateContact(b2World* world, b2ContactSim* contactSim, b2Shape* shapeA, if (shapeA->isSensor || shapeB->isSensor) { // Sensors don't generate manifolds or hit events - touching = b2TestShapeOverlap(shapeA, transformA, shapeB, transformB); + touching = b2TestShapeOverlap(shapeA, transformA, shapeB, transformB, &contactSim->cache); } else { diff --git a/src/contact.h b/src/contact.h index 3b7d3da9..e3787994 100644 --- a/src/contact.h +++ b/src/contact.h @@ -5,7 +5,6 @@ #include "core.h" -#include "box2d/distance.h" #include "box2d/collision.h" #include "box2d/types.h" @@ -145,5 +144,5 @@ b2ContactSim* b2GetContactSim(b2World* world, b2Contact* contact); bool b2ShouldShapesCollide(b2Filter filterA, b2Filter filterB); -bool b2UpdateContact(b2World* world, b2ContactSim* contact, b2Shape* shapeA, b2Transform transformA, b2Vec2 centerOffsetA, +bool b2UpdateContact(b2World* world, b2ContactSim* contactSim, b2Shape* shapeA, b2Transform transformA, b2Vec2 centerOffsetA, b2Shape* shapeB, b2Transform transformB, b2Vec2 centerOffsetB); diff --git a/src/contact_solver.c b/src/contact_solver.c index 3d5204bb..d4379c4d 100644 --- a/src/contact_solver.c +++ b/src/contact_solver.c @@ -12,8 +12,6 @@ #include "x86/avx2.h" #include "x86/fma.h" -#include "box2d/color.h" - // Soft contact constraints with sub-stepping support // http://mmacklin.com/smallsteps.pdf // https://box2d.org/files/ErinCatto_SoftConstraints_GDC2011.pdf @@ -576,7 +574,7 @@ static void b2ScatterBodies(b2BodyState* restrict states, int* restrict indices, b2FloatW tt6 = simde_mm256_shuffle_ps(t5, t7, SIMDE_MM_SHUFFLE(1, 0, 1, 0)); b2FloatW tt7 = simde_mm256_shuffle_ps(t5, t7, SIMDE_MM_SHUFFLE(3, 2, 3, 2)); - // I don't use any dummy body in the body array because this will lead to multi-threaded sharing and the + // I don't use any dummy body in the body array because this will lead to multithreaded sharing and the // associated cache flushing. if (indices[0] != B2_NULL_INDEX) simde_mm256_store_ps((float*)(states + indices[0]), simde_mm256_permute2f128_ps(tt0, tt4, 0x20)); diff --git a/src/contact_solver.h b/src/contact_solver.h index 0626656d..2deff3c8 100644 --- a/src/contact_solver.h +++ b/src/contact_solver.h @@ -5,8 +5,6 @@ #include "solver.h" -#include "box2d/math_types.h" - #include "x86/avx.h" typedef struct b2ContactSim b2ContactSim; diff --git a/src/core.h b/src/core.h index 196e3ff7..26477e77 100644 --- a/src/core.h +++ b/src/core.h @@ -3,7 +3,7 @@ #pragma once -#include "box2d/api.h" +#include "box2d/base.h" #define B2_NULL_INDEX (-1) @@ -109,43 +109,54 @@ extern b2AssertFcn* b2AssertHandler; extern float b2_lengthUnitsPerMeter; -/// Used to detect bad values. Positions greater than about 16km will have precision -/// problems, so 100km as a limit should be fine in all cases. +// Used to detect bad values. Positions greater than about 16km will have precision +// problems, so 100km as a limit should be fine in all cases. #define b2_huge (100000.0f * b2_lengthUnitsPerMeter) -/// Maximum parallel workers. Used to size some static arrays. +// Maximum parallel workers. Used to size some static arrays. #define b2_maxWorkers 64 -/// Maximum number of colors in the constraint graph. Constraints that cannot -/// find a color are added to the overflow set which are solved single-threaded. +// Maximum number of colors in the constraint graph. Constraints that cannot +// find a color are added to the overflow set which are solved single-threaded. #define b2_graphColorCount 12 -/// A small length used as a collision and constraint tolerance. Usually it is -/// chosen to be numerically significant, but visually insignificant. In meters. -/// @warning modifying this can have a significant impact on stability +// A small length used as a collision and constraint tolerance. Usually it is +// chosen to be numerically significant, but visually insignificant. In meters. +// @warning modifying this can have a significant impact on stability #define b2_linearSlop (0.005f * b2_lengthUnitsPerMeter) -/// Maximum number of simultaneous worlds that can be allocated +// Maximum number of simultaneous worlds that can be allocated #define b2_maxWorlds 128 -/// The maximum translation of a body per time step. This limit is very large and is used -/// to prevent numerical problems. You shouldn't need to adjust this. Meters. -/// @warning modifying this can have a significant impact on stability +// The maximum translation of a body per time step. This limit is very large and is used +// to prevent numerical problems. You shouldn't need to adjust this. Meters. +// @warning modifying this can have a significant impact on stability #define b2_maxTranslation (4.0f * b2_lengthUnitsPerMeter) -/// The maximum rotation of a body per time step. This limit is very large and is used -/// to prevent numerical problems. You shouldn't need to adjust this. -/// @warning modifying this can have a significant impact on stability +// The maximum rotation of a body per time step. This limit is very large and is used +// to prevent numerical problems. You shouldn't need to adjust this. +// @warning modifying this can have a significant impact on stability #define b2_maxRotation (0.25f * b2_pi) -/// @warning modifying this can have a significant impact on performance and stability +// @warning modifying this can have a significant impact on performance and stability #define b2_speculativeDistance (4.0f * b2_linearSlop) -/// This is used to fatten AABBs in the dynamic tree. This allows proxies -/// to move by a small amount without triggering a tree adjustment. -/// This is in meters. -/// @warning modifying this can have a significant impact on performance +// This is used to fatten AABBs in the dynamic tree. This allows proxies +// to move by a small amount without triggering a tree adjustment. +// This is in meters. +// @warning modifying this can have a significant impact on performance #define b2_aabbMargin (0.1f * b2_lengthUnitsPerMeter) -/// The time that a body must be still before it will go to sleep. In seconds. +// The time that a body must be still before it will go to sleep. In seconds. #define b2_timeToSleep 0.5f + +// Returns the number of elements of an array +#define B2_ARRAY_COUNT(A) (int)(sizeof(A) / sizeof(A[0])) + +// Used to prevent the compiler from warning about unused variables +#define B2_MAYBE_UNUSED(x) ((void)(x)) + +// Use to validate definitions. Do not take my cookie. +#define B2_SECRET_COOKIE 1152023 + +#define b2CheckDef(DEF) B2_ASSERT(DEF->internalValue == B2_SECRET_COOKIE) diff --git a/src/distance.c b/src/distance.c index 0239cc51..bf32b063 100644 --- a/src/distance.c +++ b/src/distance.c @@ -2,12 +2,11 @@ // SPDX-FileCopyrightText: 2023 Erin Catto // SPDX-License-Identifier: MIT -#include "box2d/distance.h" #include "core.h" +#include "box2d/collision.h" #include "box2d/math_functions.h" -#include "box2d/timer.h" #include @@ -116,7 +115,7 @@ b2DistanceProxy b2MakeProxy(const b2Vec2* vertices, int32_t count, float radius) b2DistanceProxy proxy; for (int32_t i = 0; i < count; ++i) { - proxy.vertices[i] = vertices[i]; + proxy.points[i] = vertices[i]; } proxy.count = count; proxy.radius = radius; @@ -136,10 +135,10 @@ static b2Vec2 b2Weight3(float a1, b2Vec2 w1, float a2, b2Vec2 w2, float a3, b2Ve static int32_t b2FindSupport(const b2DistanceProxy* proxy, b2Vec2 direction) { int32_t bestIndex = 0; - float bestValue = b2Dot(proxy->vertices[0], direction); + float bestValue = b2Dot(proxy->points[0], direction); for (int32_t i = 1; i < proxy->count; ++i) { - float value = b2Dot(proxy->vertices[i], direction); + float value = b2Dot(proxy->points[i], direction); if (value > bestValue) { bestIndex = i; @@ -204,8 +203,8 @@ static b2Simplex b2MakeSimplexFromCache(const b2DistanceCache* cache, const b2Di b2SimplexVertex* v = vertices[i]; v->indexA = cache->indexA[i]; v->indexB = cache->indexB[i]; - b2Vec2 wALocal = proxyA->vertices[v->indexA]; - b2Vec2 wBLocal = proxyB->vertices[v->indexB]; + b2Vec2 wALocal = proxyA->points[v->indexA]; + b2Vec2 wBLocal = proxyB->points[v->indexB]; v->wA = b2TransformPoint(transformA, wALocal); v->wB = b2TransformPoint(transformB, wBLocal); v->w = b2Sub(v->wB, v->wA); @@ -220,8 +219,8 @@ static b2Simplex b2MakeSimplexFromCache(const b2DistanceCache* cache, const b2Di b2SimplexVertex* v = vertices[0]; v->indexA = 0; v->indexB = 0; - b2Vec2 wALocal = proxyA->vertices[0]; - b2Vec2 wBLocal = proxyB->vertices[0]; + b2Vec2 wALocal = proxyA->points[0]; + b2Vec2 wBLocal = proxyB->points[0]; v->wA = b2TransformPoint(transformA, wALocal); v->wB = b2TransformPoint(transformB, wBLocal); v->w = b2Sub(v->wB, v->wA); @@ -495,7 +494,7 @@ void b2SolveSimplex3(b2Simplex* B2_RESTRICT s) #define B2_GJK_DEBUG 0 -// Warning: writing to these globals significantly slows multi-threading performance +// Warning: writing to these globals significantly slows multithreading performance #if B2_GJK_DEBUG int32_t b2_gjkCalls; int32_t b2_gjkIters; @@ -521,20 +520,19 @@ b2DistanceOutput b2ShapeDistance(b2DistanceCache* cache, const b2DistanceInput* // Get simplex vertices as an array. b2SimplexVertex* vertices[] = {&simplex.v1, &simplex.v2, &simplex.v3}; - const int32_t k_maxIters = 20; + const int k_maxIters = 20; // These store the vertices of the last simplex so that we // can check for duplicates and prevent cycling. - int32_t saveA[3], saveB[3]; - int32_t saveCount = 0; + int saveA[3], saveB[3]; // Main iteration loop. - int32_t iter = 0; + int iter = 0; while (iter < k_maxIters) { // Copy simplex so we can identify duplicates. - saveCount = simplex.count; - for (int32_t i = 0; i < saveCount; ++i) + int saveCount = simplex.count; + for (int i = 0; i < saveCount; ++i) { saveA[i] = vertices[i]->indexA; saveB[i] = vertices[i]->indexB; @@ -581,9 +579,9 @@ b2DistanceOutput b2ShapeDistance(b2DistanceCache* cache, const b2DistanceInput* // Compute a tentative new simplex vertex using support points. b2SimplexVertex* vertex = vertices[simplex.count]; vertex->indexA = b2FindSupport(proxyA, b2InvRotateVector(transformA.q, b2Neg(d))); - vertex->wA = b2TransformPoint(transformA, proxyA->vertices[vertex->indexA]); + vertex->wA = b2TransformPoint(transformA, proxyA->points[vertex->indexA]); vertex->indexB = b2FindSupport(proxyB, b2InvRotateVector(transformB.q, d)); - vertex->wB = b2TransformPoint(transformB, proxyB->vertices[vertex->indexB]); + vertex->wB = b2TransformPoint(transformB, proxyB->points[vertex->indexB]); vertex->w = b2Sub(vertex->wB, vertex->wA); // Iteration count is equated to the number of support point calls. @@ -595,7 +593,7 @@ b2DistanceOutput b2ShapeDistance(b2DistanceCache* cache, const b2DistanceInput* // Check for duplicate support points. This is the main termination criteria. bool duplicate = false; - for (int32_t i = 0; i < saveCount; ++i) + for (int i = 0; i < saveCount; ++i) { if (vertex->indexA == saveA[i] && vertex->indexB == saveB[i]) { @@ -678,7 +676,7 @@ b2CastOutput b2ShapeCast(const b2ShapeCastPairInput* input) for (int i = 0; i < proxyB.count; ++i) { - proxyB.vertices[i] = b2TransformPoint(xf, input->proxyB.vertices[i]); + proxyB.points[i] = b2TransformPoint(xf, input->proxyB.points[i]); } float radius = proxyA.radius + proxyB.radius; @@ -695,10 +693,10 @@ b2CastOutput b2ShapeCast(const b2ShapeCastPairInput* input) b2SimplexVertex* vertices[] = {&simplex.v1, &simplex.v2, &simplex.v3}; // Get an initial point in A - B - int32_t indexA = b2FindSupport(&proxyA, b2Neg(r)); - b2Vec2 wA = proxyA.vertices[indexA]; - int32_t indexB = b2FindSupport(&proxyB, r); - b2Vec2 wB = proxyB.vertices[indexB]; + int indexA = b2FindSupport(&proxyA, b2Neg(r)); + b2Vec2 wA = proxyA.points[indexA]; + int indexB = b2FindSupport(&proxyB, r); + b2Vec2 wB = proxyB.points[indexB]; b2Vec2 v = b2Sub(wA, wB); // Sigma is the target distance between proxies @@ -706,8 +704,8 @@ b2CastOutput b2ShapeCast(const b2ShapeCastPairInput* input) const float sigma = b2MaxFloat(linearSlop, radius - linearSlop); // Main iteration loop. - const int32_t k_maxIters = 20; - int32_t iter = 0; + const int k_maxIters = 20; + int iter = 0; while (iter < k_maxIters && b2Length(v) > sigma + 0.5f * linearSlop) { B2_ASSERT(simplex.count < 3); @@ -716,9 +714,9 @@ b2CastOutput b2ShapeCast(const b2ShapeCastPairInput* input) // Support in direction -v (A - B) indexA = b2FindSupport(&proxyA, b2Neg(v)); - wA = proxyA.vertices[indexA]; + wA = proxyA.points[indexA]; indexB = b2FindSupport(&proxyB, v); - wB = proxyB.vertices[indexB]; + wB = proxyB.points[indexB]; b2Vec2 p = b2Sub(wA, wB); // -v is a normal at p, normalize to work with sigma @@ -813,11 +811,11 @@ b2CastOutput b2ShapeCast(const b2ShapeCastPairInput* input) #define B2_TOI_DEBUG 0 -// Warning: writing to these globals significantly slows multi-threading performance +// Warning: writing to these globals significantly slows multithreading performance #if B2_TOI_DEBUG float b2_toiTime, b2_toiMaxTime; -int32_t b2_toiCalls, b2_toiIters, b2_toiMaxIters; -int32_t b2_toiRootIters, b2_toiMaxRootIters; +int b2_toiCalls, b2_toiIters, b2_toiMaxIters; +int b2_toiRootIters, b2_toiMaxRootIters; #endif typedef enum b2SeparationType @@ -844,7 +842,7 @@ b2SeparationFunction b2MakeSeparationFunction(const b2DistanceCache* cache, cons f.proxyA = proxyA; f.proxyB = proxyB; - int32_t count = cache->count; + int count = cache->count; B2_ASSERT(0 < count && count < 3); f.sweepA = *sweepA; @@ -856,8 +854,8 @@ b2SeparationFunction b2MakeSeparationFunction(const b2DistanceCache* cache, cons if (count == 1) { f.type = b2_pointsType; - b2Vec2 localPointA = proxyA->vertices[cache->indexA[0]]; - b2Vec2 localPointB = proxyB->vertices[cache->indexB[0]]; + b2Vec2 localPointA = proxyA->points[cache->indexA[0]]; + b2Vec2 localPointB = proxyB->points[cache->indexB[0]]; b2Vec2 pointA = b2TransformPoint(xfA, localPointA); b2Vec2 pointB = b2TransformPoint(xfB, localPointB); f.axis = b2Normalize(b2Sub(pointB, pointA)); @@ -869,8 +867,8 @@ b2SeparationFunction b2MakeSeparationFunction(const b2DistanceCache* cache, cons { // Two points on B and one on A. f.type = b2_faceBType; - b2Vec2 localPointB1 = proxyB->vertices[cache->indexB[0]]; - b2Vec2 localPointB2 = proxyB->vertices[cache->indexB[1]]; + b2Vec2 localPointB1 = proxyB->points[cache->indexB[0]]; + b2Vec2 localPointB2 = proxyB->points[cache->indexB[1]]; f.axis = b2CrossVS(b2Sub(localPointB2, localPointB1), 1.0f); f.axis = b2Normalize(f.axis); @@ -879,7 +877,7 @@ b2SeparationFunction b2MakeSeparationFunction(const b2DistanceCache* cache, cons f.localPoint = (b2Vec2){0.5f * (localPointB1.x + localPointB2.x), 0.5f * (localPointB1.y + localPointB2.y)}; b2Vec2 pointB = b2TransformPoint(xfB, f.localPoint); - b2Vec2 localPointA = proxyA->vertices[cache->indexA[0]]; + b2Vec2 localPointA = proxyA->points[cache->indexA[0]]; b2Vec2 pointA = b2TransformPoint(xfA, localPointA); float s = b2Dot(b2Sub(pointA, pointB), normal); @@ -892,8 +890,8 @@ b2SeparationFunction b2MakeSeparationFunction(const b2DistanceCache* cache, cons // Two points on A and one or two points on B. f.type = b2_faceAType; - b2Vec2 localPointA1 = proxyA->vertices[cache->indexA[0]]; - b2Vec2 localPointA2 = proxyA->vertices[cache->indexA[1]]; + b2Vec2 localPointA1 = proxyA->points[cache->indexA[0]]; + b2Vec2 localPointA2 = proxyA->points[cache->indexA[1]]; f.axis = b2CrossVS(b2Sub(localPointA2, localPointA1), 1.0f); f.axis = b2Normalize(f.axis); @@ -902,7 +900,7 @@ b2SeparationFunction b2MakeSeparationFunction(const b2DistanceCache* cache, cons f.localPoint = (b2Vec2){0.5f * (localPointA1.x + localPointA2.x), 0.5f * (localPointA1.y + localPointA2.y)}; b2Vec2 pointA = b2TransformPoint(xfA, f.localPoint); - b2Vec2 localPointB = proxyB->vertices[cache->indexB[0]]; + b2Vec2 localPointB = proxyB->points[cache->indexB[0]]; b2Vec2 pointB = b2TransformPoint(xfB, localPointB); float s = b2Dot(b2Sub(pointB, pointA), normal); @@ -913,7 +911,7 @@ b2SeparationFunction b2MakeSeparationFunction(const b2DistanceCache* cache, cons return f; } -float b2FindMinSeparation(const b2SeparationFunction* f, int32_t* indexA, int32_t* indexB, float t) +static float b2FindMinSeparation(const b2SeparationFunction* f, int32_t* indexA, int32_t* indexB, float t) { b2Transform xfA = b2GetSweepTransform(&f->sweepA, t); b2Transform xfB = b2GetSweepTransform(&f->sweepB, t); @@ -928,8 +926,8 @@ float b2FindMinSeparation(const b2SeparationFunction* f, int32_t* indexA, int32_ *indexA = b2FindSupport(f->proxyA, axisA); *indexB = b2FindSupport(f->proxyB, axisB); - b2Vec2 localPointA = f->proxyA->vertices[*indexA]; - b2Vec2 localPointB = f->proxyB->vertices[*indexB]; + b2Vec2 localPointA = f->proxyA->points[*indexA]; + b2Vec2 localPointB = f->proxyB->points[*indexB]; b2Vec2 pointA = b2TransformPoint(xfA, localPointA); b2Vec2 pointB = b2TransformPoint(xfB, localPointB); @@ -948,7 +946,7 @@ float b2FindMinSeparation(const b2SeparationFunction* f, int32_t* indexA, int32_ *indexA = -1; *indexB = b2FindSupport(f->proxyB, axisB); - b2Vec2 localPointB = f->proxyB->vertices[*indexB]; + b2Vec2 localPointB = f->proxyB->points[*indexB]; b2Vec2 pointB = b2TransformPoint(xfB, localPointB); float separation = b2Dot(b2Sub(pointB, pointA), normal); @@ -965,7 +963,7 @@ float b2FindMinSeparation(const b2SeparationFunction* f, int32_t* indexA, int32_ *indexB = -1; *indexA = b2FindSupport(f->proxyA, axisA); - b2Vec2 localPointA = f->proxyA->vertices[*indexA]; + b2Vec2 localPointA = f->proxyA->points[*indexA]; b2Vec2 pointA = b2TransformPoint(xfA, localPointA); float separation = b2Dot(b2Sub(pointA, pointB), normal); @@ -990,8 +988,8 @@ float b2EvaluateSeparation(const b2SeparationFunction* f, int32_t indexA, int32_ { case b2_pointsType: { - b2Vec2 localPointA = f->proxyA->vertices[indexA]; - b2Vec2 localPointB = f->proxyB->vertices[indexB]; + b2Vec2 localPointA = f->proxyA->points[indexA]; + b2Vec2 localPointB = f->proxyB->points[indexB]; b2Vec2 pointA = b2TransformPoint(xfA, localPointA); b2Vec2 pointB = b2TransformPoint(xfB, localPointB); @@ -1005,7 +1003,7 @@ float b2EvaluateSeparation(const b2SeparationFunction* f, int32_t indexA, int32_ b2Vec2 normal = b2RotateVector(xfA.q, f->axis); b2Vec2 pointA = b2TransformPoint(xfA, f->localPoint); - b2Vec2 localPointB = f->proxyB->vertices[indexB]; + b2Vec2 localPointB = f->proxyB->points[indexB]; b2Vec2 pointB = b2TransformPoint(xfB, localPointB); float separation = b2Dot(b2Sub(pointB, pointA), normal); @@ -1017,7 +1015,7 @@ float b2EvaluateSeparation(const b2SeparationFunction* f, int32_t indexA, int32_ b2Vec2 normal = b2RotateVector(xfB.q, f->axis); b2Vec2 pointB = b2TransformPoint(xfB, f->localPoint); - b2Vec2 localPointA = f->proxyA->vertices[indexA]; + b2Vec2 localPointA = f->proxyA->points[indexA]; b2Vec2 pointA = b2TransformPoint(xfA, localPointA); float separation = b2Dot(b2Sub(pointA, pointB), normal); diff --git a/src/distance_joint.c b/src/distance_joint.c index 0447994d..8db379f4 100644 --- a/src/distance_joint.c +++ b/src/distance_joint.c @@ -12,8 +12,6 @@ // needed for dll export #include "box2d/box2d.h" -#include "box2d/debug_draw.h" -#include "box2d/joint_types.h" #include diff --git a/src/dynamic_tree.c b/src/dynamic_tree.c index 051daa81..4c0e6a81 100644 --- a/src/dynamic_tree.c +++ b/src/dynamic_tree.c @@ -4,9 +4,8 @@ #include "aabb.h" #include "allocate.h" #include "core.h" -#include "util.h" -#include "box2d/dynamic_tree.h" +#include "box2d/collision.h" #include "box2d/math_functions.h" #include @@ -822,7 +821,7 @@ void b2DynamicTree_EnlargeProxy(b2DynamicTree* tree, int32_t proxyId, b2AABB aab } } -int32_t b2DynamicTree_GetHeight(const b2DynamicTree* tree) +int b2DynamicTree_GetHeight(const b2DynamicTree* tree) { if (tree->root == B2_NULL_INDEX) { @@ -859,7 +858,7 @@ float b2DynamicTree_GetAreaRatio(const b2DynamicTree* tree) } // Compute the height of a sub-tree. -static int32_t b2ComputeHeight(const b2DynamicTree* tree, int32_t nodeId) +static int b2ComputeHeight(const b2DynamicTree* tree, int32_t nodeId) { B2_ASSERT(0 <= nodeId && nodeId < tree->nodeCapacity); b2TreeNode* node = tree->nodes + nodeId; @@ -874,9 +873,9 @@ static int32_t b2ComputeHeight(const b2DynamicTree* tree, int32_t nodeId) return 1 + b2MaxInt16(height1, height2); } -int32_t b2DynamicTree_ComputeHeight(const b2DynamicTree* tree) +int b2DynamicTree_ComputeHeight(const b2DynamicTree* tree) { - int32_t height = b2ComputeHeight(tree, tree->root); + int height = b2ComputeHeight(tree, tree->root); return height; } @@ -1022,7 +1021,7 @@ int32_t b2DynamicTree_GetMaxBalance(const b2DynamicTree* tree) void b2DynamicTree_RebuildBottomUp(b2DynamicTree* tree) { - int32_t* nodes = (int32_t*)b2Alloc(tree->nodeCount * sizeof(int32_t)); + int32_t* nodes = b2Alloc(tree->nodeCount * sizeof(int32_t)); int32_t count = 0; // Build array of leaves. Free the rest. @@ -1117,7 +1116,7 @@ int b2DynamicTree_GetByteCount(const b2DynamicTree* tree) return (int)size; } -void b2DynamicTree_QueryFiltered(const b2DynamicTree* tree, b2AABB aabb, uint32_t maskBits, b2TreeQueryCallbackFcn* callback, +void b2DynamicTree_Query(const b2DynamicTree* tree, b2AABB aabb, uint32_t maskBits, b2TreeQueryCallbackFcn* callback, void* context) { int32_t stack[b2_treeStackSize]; @@ -1158,46 +1157,6 @@ void b2DynamicTree_QueryFiltered(const b2DynamicTree* tree, b2AABB aabb, uint32_ } } -void b2DynamicTree_Query(const b2DynamicTree* tree, b2AABB aabb, b2TreeQueryCallbackFcn* callback, void* context) -{ - int32_t stack[b2_treeStackSize]; - int32_t stackCount = 0; - stack[stackCount++] = tree->root; - - while (stackCount > 0) - { - int32_t nodeId = stack[--stackCount]; - if (nodeId == B2_NULL_INDEX) - { - continue; - } - - const b2TreeNode* node = tree->nodes + nodeId; - - if (b2AABB_Overlaps(node->aabb, aabb)) - { - if (b2IsLeaf(node)) - { - // callback to user code with proxy id - bool proceed = callback(nodeId, node->userData, context); - if (proceed == false) - { - return; - } - } - else - { - B2_ASSERT(stackCount < b2_treeStackSize - 1); - if (stackCount < b2_treeStackSize - 1) - { - stack[stackCount++] = node->child1; - stack[stackCount++] = node->child2; - } - } - } - } -} - void b2DynamicTree_RayCast(const b2DynamicTree* tree, const b2RayCastInput* input, uint32_t maskBits, b2TreeRayCastCallbackFcn* callback, void* context) { diff --git a/src/geometry.c b/src/geometry.c index 9c3e5f75..caee3eb3 100644 --- a/src/geometry.c +++ b/src/geometry.c @@ -1,13 +1,11 @@ // SPDX-FileCopyrightText: 2023 Erin Catto // SPDX-License-Identifier: MIT -#include "box2d/geometry.h" - #include "aabb.h" #include "core.h" #include "shape.h" -#include "box2d/distance.h" +#include "box2d/collision.h" #include "box2d/math_functions.h" #include diff --git a/src/hull.c b/src/hull.c index aa380605..8eb80493 100644 --- a/src/hull.c +++ b/src/hull.c @@ -1,10 +1,9 @@ // SPDX-FileCopyrightText: 2023 Erin Catto // SPDX-License-Identifier: MIT -#include "box2d/geometry.h" - #include "core.h" +#include "box2d/collision.h" #include "box2d/math_functions.h" #include diff --git a/src/id_pool.c b/src/id_pool.c index a3406e69..4c95afcf 100644 --- a/src/id_pool.c +++ b/src/id_pool.c @@ -4,7 +4,6 @@ #include "id_pool.h" #include "array.h" -#include "util.h" b2IdPool b2CreateIdPool() { diff --git a/src/island.c b/src/island.c index 5a318850..f20630cc 100644 --- a/src/island.c +++ b/src/island.c @@ -9,12 +9,8 @@ #include "core.h" #include "joint.h" #include "solver_set.h" -#include "util.h" #include "world.h" -#include "box2d/color.h" -#include "box2d/timer.h" - #include b2Island* b2CreateIsland(b2World* world, int setIndex) @@ -51,7 +47,7 @@ b2Island* b2CreateIsland(b2World* world, int setIndex) island->parentIsland = B2_NULL_INDEX; island->constraintRemoveCount = 0; - b2IslandSim* islandSim = b2AddIsland(&world->blockAllocator, &set->islands); + b2IslandSim* islandSim = b2AddIsland(&set->islands); islandSim->islandId = islandId; return island; diff --git a/src/joint.c b/src/joint.c index 1d1b1144..675a8dea 100644 --- a/src/joint.c +++ b/src/joint.c @@ -9,20 +9,19 @@ #include "shape.h" #include "solver.h" #include "solver_set.h" -#include "util.h" #include "world.h" // needed for dll export #include "box2d/box2d.h" -#include "box2d/color.h" -#include "box2d/debug_draw.h" -#include "box2d/joint_types.h" + +#include b2DistanceJointDef b2DefaultDistanceJointDef() { b2DistanceJointDef def = {0}; def.length = 1.0f; def.maxLength = b2_huge; + def.internalValue = B2_SECRET_COOKIE; return def; } @@ -32,6 +31,7 @@ b2MotorJointDef b2DefaultMotorJointDef() def.maxForce = 1.0f; def.maxTorque = 1.0f; def.correctionFactor = 0.3f; + def.internalValue = B2_SECRET_COOKIE; return def; } @@ -41,6 +41,7 @@ b2MouseJointDef b2DefaultMouseJointDef() def.hertz = 4.0f; def.dampingRatio = 1.0f; def.maxForce = 1.0f; + def.internalValue = B2_SECRET_COOKIE; return def; } @@ -48,6 +49,7 @@ b2PrismaticJointDef b2DefaultPrismaticJointDef() { b2PrismaticJointDef def = {0}; def.localAxisA = (b2Vec2){1.0f, 0.0f}; + def.internalValue = B2_SECRET_COOKIE; return def; } @@ -55,12 +57,14 @@ b2RevoluteJointDef b2DefaultRevoluteJointDef() { b2RevoluteJointDef def = {0}; def.drawSize = 0.25f; + def.internalValue = B2_SECRET_COOKIE; return def; } b2WeldJointDef b2DefaultWeldJointDef() { b2WeldJointDef def = {0}; + def.internalValue = B2_SECRET_COOKIE; return def; } @@ -71,6 +75,7 @@ b2WheelJointDef b2DefaultWheelJointDef() def.enableSpring = true; def.hertz = 1.0f; def.dampingRatio = 0.7f; + def.internalValue = B2_SECRET_COOKIE; return def; } @@ -199,7 +204,7 @@ static b2JointPair b2CreateJoint(b2World* world, b2Body* bodyA, b2Body* bodyB, v joint->setIndex = b2_disabledSet; joint->localIndex = set->joints.count; - jointSim = b2AddJoint(&world->blockAllocator, &set->joints); + jointSim = b2AddJoint(&set->joints); jointSim->jointId = jointId; jointSim->bodyIdA = bodyIdA; jointSim->bodyIdB = bodyIdB; @@ -211,7 +216,7 @@ static b2JointPair b2CreateJoint(b2World* world, b2Body* bodyA, b2Body* bodyB, v joint->setIndex = b2_staticSet; joint->localIndex = set->joints.count; - jointSim = b2AddJoint(&world->blockAllocator, &set->joints); + jointSim = b2AddJoint(&set->joints); jointSim->jointId = jointId; jointSim->bodyIdA = bodyIdA; jointSim->bodyIdB = bodyIdB; @@ -244,7 +249,7 @@ static b2JointPair b2CreateJoint(b2World* world, b2Body* bodyA, b2Body* bodyB, v b2SolverSet* set = world->solverSetArray + setIndex; joint->setIndex = setIndex; joint->localIndex = set->joints.count; - jointSim = b2AddJoint(&world->blockAllocator, &set->joints); + jointSim = b2AddJoint(&set->joints); jointSim->jointId = jointId; jointSim->bodyIdA = bodyIdA; jointSim->bodyIdB = bodyIdB; @@ -325,6 +330,7 @@ static void b2DestroyContactsBetweenBodies(b2World* world, b2Body* bodyA, b2Body b2JointId b2CreateDistanceJoint(b2WorldId worldId, const b2DistanceJointDef* def) { + b2CheckDef(def); b2World* world = b2GetWorldFromId(worldId); B2_ASSERT(world->locked == false); @@ -377,6 +383,7 @@ b2JointId b2CreateDistanceJoint(b2WorldId worldId, const b2DistanceJointDef* def b2JointId b2CreateMotorJoint(b2WorldId worldId, const b2MotorJointDef* def) { + b2CheckDef(def); b2World* world = b2GetWorldFromId(worldId); B2_ASSERT(world->locked == false); @@ -414,6 +421,7 @@ b2JointId b2CreateMotorJoint(b2WorldId worldId, const b2MotorJointDef* def) b2JointId b2CreateMouseJoint(b2WorldId worldId, const b2MouseJointDef* def) { + b2CheckDef(def); b2World* world = b2GetWorldFromId(worldId); B2_ASSERT(world->locked == false); @@ -449,6 +457,7 @@ b2JointId b2CreateMouseJoint(b2WorldId worldId, const b2MouseJointDef* def) b2JointId b2CreateRevoluteJoint(b2WorldId worldId, const b2RevoluteJointDef* def) { + b2CheckDef(def); b2World* world = b2GetWorldFromId(worldId); B2_ASSERT(world->locked == false); @@ -502,6 +511,7 @@ b2JointId b2CreateRevoluteJoint(b2WorldId worldId, const b2RevoluteJointDef* def b2JointId b2CreatePrismaticJoint(b2WorldId worldId, const b2PrismaticJointDef* def) { + b2CheckDef(def); b2World* world = b2GetWorldFromId(worldId); B2_ASSERT(world->locked == false); @@ -554,6 +564,7 @@ b2JointId b2CreatePrismaticJoint(b2WorldId worldId, const b2PrismaticJointDef* d b2JointId b2CreateWeldJoint(b2WorldId worldId, const b2WeldJointDef* def) { + b2CheckDef(def); b2World* world = b2GetWorldFromId(worldId); B2_ASSERT(world->locked == false); @@ -595,6 +606,7 @@ b2JointId b2CreateWeldJoint(b2WorldId worldId, const b2WeldJointDef* def) b2JointId b2CreateWheelJoint(b2WorldId worldId, const b2WheelJointDef* def) { + b2CheckDef(def); b2World* world = b2GetWorldFromId(worldId); B2_ASSERT(world->locked == false); @@ -825,7 +837,7 @@ void b2Joint_SetCollideConnected(b2JointId jointId, bool shouldCollide) if (shape->proxyKey != B2_NULL_INDEX) { - b2BufferMove(&world->broadPhase, shape->proxyKey); + b2BufferMove(&world->broadPhase, (b2MovedProxy){shape->proxyKey, shape->filter.maskBits}); } shapeId = shape->nextShapeId; @@ -1185,7 +1197,7 @@ void b2DrawJoint(b2DebugDraw* draw, b2World* world, b2Joint* joint) draw->DrawPoint(target, 4.0f, c1, draw->context); draw->DrawPoint(pB, 4.0f, c1, draw->context); - b2HexColor c2 = b2_colorGray80; + b2HexColor c2 = b2_colorGray8; draw->DrawSegment(target, pB, c2, draw->context); } break; diff --git a/src/joint.h b/src/joint.h index bad1f474..5e82c5e3 100644 --- a/src/joint.h +++ b/src/joint.h @@ -4,7 +4,7 @@ #include "solver.h" -#include "box2d/joint_types.h" +#include "box2d/types.h" typedef struct b2DebugDraw b2DebugDraw; typedef struct b2StepContext b2StepContext; diff --git a/src/manifold.c b/src/manifold.c index f0cbdac0..b1ff490d 100644 --- a/src/manifold.c +++ b/src/manifold.c @@ -5,8 +5,6 @@ #include "core.h" -#include "box2d/distance.h" -#include "box2d/geometry.h" #include "box2d/math_functions.h" #include diff --git a/src/motor_joint.c b/src/motor_joint.c index a48535ae..15bcb703 100644 --- a/src/motor_joint.c +++ b/src/motor_joint.c @@ -10,7 +10,6 @@ // needed for dll export #include "box2d/box2d.h" -#include "box2d/debug_draw.h" void b2MotorJoint_SetLinearOffset(b2JointId jointId, b2Vec2 linearOffset) { @@ -27,7 +26,7 @@ b2Vec2 b2MotorJoint_GetLinearOffset(b2JointId jointId) void b2MotorJoint_SetAngularOffset(b2JointId jointId, float angularOffset) { b2JointSim* joint = b2GetJointSimCheckType(jointId, b2_motorJoint); - joint->motorJoint.angularOffset = angularOffset; + joint->motorJoint.angularOffset = b2ClampFloat(angularOffset, -b2_pi, b2_pi); } float b2MotorJoint_GetAngularOffset(b2JointId jointId) @@ -148,6 +147,7 @@ void b2PrepareMotorJoint(b2JointSim* base, b2StepContext* context) joint->anchorB = b2RotateVector(bodySimB->transform.q, b2Sub(base->localOriginAnchorB, bodySimB->localCenter)); joint->deltaCenter = b2Sub(b2Sub(bodySimB->center, bodySimA->center), joint->linearOffset); joint->deltaAngle = b2RelativeAngle(bodySimB->transform.q, bodySimA->transform.q) - joint->angularOffset; + joint->deltaAngle = b2UnwindAngle(joint->deltaAngle); b2Vec2 rA = joint->anchorA; b2Vec2 rB = joint->anchorB; @@ -217,6 +217,8 @@ void b2SolveMotorJoint(b2JointSim* base, const b2StepContext* context, bool useB // angular constraint { float angularSeperation = b2RelativeAngle(bodyB->deltaRotation, bodyA->deltaRotation) + joint->deltaAngle; + angularSeperation = b2UnwindAngle(angularSeperation); + float angularBias = context->inv_h * joint->correctionFactor * angularSeperation; float Cdot = wB - wA; @@ -269,8 +271,6 @@ void b2SolveMotorJoint(b2JointSim* base, const b2StepContext* context, bool useB bodyB->angularVelocity = wB; } - - #if 0 void b2DumpMotorJoint() { diff --git a/src/prismatic_joint.c b/src/prismatic_joint.c index 7fb6052f..ddff0a7e 100644 --- a/src/prismatic_joint.c +++ b/src/prismatic_joint.c @@ -10,7 +10,6 @@ // needed for dll export #include "box2d/box2d.h" -#include "box2d/debug_draw.h" #include @@ -584,11 +583,11 @@ void b2DrawPrismaticJoint(b2DebugDraw* draw, b2JointSim* base, b2Transform trans b2Vec2 axis = b2RotateVector(transformA.q, joint->localAxisA); - b2HexColor c1 = b2_colorGray70; + b2HexColor c1 = b2_colorGray7; b2HexColor c2 = b2_colorGreen; b2HexColor c3 = b2_colorRed; b2HexColor c4 = b2_colorBlue; - b2HexColor c5 = b2_colorGray40; + b2HexColor c5 = b2_colorGray4; draw->DrawSegment(pA, pB, c5, draw->context); diff --git a/src/revolute_joint.c b/src/revolute_joint.c index b08239d5..59c16aa5 100644 --- a/src/revolute_joint.c +++ b/src/revolute_joint.c @@ -12,7 +12,6 @@ // needed for dll export #include "box2d/box2d.h" -#include "box2d/debug_draw.h" #include @@ -501,7 +500,7 @@ void b2DrawRevoluteJoint(b2DebugDraw* draw, b2JointSim* base, b2Transform transf b2Vec2 pA = b2TransformPoint(transformA, base->localOriginAnchorA); b2Vec2 pB = b2TransformPoint(transformB, base->localOriginAnchorB); - b2HexColor c1 = b2_colorGray70; + b2HexColor c1 = b2_colorGray7; b2HexColor c2 = b2_colorGreen; b2HexColor c3 = b2_colorRed; diff --git a/src/shape.c b/src/shape.c index 95c86099..c9df7688 100644 --- a/src/shape.c +++ b/src/shape.c @@ -11,7 +11,8 @@ // needed for dll export #include "box2d/box2d.h" -#include "box2d/event_types.h" + +#include static b2Shape* b2GetShape(b2World* world, b2ShapeId shapeId) { @@ -152,6 +153,7 @@ static b2Shape* b2CreateShapeInternal(b2World* world, b2Body* body, b2Transform b2ShapeId b2CreateShape(b2BodyId bodyId, const b2ShapeDef* def, const void* geometry, b2ShapeType shapeType) { + b2CheckDef(def); B2_ASSERT(b2IsValid(def->density) && def->density >= 0.0f); B2_ASSERT(b2IsValid(def->friction) && def->friction >= 0.0f); B2_ASSERT(b2IsValid(def->restitution) && def->restitution >= 0.0f); @@ -288,6 +290,7 @@ void b2DestroyShape(b2ShapeId shapeId) b2ChainId b2CreateChain(b2BodyId bodyId, const b2ChainDef* def) { + b2CheckDef(def); B2_ASSERT(b2IsValid(def->friction) && def->friction >= 0.0f); B2_ASSERT(b2IsValid(def->restitution) && def->restitution >= 0.0f); B2_ASSERT(def->count >= 4); @@ -691,7 +694,7 @@ void b2CreateShapeProxy(b2Shape* shape, b2BroadPhase* bp, b2ProxyType type, b2Tr b2UpdateShapeAABBs(shape, transform, type); // Create proxies in the broad-phase. - shape->proxyKey = b2BroadPhase_CreateProxy(bp, type, shape->fatAABB, shape->filter.categoryBits, shape->id, forcePairCreation); + shape->proxyKey = b2BroadPhase_CreateProxy(bp, type, shape->fatAABB, shape->filter.categoryBits, shape->filter.maskBits, shape->id, forcePairCreation); B2_ASSERT(B2_PROXY_TYPE(shape->proxyKey) < b2_proxyTypeCount); } @@ -907,7 +910,7 @@ b2Filter b2Shape_GetFilter(b2ShapeId shapeId) return shape->filter; } -static void b2ResetProxy(b2World* world, b2Shape* shape, bool wakeBodies) +static void b2ResetProxy(b2World* world, b2Shape* shape, bool wakeBodies, bool destroyProxy) { b2Body* body = b2GetBody(world, shape->bodyId); @@ -935,7 +938,19 @@ static void b2ResetProxy(b2World* world, b2Shape* shape, bool wakeBodies) { b2ProxyType proxyType = B2_PROXY_TYPE(shape->proxyKey); b2UpdateShapeAABBs(shape, transform, proxyType); - b2BroadPhase_MoveProxy(&world->broadPhase, shape->proxyKey, shape->fatAABB); + + if (destroyProxy) + { + b2BroadPhase_DestroyProxy(&world->broadPhase, shape->proxyKey); + + bool forcePairCreation = true; + shape->proxyKey = b2BroadPhase_CreateProxy(&world->broadPhase, proxyType, shape->fatAABB, shape->filter.categoryBits, + shape->filter.maskBits, shapeId, forcePairCreation); + } + else + { + b2BroadPhase_MoveProxy(&world->broadPhase, shape->proxyKey, shape->fatAABB, shape->filter.maskBits); + } } else { @@ -955,11 +970,21 @@ void b2Shape_SetFilter(b2ShapeId shapeId, b2Filter filter) } b2Shape* shape = b2GetShape(world, shapeId); + if (filter.maskBits == shape->filter.maskBits && + filter.categoryBits == shape->filter.categoryBits && + filter.groupIndex == shape->filter.groupIndex) + { + return; + } + + // If the category bits change, I need to destroy the proxy because it affects the tree sorting. + bool destroyProxy = filter.categoryBits == shape->filter.categoryBits; + shape->filter = filter; // need to wake bodies because a filter change may destroy contacts bool wakeBodies = true; - b2ResetProxy(world, shape, wakeBodies); + b2ResetProxy(world, shape, wakeBodies, destroyProxy); } void b2Shape_EnableSensorEvents(b2ShapeId shapeId, bool flag) @@ -1100,7 +1125,8 @@ void b2Shape_SetCircle(b2ShapeId shapeId, const b2Circle* circle) // need to wake bodies so they can react to the shape change bool wakeBodies = true; - b2ResetProxy(world, shape, wakeBodies); + bool destroyProxy = true; + b2ResetProxy(world, shape, wakeBodies, destroyProxy); } void b2Shape_SetCapsule(b2ShapeId shapeId, const b2Capsule* capsule) @@ -1117,7 +1143,8 @@ void b2Shape_SetCapsule(b2ShapeId shapeId, const b2Capsule* capsule) // need to wake bodies so they can react to the shape change bool wakeBodies = true; - b2ResetProxy(world, shape, wakeBodies); + bool destroyProxy = true; + b2ResetProxy(world, shape, wakeBodies, destroyProxy); } void b2Shape_SetSegment(b2ShapeId shapeId, const b2Segment* segment) @@ -1134,7 +1161,8 @@ void b2Shape_SetSegment(b2ShapeId shapeId, const b2Segment* segment) // need to wake bodies so they can react to the shape change bool wakeBodies = true; - b2ResetProxy(world, shape, wakeBodies); + bool destroyProxy = true; + b2ResetProxy(world, shape, wakeBodies, destroyProxy); } void b2Shape_SetPolygon(b2ShapeId shapeId, const b2Polygon* polygon) @@ -1151,7 +1179,8 @@ void b2Shape_SetPolygon(b2ShapeId shapeId, const b2Polygon* polygon) // need to wake bodies so they can react to the shape change bool wakeBodies = true; - b2ResetProxy(world, shape, wakeBodies); + bool destroyProxy = true; + b2ResetProxy(world, shape, wakeBodies, destroyProxy); } b2ChainId b2Shape_GetParentChain(b2ShapeId shapeId) diff --git a/src/shape.h b/src/shape.h index 213f32af..6eedb41d 100644 --- a/src/shape.h +++ b/src/shape.h @@ -5,8 +5,6 @@ #include "world.h" -#include "box2d/distance.h" -#include "box2d/geometry.h" #include "box2d/types.h" typedef struct b2BroadPhase b2BroadPhase; diff --git a/src/solver.c b/src/solver.c index 521c23db..99fecc6b 100644 --- a/src/solver.c +++ b/src/solver.c @@ -14,13 +14,8 @@ #include "shape.h" #include "solver_set.h" #include "stack_allocator.h" -#include "util.h" #include "world.h" -#include "box2d/color.h" -#include "box2d/event_types.h" -#include "box2d/timer.h" - // for mm_pause #include "x86/sse2.h" @@ -60,9 +55,9 @@ static void b2IntegrateVelocitiesTask(int startIndex, int endIndex, b2StepContex // Apply forces, torque, gravity, and damping // Apply damping. - // ODE: dv/dt + c * v = 0 + // Differential equation: dv/dt + c * v = 0 // Solution: v(t) = v0 * exp(-c * t) - // Time step: v(t + dt) = v0 * exp(-c * (t + dt)) = v0 * exp(-c * t) * exp(-c * dt) = v * exp(-c * dt) + // Time step: v(t + dt) = v0 * exp(-c * (t + dt)) = v0 * exp(-c * t) * exp(-c * dt) = v(t) * exp(-c * dt) // v2 = exp(-c * dt) * v1 // Pade approximation: // v2 = v1 * 1 / (1 + c * dt) @@ -799,6 +794,7 @@ struct b2ContinuousContext float fraction; }; +// todo this may lead to pauses for scenarios where pre-solve would disable collision static bool b2ContinuousQueryCallback(int proxyId, int shapeId, void* context) { B2_MAYBE_UNUSED(proxyId); @@ -953,11 +949,11 @@ static void b2SolveContinuous(b2World* world, int bodySimIndex) // Store this for later fastShape->aabb = box2; - b2DynamicTree_Query(staticTree, box, b2ContinuousQueryCallback, &context); + b2DynamicTree_Query(staticTree, box, fastShape->filter.maskBits, b2ContinuousQueryCallback, &context); if (isBullet) { - b2DynamicTree_Query(movableTree, box, b2ContinuousQueryCallback, &context); + b2DynamicTree_Query(movableTree, box, fastShape->filter.maskBits, b2ContinuousQueryCallback, &context); } shapeId = fastShape->nextShapeId; @@ -1756,13 +1752,13 @@ void b2Solve(b2World* world, b2StepContext* stepContext) { B2_ASSERT(shape->isFast == false); - b2BroadPhase_EnlargeProxy(broadPhase, shape->proxyKey, shape->fatAABB); + b2BroadPhase_EnlargeProxy(broadPhase, shape->proxyKey, shape->fatAABB, shape->filter.maskBits); shape->enlargedAABB = false; } else if (shape->isFast) { // Shape is fast. It's aabb will be enlarged in continuous collision. - b2BufferMove(broadPhase, shape->proxyKey); + b2BufferMove(broadPhase, (b2MovedProxy){shape->proxyKey, shape->filter.maskBits}); } shapeId = shape->nextShapeId; diff --git a/src/solver.h b/src/solver.h index 2f14ea99..1a8de395 100644 --- a/src/solver.h +++ b/src/solver.h @@ -5,7 +5,7 @@ #include "block_array.h" -#include "box2d/math_types.h" +#include "box2d/math_functions.h" #include #include diff --git a/src/solver_set.c b/src/solver_set.c index 96f03945..b81a92c6 100644 --- a/src/solver_set.c +++ b/src/solver_set.c @@ -11,18 +11,16 @@ #include "joint.h" #include "world.h" -#include "box2d/event_types.h" - #include void b2DestroySolverSet(b2World* world, int setIndex) { b2SolverSet* set = world->solverSetArray + setIndex; - b2DestroyBodySimArray(&world->blockAllocator, &set->sims); - b2DestroyBodyStateArray(&world->blockAllocator, &set->states); - b2DestroyContactArray(&world->blockAllocator, &set->contacts); - b2DestroyJointArray(&world->blockAllocator, &set->joints); - b2DestroyIslandArray(&world->blockAllocator, &set->islands); + b2DestroyBodySimArray(&set->sims); + b2DestroyBodyStateArray(&set->states); + b2DestroyContactArray(&set->contacts); + b2DestroyJointArray(&set->joints); + b2DestroyIslandArray(&set->islands); b2FreeId(&world->solverSetIdPool, setIndex); *set = (b2SolverSet){0}; set->setIndex = B2_NULL_INDEX; @@ -42,7 +40,6 @@ void b2WakeSolverSet(b2World* world, int setIndex) b2SolverSet* awakeSet = world->solverSetArray + b2_awakeSet; b2SolverSet* disabledSet = world->solverSetArray + b2_disabledSet; - b2BlockAllocator* alloc = &world->blockAllocator; b2Body* bodies = world->bodyArray; b2Contact* contacts = world->contactArray; @@ -59,10 +56,10 @@ void b2WakeSolverSet(b2World* world, int setIndex) // Reset sleep timer body->sleepTime = 0.0f; - b2BodySim* simDst = b2AddBodySim(alloc, &awakeSet->sims); + b2BodySim* simDst = b2AddBodySim(&awakeSet->sims); memcpy(simDst, simSrc, sizeof(b2BodySim)); - b2BodyState* state = b2AddBodyState(alloc, &awakeSet->states); + b2BodyState* state = b2AddBodyState(&awakeSet->states); *state = b2_identityBodyState; // move non-touching contacts from disabled set to awake set @@ -91,7 +88,7 @@ void b2WakeSolverSet(b2World* world, int setIndex) contact->setIndex = b2_awakeSet; contact->localIndex = awakeSet->contacts.count; - b2ContactSim* awakeContactSim = b2AddContact(&world->blockAllocator, &awakeSet->contacts); + b2ContactSim* awakeContactSim = b2AddContact(&awakeSet->contacts); memcpy(awakeContactSim, contactSim, sizeof(b2ContactSim)); int movedLocalIndex = b2RemoveContact(&disabledSet->contacts, localIndex); @@ -151,7 +148,7 @@ void b2WakeSolverSet(b2World* world, int setIndex) b2Island* island = islands + islandSrc->islandId; island->setIndex = b2_awakeSet; island->localIndex = awakeSet->islands.count; - b2IslandSim* islandDst = b2AddIsland(alloc, &awakeSet->islands); + b2IslandSim* islandDst = b2AddIsland(&awakeSet->islands); memcpy(islandDst, islandSrc, sizeof(b2IslandSim)); } } @@ -198,9 +195,9 @@ void b2TrySleepIsland(b2World* world, int islandId) B2_ASSERT(0 <= island->localIndex && island->localIndex < awakeSet->islands.count); sleepSet->setIndex = sleepSetId; - sleepSet->sims = b2CreateBodySimArray(&world->blockAllocator, island->bodyCount); - sleepSet->contacts = b2CreateContactArray(&world->blockAllocator, island->contactCount); - sleepSet->joints = b2CreateJointArray(&world->blockAllocator, island->jointCount); + sleepSet->sims = b2CreateBodySimArray(island->bodyCount); + sleepSet->contacts = b2CreateContactArray(island->contactCount); + sleepSet->joints = b2CreateJointArray(island->jointCount); // move awake bodies to sleeping set // this shuffles around bodies in the awake set @@ -234,7 +231,7 @@ void b2TrySleepIsland(b2World* world, int islandId) // move body sim to sleep set int sleepBodyIndex = sleepSet->sims.count; - b2BodySim* sleepBodySim = b2AddBodySim(&world->blockAllocator, &sleepSet->sims); + b2BodySim* sleepBodySim = b2AddBodySim(&sleepSet->sims); memcpy(sleepBodySim, awakeSim, sizeof(b2BodySim)); int movedIndex = b2RemoveBodySim(&awakeSet->sims, awakeBodyIndex); @@ -302,7 +299,7 @@ void b2TrySleepIsland(b2World* world, int islandId) // move the non-touching contact to the disabled set contact->setIndex = b2_disabledSet; contact->localIndex = disabledSet->contacts.count; - b2ContactSim* disabledContactSim = b2AddContact(&world->blockAllocator, &disabledSet->contacts); + b2ContactSim* disabledContactSim = b2AddContact(&disabledSet->contacts); memcpy(disabledContactSim, contactSim, sizeof(b2ContactSim)); int movedContactIndex = b2RemoveContact(&awakeSet->contacts, localIndex); @@ -350,7 +347,7 @@ void b2TrySleepIsland(b2World* world, int islandId) b2ContactSim* awakeContactSim = color->contacts.data + awakeContactIndex; int sleepContactIndex = sleepSet->contacts.count; - b2ContactSim* sleepContactSim = b2AddContact(&world->blockAllocator, &sleepSet->contacts); + b2ContactSim* sleepContactSim = b2AddContact(&sleepSet->contacts); memcpy(sleepContactSim, awakeContactSim, sizeof(b2ContactSim)); int movedIndex = b2RemoveContact(&color->contacts, awakeContactIndex); @@ -402,7 +399,7 @@ void b2TrySleepIsland(b2World* world, int islandId) } int sleepJointIndex = sleepSet->joints.count; - b2JointSim* sleepJointSim = b2AddJoint(&world->blockAllocator, &sleepSet->joints); + b2JointSim* sleepJointSim = b2AddJoint(&sleepSet->joints); memcpy(sleepJointSim, awakeJointSim, sizeof(b2JointSim)); int movedIndex = b2RemoveJoint(&color->joints, localIndex); @@ -430,7 +427,7 @@ void b2TrySleepIsland(b2World* world, int islandId) B2_ASSERT(island->setIndex == b2_awakeSet); int islandIndex = island->localIndex; - b2IslandSim* sleepIsland = b2AddIsland(&world->blockAllocator, &sleepSet->islands); + b2IslandSim* sleepIsland = b2AddIsland(&sleepSet->islands); sleepIsland->islandId = islandId; int movedIslandIndex = b2RemoveIsland(&awakeSet->islands, islandIndex); @@ -476,8 +473,6 @@ void b2MergeSolverSets(b2World* world, int setId1, int setId2) setId2 = tempId; } - b2BlockAllocator* alloc = &world->blockAllocator; - // transfer bodies { b2Body* bodies = world->bodyArray; @@ -491,7 +486,7 @@ void b2MergeSolverSets(b2World* world, int setId1, int setId2) body->setIndex = setId1; body->localIndex = set1->sims.count; - b2BodySim* simDst = b2AddBodySim(alloc, &set1->sims); + b2BodySim* simDst = b2AddBodySim(&set1->sims); memcpy(simDst, simSrc, sizeof(b2BodySim)); } } @@ -509,7 +504,7 @@ void b2MergeSolverSets(b2World* world, int setId1, int setId2) contact->setIndex = setId1; contact->localIndex = set1->contacts.count; - b2ContactSim* contactDst = b2AddContact(alloc, &set1->contacts); + b2ContactSim* contactDst = b2AddContact(&set1->contacts); memcpy(contactDst, contactSrc, sizeof(b2ContactSim)); } } @@ -527,7 +522,7 @@ void b2MergeSolverSets(b2World* world, int setId1, int setId2) joint->setIndex = setId1; joint->localIndex = set1->joints.count; - b2JointSim* jointDst = b2AddJoint(alloc, &set1->joints); + b2JointSim* jointDst = b2AddJoint(&set1->joints); memcpy(jointDst, jointSrc, sizeof(b2JointSim)); } } @@ -546,7 +541,7 @@ void b2MergeSolverSets(b2World* world, int setId1, int setId2) island->setIndex = setId1; island->localIndex = set1->islands.count; - b2IslandSim* islandDst = b2AddIsland(alloc, &set1->islands); + b2IslandSim* islandDst = b2AddIsland(&set1->islands); memcpy(islandDst, islandSrc, sizeof(b2IslandSim)); } } @@ -566,7 +561,7 @@ void b2TransferBody(b2World* world, b2SolverSet* targetSet, b2SolverSet* sourceS b2BodySim* sourceSim = sourceSet->sims.data + sourceIndex; int targetIndex = targetSet->sims.count; - b2BodySim* targetSim = b2AddBodySim(&world->blockAllocator, &targetSet->sims); + b2BodySim* targetSim = b2AddBodySim(&targetSet->sims); memcpy(targetSim, sourceSim, sizeof(b2BodySim)); // Remove body sim from solver set that owns it @@ -587,7 +582,7 @@ void b2TransferBody(b2World* world, b2SolverSet* targetSet, b2SolverSet* sourceS } else if (targetSet->setIndex == b2_awakeSet) { - b2BodyState* state = b2AddBodyState(&world->blockAllocator, &targetSet->states); + b2BodyState* state = b2AddBodyState(&targetSet->states); *state = b2_identityBodyState; } @@ -631,7 +626,7 @@ void b2TransferJoint(b2World* world, b2SolverSet* targetSet, b2SolverSet* source joint->localIndex = targetSet->joints.count; joint->colorIndex = B2_NULL_INDEX; - b2JointSim* targetSim = b2AddJoint(&world->blockAllocator, &targetSet->joints); + b2JointSim* targetSim = b2AddJoint(&targetSet->joints); memcpy(targetSim, sourceSim, sizeof(b2JointSim)); } diff --git a/src/table.c b/src/table.c index 24c1f7a3..0c395e22 100644 --- a/src/table.c +++ b/src/table.c @@ -6,7 +6,6 @@ #include "allocate.h" #include "core.h" #include "ctz.h" -#include "util.h" #include #include diff --git a/src/timer.c b/src/timer.c index 40d268c4..05758fba 100644 --- a/src/timer.c +++ b/src/timer.c @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2023 Erin Catto // SPDX-License-Identifier: MIT -#include "box2d/timer.h" +#include "box2d/base.h" #if defined(_WIN32) diff --git a/src/types.c b/src/types.c index 218b7204..3803cb0d 100644 --- a/src/types.c +++ b/src/types.c @@ -19,6 +19,7 @@ b2WorldDef b2DefaultWorldDef() def.jointDampingRatio = 2.0f; def.enableSleep = true; def.enableContinous = true; + def.internalValue = B2_SECRET_COOKIE; return def; } @@ -32,6 +33,7 @@ b2BodyDef b2DefaultBodyDef() def.isAwake = true; def.isEnabled = true; def.automaticMass = true; + def.internalValue = B2_SECRET_COOKIE; return def; } @@ -55,6 +57,7 @@ b2ShapeDef b2DefaultShapeDef() def.filter = b2DefaultFilter(); def.enableSensorEvents = true; def.enableContactEvents = true; + def.internalValue = B2_SECRET_COOKIE; return def; } @@ -63,5 +66,6 @@ b2ChainDef b2DefaultChainDef() b2ChainDef def = {0}; def.friction = 0.6f; def.filter = b2DefaultFilter(); + def.internalValue = B2_SECRET_COOKIE; return def; } diff --git a/src/util.h b/src/util.h deleted file mode 100644 index f2e620f0..00000000 --- a/src/util.h +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Erin Catto -// SPDX-License-Identifier: MIT - -#pragma once - -/// Returns the number of elements of an array -#define B2_ARRAY_COUNT(A) (int)(sizeof(A) / sizeof(A[0])) - -/// Used to prevent the compiler from warning about unused variables -#define B2_MAYBE_UNUSED(x) ((void)(x)) diff --git a/src/wheel_joint.c b/src/wheel_joint.c index 73ccc5f1..8c6f5973 100644 --- a/src/wheel_joint.c +++ b/src/wheel_joint.c @@ -10,7 +10,6 @@ // needed for dll export #include "box2d/box2d.h" -#include "box2d/debug_draw.h" #include @@ -538,10 +537,10 @@ void b2DrawWheelJoint(b2DebugDraw* draw, b2JointSim* base, b2Transform transform b2Vec2 pB = b2TransformPoint(transformB, base->localOriginAnchorB); b2Vec2 axis = b2RotateVector(transformA.q, joint->localAxisA); - b2HexColor c1 = b2_colorGray70; + b2HexColor c1 = b2_colorGray7; b2HexColor c2 = b2_colorGreen; b2HexColor c3 = b2_colorRed; - b2HexColor c4 = b2_colorGray40; + b2HexColor c4 = b2_colorGray4; b2HexColor c5 = b2_colorBlue; draw->DrawSegment(pA, pB, c5, draw->context); diff --git a/src/world.c b/src/world.c index 8fd215f6..c657610e 100644 --- a/src/world.c +++ b/src/world.c @@ -9,7 +9,6 @@ #include "allocate.h" #include "array.h" #include "bitset.h" -#include "block_allocator.h" #include "block_array.h" #include "body.h" #include "broad_phase.h" @@ -23,20 +22,13 @@ #include "solver.h" #include "solver_set.h" #include "stack_allocator.h" -#include "util.h" - -// needed for dll export -#include "box2d/box2d.h" -#include "box2d/color.h" -#include "box2d/debug_draw.h" -#include "box2d/distance.h" -#include "box2d/event_types.h" -#include "box2d/timer.h" #include #include #include +#include "box2d/box2d.h" + _Static_assert(b2_maxWorlds > 0, "must be 1 or more"); b2World b2_worlds[b2_maxWorlds]; @@ -88,6 +80,7 @@ static void b2DefaultFinishTaskFcn(void* userTask, void* userContext) b2WorldId b2CreateWorld(const b2WorldDef* def) { _Static_assert(b2_maxWorlds < UINT16_MAX, "b2_maxWorlds limit exceeded"); + b2CheckDef(def); int worldId = B2_NULL_INDEX; for (int i = 0; i < b2_maxWorlds; ++i) @@ -112,7 +105,6 @@ b2WorldId b2CreateWorld(const b2WorldDef* def) world->worldId = (uint16_t)worldId; world->inUse = true; - world->blockAllocator = b2CreateBlockAllocator(); world->stackAllocator = b2CreateStackAllocator(2048); b2CreateBroadPhase(&world->broadPhase); b2CreateGraph(&world->constraintGraph, 16); @@ -272,7 +264,7 @@ void b2DestroyWorld(b2WorldId worldId) b2DestroyArray(world->solverSetArray, sizeof(b2SolverSet)); - b2DestroyGraph(&world->constraintGraph, &world->blockAllocator); + b2DestroyGraph(&world->constraintGraph); b2DestroyBroadPhase(&world->broadPhase); b2DestroyIdPool(&world->bodyIdPool); @@ -283,7 +275,6 @@ void b2DestroyWorld(b2WorldId worldId) b2DestroyIdPool(&world->islandIdPool); b2DestroyIdPool(&world->solverSetIdPool); - b2DestroyBlockAllocator(&world->blockAllocator); b2DestroyStackAllocator(&world->stackAllocator); // Wipe world but preserve revision @@ -392,7 +383,7 @@ static void b2AddNonTouchingContact(b2World* world, b2Contact* contact, b2Contac contact->colorIndex = B2_NULL_INDEX; contact->localIndex = set->contacts.count; - b2ContactSim* newContactSim = b2AddContact(&world->blockAllocator, &set->contacts); + b2ContactSim* newContactSim = b2AddContact(&set->contacts); memcpy(newContactSim, contactSim, sizeof(b2ContactSim)); } @@ -868,6 +859,10 @@ static bool DrawQueryCallback(int proxyId, int shapeId, void* context) { color = b2_colorWheat; } + else if (bodySim->isBullet && body->setIndex == b2_awakeSet) + { + color = b2_colorTurquoise; + } else if (body->isSpeedCapped) { color = b2_colorYellow; @@ -919,10 +914,10 @@ static void b2DrawWithBounds(b2World* world, b2DebugDraw* draw) const float k_impulseScale = 1.0f; const float k_axisScale = 0.3f; - b2HexColor speculativeColor = b2_colorGray30; + b2HexColor speculativeColor = b2_colorGray3; b2HexColor addColor = b2_colorGreen; b2HexColor persistColor = b2_colorBlue; - b2HexColor normalColor = b2_colorGray90; + b2HexColor normalColor = b2_colorGray9; b2HexColor impulseColor = b2_colorMagenta; b2HexColor frictionColor = b2_colorYellow; @@ -943,7 +938,7 @@ static void b2DrawWithBounds(b2World* world, b2DebugDraw* draw) for (int i = 0; i < b2_proxyTypeCount; ++i) { - b2DynamicTree_Query(world->broadPhase.trees + i, draw->drawingBounds, DrawQueryCallback, &drawContext); + b2DynamicTree_Query(world->broadPhase.trees + i, draw->drawingBounds, b2_defaultMaskBits, DrawQueryCallback, &drawContext); } uint32_t wordCount = world->debugBodySet.blockCount; @@ -1149,6 +1144,10 @@ void b2World_Draw(b2WorldId worldId, b2DebugDraw* draw) { color = b2_colorWheat; } + else if (bodySim->isBullet && body->setIndex == b2_awakeSet) + { + color = b2_colorTurquoise; + } else if (body->isSpeedCapped) { color = b2_colorYellow; @@ -1266,10 +1265,10 @@ void b2World_Draw(b2WorldId worldId, b2DebugDraw* draw) const float k_axisScale = 0.3f; const float linearSlop = b2_linearSlop; - b2HexColor speculativeColor = b2_colorGray30; + b2HexColor speculativeColor = b2_colorGray3; b2HexColor addColor = b2_colorGreen; b2HexColor persistColor = b2_colorBlue; - b2HexColor normalColor = b2_colorGray90; + b2HexColor normalColor = b2_colorGray9; b2HexColor impulseColor = b2_colorMagenta; b2HexColor frictionColor = b2_colorYellow; @@ -1819,7 +1818,7 @@ void b2World_OverlapAABB(b2WorldId worldId, b2AABB aabb, b2QueryFilter filter, b for (int i = 0; i < b2_proxyTypeCount; ++i) { - b2DynamicTree_Query(world->broadPhase.trees + i, aabb, TreeQueryCallback, &worldContext); + b2DynamicTree_Query(world->broadPhase.trees + i, aabb, filter.maskBits, TreeQueryCallback, &worldContext); } } @@ -1894,7 +1893,7 @@ void b2World_OverlapCircle(b2WorldId worldId, const b2Circle* circle, b2Transfor for (int i = 0; i < b2_proxyTypeCount; ++i) { - b2DynamicTree_Query(world->broadPhase.trees + i, aabb, TreeOverlapCallback, &worldContext); + b2DynamicTree_Query(world->broadPhase.trees + i, aabb, filter.maskBits, TreeOverlapCallback, &worldContext); } } @@ -1918,7 +1917,7 @@ void b2World_OverlapCapsule(b2WorldId worldId, const b2Capsule* capsule, b2Trans for (int i = 0; i < b2_proxyTypeCount; ++i) { - b2DynamicTree_Query(world->broadPhase.trees + i, aabb, TreeOverlapCallback, &worldContext); + b2DynamicTree_Query(world->broadPhase.trees + i, aabb, filter.maskBits, TreeOverlapCallback, &worldContext); } } @@ -1942,7 +1941,7 @@ void b2World_OverlapPolygon(b2WorldId worldId, const b2Polygon* polygon, b2Trans for (int i = 0; i < b2_proxyTypeCount; ++i) { - b2DynamicTree_Query(world->broadPhase.trees + i, aabb, TreeOverlapCallback, &worldContext); + b2DynamicTree_Query(world->broadPhase.trees + i, aabb, filter.maskBits, TreeOverlapCallback, &worldContext); } } @@ -2410,7 +2409,7 @@ void b2World_Explode(b2WorldId worldId, b2Vec2 position, float radius, float mag aabb.upperBound.x = position.x + radius; aabb.upperBound.y = position.y + radius; - b2DynamicTree_Query(world->broadPhase.trees + b2_movableProxy, aabb, ExplosionCallback, &explosionContext); + b2DynamicTree_Query(world->broadPhase.trees + b2_movableProxy, aabb, b2_defaultMaskBits, ExplosionCallback, &explosionContext); } #if B2_VALIDATE diff --git a/src/world.h b/src/world.h index 5217fd82..b62e7f18 100644 --- a/src/world.h +++ b/src/world.h @@ -3,7 +3,6 @@ #pragma once -#include "block_allocator.h" #include "bitset.h" #include "broad_phase.h" #include "constraint_graph.h" @@ -11,7 +10,7 @@ #include "island.h" #include "stack_allocator.h" -#include "box2d/callbacks.h" +#include "box2d/types.h" typedef struct b2ContactSim b2ContactSim; @@ -47,7 +46,6 @@ typedef struct b2TaskContext /// management facilities. typedef struct b2World { - b2BlockAllocator blockAllocator; b2StackAllocator stackAllocator; b2BroadPhase broadPhase; b2ConstraintGraph constraintGraph; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 47373e26..4afdc961 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -2,7 +2,6 @@ add_executable(test main.c - test_allocator.c test_bitset.c test_collision.c test_determinism.c diff --git a/test/main.c b/test/main.c index e1522b5e..0624d2ad 100644 --- a/test/main.c +++ b/test/main.c @@ -18,7 +18,6 @@ //} #endif -extern int AllocatorTest(void); extern int BitSetTest(void); extern int MathTest(void); extern int CollisionTest(void); @@ -47,7 +46,6 @@ int main(void) printf("Starting Box2D unit tests\n"); printf("======================================\n"); - RUN_TEST(AllocatorTest); RUN_TEST(MathTest); RUN_TEST(CollisionTest); RUN_TEST(DeterminismTest); diff --git a/test/test_allocator.c b/test/test_allocator.c deleted file mode 100644 index 8ddd8017..00000000 --- a/test/test_allocator.c +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Erin Catto -// SPDX-License-Identifier: MIT - -#include "block_allocator.h" -#include "test_macros.h" - -int BlockAllocatorTest(void) -{ - b2BlockAllocator alloc = b2CreateBlockAllocator(); - - void* data1[64]; - int size1 = 16; - for (int i = 0; i < 64; ++i) - { - data1[i] = b2AllocBlock(&alloc, size1); - } - - void* data2[64]; - int size2 = 3008; - for (int i = 0; i < 64; ++i) - { - data2[i] = b2AllocBlock(&alloc, size2); - } - - void* data3[64]; - int size3 = 16384; - for (int i = 0; i < 64; ++i) - { - data3[i] = b2AllocBlock(&alloc, size3); - } - - for (int i = 63; i >= 0; --i) - { - b2FreeBlock(&alloc, data2[i], size2); - } - - for (int i = 63; i >= 0; --i) - { - b2FreeBlock(&alloc, data1[i], size1); - } - - ENSURE(b2ValidateBlockAllocator(&alloc)); - - for (int i = 0; i < 64; ++i) - { - b2FreeBlock(&alloc, data3[i], size3); - } - - ENSURE(b2ValidateBlockAllocator(&alloc)); - - b2DestroyBlockAllocator(&alloc); - - - return 0; -} - -int AllocatorTest(void) -{ - RUN_SUBTEST(BlockAllocatorTest); - - return 0; -} diff --git a/test/test_bitset.c b/test/test_bitset.c index 4ba8e238..1f9a6c11 100644 --- a/test/test_bitset.c +++ b/test/test_bitset.c @@ -3,7 +3,6 @@ #include "test_macros.h" #include "bitset.h" -#include "box2d/timer.h" #define COUNT 169 diff --git a/test/test_determinism.c b/test/test_determinism.c index dccc1b0e..a11ae4b8 100644 --- a/test/test_determinism.c +++ b/test/test_determinism.c @@ -2,7 +2,6 @@ // SPDX-License-Identifier: MIT #include "box2d/box2d.h" -#include "box2d/geometry.h" #include "box2d/math_functions.h" #include "box2d/types.h" #include "test_macros.h" @@ -176,7 +175,7 @@ void TiltedStacks(int testIndex, int workerCount) enkiDeleteTaskScheduler(scheduler); } -// Test multi-threaded determinism. +// Test multithreaded determinism. int DeterminismTest(void) { // Test 1 : 4 threads diff --git a/test/test_distance.c b/test/test_distance.c index 8942085f..2cbd714b 100644 --- a/test/test_distance.c +++ b/test/test_distance.c @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2023 Erin Catto // SPDX-License-Identifier: MIT -#include "box2d/distance.h" +#include "box2d/collision.h" #include "box2d/math_functions.h" #include "test_macros.h" diff --git a/test/test_math.c b/test/test_math.c index e1e17c54..873722aa 100644 --- a/test/test_math.c +++ b/test/test_math.c @@ -21,20 +21,20 @@ int MathTest(void) v = b2Add(two, two); ENSURE(v.x != 5.0f && v.y != 5.0f); - b2Transform xf1 = {{-2.0f, 3.0f}, b2MakeRot(1.0f)}; - b2Transform xf2 = {{1.0f, 0.0f}, b2MakeRot(-2.0f)}; + b2Transform transform1 = {{-2.0f, 3.0f}, b2MakeRot(1.0f)}; + b2Transform transform2 = {{1.0f, 0.0f}, b2MakeRot(-2.0f)}; - b2Transform xf = b2MulTransforms(xf2, xf1); + b2Transform transform = b2MulTransforms(transform2, transform1); - v = b2TransformPoint(xf2, b2TransformPoint(xf1, two)); + v = b2TransformPoint(transform2, b2TransformPoint(transform1, two)); - b2Vec2 u = b2TransformPoint(xf, two); + b2Vec2 u = b2TransformPoint(transform, two); ENSURE_SMALL(u.x - v.x, 10.0f * FLT_EPSILON); ENSURE_SMALL(u.y - v.y, 10.0f * FLT_EPSILON); - v = b2TransformPoint(xf1, two); - v = b2InvTransformPoint(xf1, v); + v = b2TransformPoint(transform1, two); + v = b2InvTransformPoint(transform1, v); ENSURE_SMALL(v.x - two.x, 8.0f * FLT_EPSILON); ENSURE_SMALL(v.y - two.y, 8.0f * FLT_EPSILON); diff --git a/test/test_shape.c b/test/test_shape.c index 304d1595..b6337a7a 100644 --- a/test/test_shape.c +++ b/test/test_shape.c @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2023 Erin Catto // SPDX-License-Identifier: MIT -#include "box2d/geometry.h" +#include "box2d/collision.h" #include "box2d/math_functions.h" #include "test_macros.h" diff --git a/test/test_table.c b/test/test_table.c index 0959ba43..66423246 100644 --- a/test/test_table.c +++ b/test/test_table.c @@ -5,7 +5,7 @@ #include "table.h" #include "test_macros.h" -#include "box2d/timer.h" +#include "box2d/base.h" #include diff --git a/test/test_world.c b/test/test_world.c index 64063fbd..35228807 100644 --- a/test/test_world.c +++ b/test/test_world.c @@ -2,7 +2,7 @@ // SPDX-License-Identifier: MIT #include "box2d/box2d.h" -#include "box2d/geometry.h" +#include "box2d/collision.h" #include "box2d/math_functions.h" #include "test_macros.h" @@ -15,12 +15,9 @@ // with your rendering engine in your game engine. int HelloWorld(void) { - // Define the gravity vector. - b2Vec2 gravity = {0.0f, -10.0f}; - // Construct a world object, which will hold and simulate the rigid bodies. b2WorldDef worldDef = b2DefaultWorldDef(); - worldDef.gravity = gravity; + worldDef.gravity = (b2Vec2){0.0f, -10.0f}; b2WorldId worldId = b2CreateWorld(&worldDef); ENSURE(b2World_IsValid(worldId)); @@ -68,13 +65,13 @@ int HelloWorld(void) // second (60Hz) and 4 sub-steps. This provides a high quality simulation // in most game scenarios. float timeStep = 1.0f / 60.0f; - int32_t subStepCount = 4; + int subStepCount = 4; b2Vec2 position = b2Body_GetPosition(bodyId); float angle = b2Body_GetAngle(bodyId); // This is our little game loop. - for (int32_t i = 0; i < 90; ++i) + for (int i = 0; i < 90; ++i) { // Instruct the world to perform a single step of simulation. // It is generally best to keep the time step and iterations fixed.