From 62b87a13b6bb37b67d431e1f0ea6a9b9d54d33fb Mon Sep 17 00:00:00 2001 From: Erin Catto Date: Mon, 27 May 2024 20:17:29 -0700 Subject: [PATCH] manual first pass complete --- docs/CMakeLists.txt | 10 +- docs/FAQ.md | 106 ++-- docs/{base.md => foundation.md} | 0 docs/images/body_origin.gif | Bin 912 -> 0 bytes docs/images/convex_concave.gif | Bin 2303 -> 0 bytes docs/images/distance_joint.gif | Bin 2358 -> 0 bytes docs/images/distance_joint.svg | 159 ++++++ docs/images/gear_joint.gif | Bin 2119 -> 0 bytes docs/images/prismatic_joint.gif | Bin 2298 -> 0 bytes docs/images/prismatic_joint.svg | 184 +++++++ docs/images/pulley_joint.gif | Bin 3526 -> 0 bytes docs/images/revolute_joint.gif | Bin 1920 -> 0 bytes docs/images/revolute_joint.svg | 162 +++++++ docs/images/skinned_polygon.svg | 181 ------- docs/layout.xml | 4 +- docs/loose_ends.md | 183 ++----- docs/migration.md | 6 + docs/overview.md | 6 +- docs/{references.md => reading.md} | 0 docs/{dynamics.md => simulation.md} | 721 +++++++++++++--------------- include/box2d/types.h | 33 +- samples/sample_joints.cpp | 8 +- src/joint.c | 14 +- src/solver.c | 1 + 24 files changed, 955 insertions(+), 823 deletions(-) rename docs/{base.md => foundation.md} (100%) delete mode 100644 docs/images/body_origin.gif delete mode 100644 docs/images/convex_concave.gif delete mode 100644 docs/images/distance_joint.gif create mode 100644 docs/images/distance_joint.svg delete mode 100644 docs/images/gear_joint.gif delete mode 100644 docs/images/prismatic_joint.gif create mode 100644 docs/images/prismatic_joint.svg delete mode 100644 docs/images/pulley_joint.gif delete mode 100644 docs/images/revolute_joint.gif create mode 100644 docs/images/revolute_joint.svg delete mode 100644 docs/images/skinned_polygon.svg rename docs/{references.md => reading.md} (100%) rename docs/{dynamics.md => simulation.md} (76%) diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index ebd64f7e..72412269 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -7,7 +7,6 @@ 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) @@ -37,18 +36,17 @@ set(DOXYGEN_HTML_COLORSTYLE DARK) 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" "samples.md" - "base.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 cadd74b3..cc32e288 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -1,74 +1,67 @@ -# FAQ (Out Of Date) +# 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, shapse 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 derministic under multi-threading. 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/base.md b/docs/foundation.md similarity index 100% rename from docs/base.md rename to docs/foundation.md diff --git a/docs/images/body_origin.gif b/docs/images/body_origin.gif deleted file mode 100644 index cef170764635242ad2523157cca1e9707600465d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 912 zcmZ?wbhEHbOkv1nI3mDs;J|?oA3j{TaKXXBfq{YH!Gi}27AzS6<9Hwd&d%(^aO4yC39-^PHP>_D<2y zoYd7b^D_U`&N+O!q~zW_{!dfuYXhn)n*19>THCxkoO`=f`|G+Ubhb=wpAt1`#vJo` z6Bd{*YFpwmYlg$J=#@Pya@VglSsk}=)0&bkJGaf;K5h5DzCGm!op$*iK6+d^l#!Rs zjgf(|_Owz&Jrg%KkKuv)XHE)S@L-<);5s`yAJcVy4#so5m-tzg891Kj-{50pTKc327Q*H^_AzI}YF^NsrEg%|pQqPyO%+>rkDU@do9p2g0x5+zGW&{sN4eP0j%q4lMun zYJ=EqSDQIMZhU&y`pRzq?c{#Zp!=uq3)a_l^#+&RxPN~C{x12n1-ge13o1oqY@oL=_nrtO2wccyRy# diff --git a/docs/images/convex_concave.gif b/docs/images/convex_concave.gif deleted file mode 100644 index feefdf04b57c9ae4291c004579d3fbd07af85e1c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2303 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 F{|kPZ$aeq$ diff --git a/docs/images/distance_joint.gif b/docs/images/distance_joint.gif deleted file mode 100644 index 3fd44a47b8b5d340bfb6a1bd819598197f1ccdfd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2358 zcmcJR`#;nB1IOQ=?X#=JHrLItNhOg*`6?cn!d$a59V%>bTvBZ6p`uftjhG^Hlw0++ zQj3yJhtlO}(@~Pxx}h8CHl2!G&ROc@TjziHK7W5bUa!~V^~;MJ66C#HcobxVhfMGt z4u_-BXb=Rse*LU2UI$d91-|+A-nM_tF6l^wIBodjKnWoW`izyz*89DZ~H z*jbKT6zobb?K+j!mda<7gExnq-=G?3%s#r5XK%_8))|wWp0?!N&9KGfso!vn10U=U z=Jt(^nNuM^H>cot${egodrJ-ko`RapU?Tlvz*X`8C+SEC(KSe9{Zvd_mr03i?#d$!u|`HV3pE21fzGMHvm%>H_{j@fh%Ik#PnnsfAUnT} z)onE`A zhrxHWrMlXXppQ&MgRyYrXjm?L$6wlcKNmwN;;R^jC-rVy5Goal;=r_U5ri{mX#DMn z7AP$S?$Jxq@jJ*`*n|o+(3(N9q@+jm>J**CtM8UN>}9{k;gXbERzZFmSB#sQf^g5> z(&Vz%wxua_-{xJQFz1o}E$#quP$MG%o_hM5){LNhhVQhzEGix z#V3cswoDO%YM$|)J@q{dbx~m+p)9ahCL=Y561^(E7PG?DQG?50u%sKX82c0;> zSGLrMd?mN6WYev-l$EUzC{Q+PyWLbDxCN2B*?6#g?$4LzE@I))UK@|fQRhhSI^zReLpAXu|~1nmPUg*hUZTg%yoB3#dE_e&ulj-y!V(t&}) zAh0ecr5p^a%*xIUeqU>)gw_ewzM!vF<56Zr*1=5IyAA=QYs>u%o3QB{5k#X8l;O-fkO4R|5RN)cG#EM_Z;Ma!NGE)QRA$3V{U~ejv zmOd8?)FmOV%=xG`!WssG#l?V%`d4)n#|2uKYfbB0fo{6OD?fgNHt#pB6wbFIt&4Lv zt)&&wU2Xv5LRNl{A=(%pvdWbwSb^I5!g(8IiR0n5cFL)zsB6KROHO4poFgjrtQv`= zpC~-D`WdiM++nxZg|N#AmgI$ziH;w=AVvpzyvl5OL?^7z~|W5p;0v%~r0&YBH2Ve*N4R+oF#$x!pfAd*j*pzM$TEi8`3dBZfyBc|(#mwnP+8lvlx<9OlhCqu zrDa(}bhoLUpxK|gr#_?iW7|dN@_h+=8j1&@xW$K>?dI#s!{dmQx{@Kg<5!DJX4gY5 z?R@M~4)EwY>rENfuv&c1(H1zORBq`Q2CbL??+nG&zr%L#2Nt=7ae^7lk@-9-Xr~fT4}Z=alGO9L4p-%oE!ipAYJH>M!cvy*Com1gr>2(Or={Ne zw$#uVa`(Qdim#)|PSq(Tr!pFry?ET(Fcxj3-#eRL_M|eF>C@&_-Q=!>wtb%I$8@c$ wEr@tOc5`N6uYFVUjsAgTpR3JvMcFOlh;7H-Z|MIW` + + + + + + + + + + + + + + + + anchorB + bodyB + bodyA + anchorA + length + + diff --git a/docs/images/gear_joint.gif b/docs/images/gear_joint.gif deleted file mode 100644 index 9737d7dc478e89d67c665dcba6c1a6a6b0662529..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2119 zcma)-`#+P51IM3dH=El=bE~!HGNu-jbwhdF)+nqjx0Hn(WT)wdReIbdWhl2;b2)M; z4MpkpV9CmE1Cg)U*BX(6AFFd<= zn16Oxms-@mdba*f*ez;Qg_~5X2;&07oU;KXg<*U|bE1qcLUN0YedvtU{`rdwga>o?o z%S^_n`I1??xgYg&2Q({b?Z_W*DX{dSDHsO?vty%r2++959@a?(W zVtTT+idnwu*ZZZ1cJ&^$J`j7Za?RuIgBF}JIEtB?(BuPyyc(BCw@?u|{)U-8io`P= zX@Zs-peVA0t8eu{C!FYG_+&O{ft7Bjf9K@_ZPCx8n!a{6{b7G_wE33n(@VP&_RjC0 z*&8A%xtVQek^&$-FxJ^v-yDH@0!5M9*mK@$I2~6EQb?jOZ*U0Eho}Gm+A5L^2EmV?sySB$^^9=j-xj;5WFUM6qOQXWfcc)m6Yuei+uME9JM`1mT2sFJgF1v}uX!ot zAydO&DO|~h2Q%P{%SmRRa*$6$VH0-9zMs+-7W9zhezE4O&d%g0ngrH@LdF2i{~uv# zPDc8kbOc5Gle~Mq38zMoE+oM<=?){=GE=Kq?!DZ_RBog>x^ci0^ibw`f>RYT+Hrx* zZWPb171R+hc!{-_9_4z*0Xi$Jci-?-XsT=EL`INALy~9|cv7S6tsdL(CN@f(k6buU z0+ussa&Qc)Ea0Znb7!foB-rGPhX78EWGuv^{4`fBtLX530a)t^HE+Fey50c2-IN4W zWUc>VGh|d7My2Wn_tw!j~woYkkqSi2yrivpBF7TcLr#9}s*!ntrJmYfs zuCL78wtQ^sru79UFB)OwS*=}B_P|r1uF`dKUvsBQSa|vVU;aB6q|Y5O`Ty))7O%^C z{+4LFN)4cQ+@d4Nx1A&}(E=+qyzebe;$Ve+b$}IN8XaL@Niq96m9{sa!3vDtHUXRC z6scK-6U8*-;bscLC%yt@iDV)h`rIwA9D|4a3${!tgV?~uj>7)Vq(zwWs{DJxP8os< zb$;DJ+GcBs)KnjT|%Z9wlNM>#~=m=vj3f));4hlLL8l zHsX4_?nu!$!|RjKxUMegnBrhhnbT1xU*=iZ=;~f;WZZqN*)2<*-&Z*=_LhnnS1!MZ5W4pW5%3ToX)_jv5>kcShh%dk2>3>PsXMrYeet|1i1 zjQnz$E=hfbPk~a+*`ow{GQq4=m=i|(ozaWC_PkBy*}TMOT579cPW(*1U8h;pAz%lx z0#9;BJkjqub+q28^vv*g8hYhe8)VlK2#& zMJn$y*5u0>99y9o%)@y2#+J!OUQ_di0i0qRwmQu4mmKg{m%*I1xHy@**bIIV;cTyQ z!=OI!yOre8^Gy~|FzXCzzL^V9GV3Gyk@D}qTXiP8b*#)oTZXOPkIsqm;2do)8eV+Bfk3VA>rJRT+s_KEM zcTH>*0N>_(s4UuF^UDS-g60O>KL&i8Z4bPQ#{=}5X&UFLvo4w{d2s2ol8>H?4ySzM ztNHZ--MjCH+09lzFMztTmBt#XC9x`6zG4GGpAw573 z@X0of%Yn!&fyoEY7O{zGbKYuHr2+?@*%ge5{^P7iAQCc{X8=Z7)Cv5vJ0EqgZnokM znOKUH(rO}#vxul9@q);qiE*t?FA^ir?Hg<4!0}WFs7jUQGqVE~J4P*|J3U*Z{%gDL ztlz9JUY9i-qvS%K04NE9_biXG^BD>#stU5fOO$2*-f}%FlV>)uGD@uaEy4FSCxN=D z?#@TYjI{}mvCg4AhKGBo)<4v!pliKFH(5D_O``@sb@aKFv+_b{8lwv`h3!T#KPW=? zbo;e%gS0lGB{SN5IJAF7YtreYi73;_xx4PONrgd_7%Sp)g%4-CXj4^;t)qp)KWDnc hm9l4fU|8Sw{^_%hReKzqXC4R)50ndYvq=Ep`afX+la&Af diff --git a/docs/images/prismatic_joint.gif b/docs/images/prismatic_joint.gif deleted file mode 100644 index ac2037f6f0c67c970f05a2f2f34595aea2e4e211..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2298 zcmcK2i#t^L1IO|2nR5(A=4K{$PUEsF%M@FP9EHXFJr7nQ?V>9UB{~tgNKbXklStqoboJiVh46 zn3|dScf-{zcgv0G#>aCkV|l^%})&Kb#OX2sGn z?qi@v*Gx@IW5SU5bOBBYGhUH<)>IFXl$D3bhl^bJ+5lWvhF}N#|lwFYsU5GzDgE~wL zw;zUZ+<6zJ_t{zfR22ab)}RHWQ(PLTreh_TE_HXWyX2^F^ZVy*CWRwuhi-A8BLC^@ zNM^>3GgHM`>vxQw49xSIEYD{ z1pN`Og%@w-1NOC^Hx#-4^*5W`8_(xGw}23ojv1>91!DSMG?5P(pi;;T3KFLkOUQh*|alTHiwj(Sks_SLM7ZXF={QK&oc->FX&$NkXQ4w0=iw}AaR0_>qn zwrYWyaPRbd7a~ee25Ip53Ts_3;{6;xyfbIqhEw?Q_79S!+<>abBXJR&vV-# zitHy~;Fj^{lBtFf-u9(RauhDFgkDa&s!@I77VS{Tti+zKnn_@PHtlp0R9yDv!VCnr z-keJ;O;I7bo;H~o;L=bfIl1a^Y(?Dv!_IA>QQFQZWB?!k~q?XJ+khmp!*~9fvDkaw*g}(5+)&y1$h# zOQN@0?U@u6>gVmT{*i*!(Cr1{V~aH@7Y-`a&{@5}6p)=`FVt5m97Fg7MD4;X5n`jv zQO45kGFt7@)y8jcEG*w-37__II8Ja225^Ah8+P5d&{~+OHH~~M)^9l=^Ueo#zY+`H z2jn7=(t5w^cdu#!1+r|*!6;7c!o!C~1gRLV8(B~8^c;yh{pg{)nU=~ePqWC>c6+sB zdd#+jWQHcIJ180puz*)=(U@jssVNRP+&jCv=Cu9q!+KT&sl39x<;9rW_ zLCS$BQgL0&Akf9II~e}-@g5AQ%UAnVkx5!H`N@8hk#J_X*wz!_?Rk62L_Fn5dFe}(kgkri(> zD`hr%A+xE&&&N9~Iz?~k4F}CD6zwhe+?wF%l;PI<^{{JSrS~XhW&1=zvBixu?0YW* ziRvhLPvkimQ32z1=r>cd1(n2By`CwX>OPsz21_%FX3$GEo6zmrTAv8?M8uUH}7cnqzKC%JA~iWFa8sdzcp7bT)R|p8FS_^G9C=JH+0;v$5=J;4H^CFFhMLa?LKiB+xKCSb*pH9ERlQe)imodPoZn z!w6FHZO9Ec^U-?}=?g_QT_8@^>ZsR!%xm#4|Fk#H$G*nUxOdz3GI+;vA36Y={s%VJ BwC(@^ 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 ad8d4fbfff15afc67ff63e8c3359547dedcc1d0d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3526 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 diff --git a/docs/images/revolute_joint.gif b/docs/images/revolute_joint.gif deleted file mode 100644 index 39b74e285b3acba70a186910df0dfeac74162435..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1920 zcmcJKi#wG00)T(t%r{qt!C+{N&oxQg9H%;y#Ml`nDYtP=8Fms6yC$)XLZ4e$#mDx9 zZ0;+OOwv|XeB)9I8B#9Q`r1>%scls1$=Pzwp8w&zf57`b@6KI2gj=HuK@@n102D<5 z03wk{tJUH--qX|L?(Tl##ECa=-ptO<*3{I}>rL`Fth zT3Y(~`C%AVTwJ_y+9Rr)+P`L5)%_w|Hps*|39og!4LolaQZ9p*At+w ziZF$jlX>xAL(<)ZEINE4*}FBoE$}n~)W>=VMG#agVV`MIj(itF11GKCUW|nQ=*rMP z=EPk?Ny(=}lNz?h^Y?)U$i$*UkWkCAn^*@m1G3TqQjPx2Vrz(!e~n z>%Ru5d2E+Gz0_gM=z5n@SPUTsCv9N37r0dxTS+`?ROmAm}Vaww;^JVXf z#0CBUszSKDz`nQY4LV`QX&sy-`EPe#ot%f4q$*5LI+jIDOMiw(Qa);=A8x#Ny(?c5 zHi2lsr&#dM7{5h;d-J+0*39tM8Leal$wQ8 zXrfXNh(ZtI6s24edciIS%AOV=0Izd2QeaS)k?4758fEY(pbFcC5Q!I|v`DpRyfBOK zWM;T6w4xMM*sPSJ&><@Oo8uA2T+0n+N=#jT$YgUgi976-SG<*}MAI$iE_jnuh=Rti z4LCKY&T*0hMAS!CY>47*z#yT`GL@PFDOGAJohR}*$>GXyDx;iPKx1)<%*3C!AZW}v zemOy-(@WKtDGlSzwq|}Sp(G<%g-4$Ypw_x@BG7tTq+2jf)pf%Cu1Z3i^iR)5m}Ww{ zfNg$VoW@;!=6p7K2I=&4z<%#+o;bF?%Lk_qpK3P8)Z7})v8zqCUfZ>*JU5lT!3Ij_S3nMp58q3GYRe`pn6xS=0d$k61O?O*s^ zj;%ADo4UW{Zj6NA#b{sAx@a1_vP89Auo5p^{qpBOJ$pT1$9)C^+Uqo$Hjz!^&N@$- zI+GTofH96{Fl*brhVx09BLv@80|i*^a_e!JD1%g}>uig`4OKBhc5wC2-PyZHv%Plv1kDl0kgYFfHQ< zouMK!$E?;lsbAuQ*kx2vS(E)srKNPw>$010*qm)_8wTLZIlFxmY6^YlQ8J?WsYxpO ziit7dGe>xhB1(CK2DL0)D4@Ucr^DJA&}cUDNFXBBcj-bI0S%qTWWYjf|G|hl%dBv) zYrpjLNW@DkhqN5|d>H7T+odC(xhf3r8&uTw`o<|JHRVd&#%7mBCw6T`>jJvY3e{;g zn@eH)kztJCznhyF41e}a&zEI33i|EN>k)Y-bxBndW4B2rngiCtit&TEiaEv0bqt$+ zP_%?GhZE}eckUdk`;cF1VQBxi^PQ)t^Y7hO>K%pl=y1tEU<;8CHkz4w)MY))T7F+F zA>$D5=5lMy>xh67 dwfkzs)8z}x?IfZl($lmBk4AGoGDu+Ce*oZg0R8{~ 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/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/layout.xml b/docs/layout.xml index e93b6cf3..6a4923a6 100644 --- a/docs/layout.xml +++ b/docs/layout.xml @@ -3,9 +3,9 @@ - + - + diff --git a/docs/loose_ends.md b/docs/loose_ends.md index 03ad5c2d..3ff62f77 100644 --- a/docs/loose_ends.md +++ b/docs/loose_ends.md @@ -1,157 +1,50 @@ -# Loose Ends (Out Of Date) +# Loose Ends ## User Data -The `b2Fixture`, `b2Body`, and `b2Joint` classes allow you to attach user data -as a uintptr_t. This is handy when you are examining Box2D data +Bodies, shapes, and joints allow you to attach user data +as a `void*`. This is handy when you are examining Box2D data structures and you want to determine how they relate to the objects in your game engine. -For example, it is typical to attach an actor pointer to the rigid body -on that actor. This sets up a circular reference. If you have the actor, -you can get the body. If you have the body, you can get the actor. +For example, it is typical to attach an entity pointer to the rigid body +on that entity. This sets up a circular reference. If you have the entity, +you can get the body. If you have the body, you can get the entity. -```cpp -GameActor* actor = GameCreateActor(); -b2BodyDef bodyDef; -bodyDef.userData.pointer = reinterpret_cast(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,32 @@ 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 lenght 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 joints. So you may see joint stretching on fast moving objects. Normally this recovers. +4. Box2D uses the symplectic Euler integration scheme. It does not reproduce exact parabolic motion of projectiles and has only first-order accuracy. However it is fast and has good stability. +5. Box2D uses a squential solver to provide 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..755f02d9 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -286,6 +286,12 @@ 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`. This is very 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 diff --git a/docs/overview.md b/docs/overview.md index 54ed2520..ad08e3e6 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -28,8 +28,8 @@ should be comfortable with compiling, linking, and debugging. ## Scope This manual covers the majority of the Box2D API. However, not every -aspect is covered. Please look at the samples application 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. @@ -120,9 +120,11 @@ 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 diff --git a/docs/references.md b/docs/reading.md similarity index 100% rename from docs/references.md rename to docs/reading.md diff --git a/docs/dynamics.md b/docs/simulation.md similarity index 76% rename from docs/dynamics.md rename to docs/simulation.md index 1aa309ae..058539b5 100644 --- a/docs/dynamics.md +++ b/docs/simulation.md @@ -18,7 +18,7 @@ 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. -## Handles (Ids) +## 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. @@ -26,6 +26,10 @@ Box2D works differently. Instead of pointers, you are given an *id* when you cre 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 pointer. Box2D creation functions return an id. Functions that operate on Box2D objects take ids. @@ -591,6 +595,31 @@ for (int i = 0; i < returnCount; ++i) 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. + +```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.* @@ -691,7 +720,7 @@ 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 {#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 @@ -702,17 +731,30 @@ filtering using categories and groups. Box2D supports 32 collision categories. For each shape you can specify which category it belongs to. You also specify what other categories this shape 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: +multiplayer game that all 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). +This is done with masking bits. For example: ```c +enum MyCategories +{ + PLAYER = 0x00000002, + MONSTER = 0x00000004, +}; + b2ShapeDef playerShapeDef = b2DefaultShapeDef(); b2ShapeDef monsterShapeDef = b2DefaultShapeDef(); -playerShapeDef.filter.categoryBits = 0x00000002; -monsterShapeDef.filter.categoryBits = 0x00000004; -playerShapeDef.filter.maskBits = 0x00000004; -monsterShapeDef.filter.maskBits = 0x00000002; +playerShapeDef.filter.categoryBits = PLAYER; +monsterShapeDef.filter.categoryBits = MONSTER; + +// Players collide with monters, 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: @@ -1033,6 +1075,8 @@ for (int i = 0; i < contactEvents.hitCount; ++i) ``` 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. ### Contact Filtering Often in a game you don't want all objects to collide. For example, you @@ -1040,97 +1084,59 @@ 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. This requires you to implement a -ShouldCollide function that receives two b2Shape pointers. Your function -returns true if the shapes should collide. +Contact filtering is setup on shapes and is covered [here](#filtering). + +### Advanced Contact Handling -The default implementation of ShouldCollide uses the b2FilterData -defined in Chapter 6, Shapes. +#### 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 b2ContactFilter::ShouldCollide(b2Shape* shapeA, b2Shape* shapeB) +bool MyCustomFilter(b2ShapeId shapeIdA, b2ShapeId shapeIdB, void* context) { - const b2Filter& filterA = shapeA->GetFilterData(); - const b2Filter& filterB = shapeB->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; + MyGame* myGame = context; + return myGame->WantsCollision(shapeIdA, shapeIdB); } -``` -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. - -```c -MyContactFilter filter; -world->SetContactFilter(&filter); -// filter remains in scope ... +// Elsewhere +b2World_SetCustomFilterCallback(myWorldId, MyCustomFilter, myGame); ``` -### Advanced Contact Handling - -#### Custom Filtering +This function must be thread safe and must not attempt to read or write 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 current configuration. For example, you can implement a one-sided -platform using this callback and calling b2Contact::SetEnabled(false). +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. The pre-solve -event may be fired multiple times per time step per contact due to -continuous collision detection. +so you will need to disable the contact every time-step. This impl ```c -void PreSolve(b2Contact* contact, const b2Manifold* oldManifold) +bool MyPreSolve(b2ShapeId shapeIdA, b2ShapeId shapeIdB, b2Manifold* manifold, void* context) { - b2WorldManifold worldManifold; - contact->GetWorldManifold(&worldManifold); - if (worldManifold.normal.y < -0.5f) + MyGame* myGame = context; + + if (myGame->IsHittingBelowPlatform(shapeIdA, shapeIdB, manifold)) { - contact->SetEnabled(false); + return false; } + + return true; } -``` -The pre-solve event is also a good place to determine the point state -and the approach velocity of collisions. +// Elswhere +b2World_SetPreSolveCallback(myWorldId, MyPreSolve, myGame); +``` -```c -void PreSolve(b2Contact* contact, const b2Manifold* oldManifold) -{ - b2WorldManifold worldManifold; - contact->GetWorldManifold(&worldManifold); +Note this currently does not work with high speed collisions, so you may see a +pause in those situations. - b2PointState state1[2], state2[2]; - b2GetPointStates(state1, state2, oldManifold, contact->GetManifold()); - - if (state2[0] == b2_addState) - { - const b2Body* bodyA = contact->GetShapeA()->GetBody(); - const b2Body* bodyB = contact->GetShapeB()->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(); - } - } -} -``` +See the `Platformer` sample for more details. ## Joints Joints are used to constrain bodies to the world or to each other. @@ -1139,7 +1145,8 @@ 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. +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 @@ -1150,175 +1157,182 @@ 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 +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 -actually the default behavior and you must set the collideConnected +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. +current body transforms violate the joint constraint. Additionally, some joint +definitions need a reference 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 world. +### 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 -b2World* myWorld; -b2RevoluteJointDef jointDef; -jointDef.bodyA = myBodyA; -jointDef.bodyB = myBodyB; -jointDef.anchorPoint = myBodyA->GetCenterPosition(); +b2RevoluteJointDef jointDef = b2DefaultRevoluteJointDef(); +jointDef.bodyIdA = myBodyA; +jointDef.bodyIdB = myBodyB; +jointDef.localAnchorA = (b2Vec2){0.0f, 0.0f}; +jointDef.localAnchorB = (b2Vec2){1.0f, 2.0f}; -b2RevoluteJoint* joint = (b2RevoluteJoint*)myWorld->CreateJoint(&jointDef); +b2JointId myJointId = b2CreateRevoluteJoint(myWorldId, &jointDef); // ... do stuff ... -myWorld->DestroyJoint(joint); -joint = nullptr; +b2DestroyJoint(myJointId); +myJointId = b2_nullJointId; ``` -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. +It is always good to nullify your ids after they are destroyed. -The lifetime of a joint is not simple. Heed this warning well: +The lifetime of a joint is not simple. 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. -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. See the -section on Implicit Destruction for details. +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 bodies, anchor points, and user data from +First of all, you can get the type, bodies, anchor points, and user data from a joint. ```c -b2Body* b2Joint::GetBodyA(); -b2Body* b2Joint::GetBodyB(); -b2Vec2 b2Joint::GetAnchorA(); -b2Vec2 b2Joint::GetAnchorB(); -void* b2Joint::GetUserData(); +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. This the reaction force -applied to body 2 at the anchor point. You can use reaction forces to +All joints have a reaction force and torque. This is the reaction force +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 b2Joint::GetReactionForce(); -float b2Joint::GetReactionTorque(); +b2Vec2 force = b2Joint_GetConstraintForce(myJointId); +float torque = b2Joint_GetConstraintTorque(myJointId); ``` +See the sample `BreakableJoint` for more details. + ### 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. +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.gif) +![Distance Joint](images/distance_joint.svg) -Here is an example of a distance joint definition. In this case we -decide to allow the bodies to collide. +Here is an example of a distance joint definition. In this case I +decided to allow the bodies to collide. ```c -b2DistanceJointDef jointDef; -jointDef.Initialize(myBodyA, myBodyB, worldAnchorOnBodyA, -worldAnchorOnBodyB); +b2DistanceJointDef jointDef = b2DefaultDistanceJointDef(); +jointDef.bodyIdA = myBodyIdA; +jointDef.bodyIdB = myBodyIdB; +jointDef.localAnchorA = (b2Vec2){1.0f, -3.0f}; +jointDef.localAnchorB = (b2Vec2){0.0f, 0.5f}; +jointDef.length = 4.0f; jointDef.collideConnected = true; + +b2JointId myJointId = b2CreateDistanceJoint(myWorldId, &jointDef); ``` 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. +connection. See the `DistanceJoint` sample to see how this behaves. + +Softness is achieved by enabling the spring and tuning two values in the definition: +Hertz and damping ratio. -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. ```c -void b2LinearStiffness(float& stiffness, float& damping, - float frequencyHertz, float dampingRatio, - const b2Body* bodyA, const b2Body* bodyB); +jointDef.enableSpring = true; +jointDef.hertz = 2.0f; +jointDef.dampingRatio = 0.5f; ``` 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 reason is related to the [Nyquist frequency](https://en.wikipedia.org/wiki/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). - -```c -float frequencyHz = 4.0f; -float dampingRatio = 0.5f; -b2LinearStiffness(jointDef.stiffness, jointDef.damping, frequencyHz, dampingRatio, jointDef.bodyA, jointDef.bodyB); -``` +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 +oscilation. It is also possible to define a minimum and maximum length for the distance joint. -See `b2DistanceJointDef` for details. +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. The revolute joint has a single degree of freedom: +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.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. +![Revolute Joint](images/revolute_joint.svg) -In this example, two bodies are connected by a revolute joint at the -first body's center of mass. +Like all joints, the anchor points are specified in local coordinates. +However, you can use the body utility functions to simplify this. ```c -b2RevoluteJointDef jointDef; -jointDef.Initialize(myBodyA, myBodyB, myBodyA->GetWorldCenter()); +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 CCW about the -angle point. Like all angles in Box2D, the revolute angle is measured in +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 joint -is created using Initialize(), regardless of the current rotation of the +is created, regardless of the current rotation of the two bodies. In some cases you might wish to control the joint angle. For this, the @@ -1329,9 +1343,8 @@ 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 +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?* @@ -1352,8 +1365,12 @@ joint has a limit and a motor enabled. The motor is setup to simulate joint friction. ```c -b2RevoluteJointDef jointDef; -jointDef.Initialize(bodyA, bodyB, myBodyA->GetWorldCenter()); +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; @@ -1364,16 +1381,16 @@ jointDef.enableMotor = true; You can access a revolute joint's angle, speed, and motor torque. ```c -float b2RevoluteJoint::GetJointAngle() const; -float b2RevoluteJoint::GetJointSpeed() const; -float b2RevoluteJoint::GetMotorTorque() const; +float angleInRadians = b2RevoluteJoint_GetAngle(myJointId); +float speed = b2RevoluteJoint_GetMotorSpeed(myJointId); +float currentTorque = b2RevoluteJoint_GetMotorTorque(myJointId); ``` You also update the motor parameters each step. ```c -void b2RevoluteJoint::SetMotorSpeed(float speed); -void b2RevoluteJoint::SetMaxMotorTorque(float torque); +b2RevoluteJoint_SetMotorSpeed(myJointId, 20.0f); +b2RevoluteJoint_SetMaxMotorTorque(myJointId, 100.0f); ``` Joint motors have some interesting abilities. You can update the joint @@ -1383,7 +1400,7 @@ a sine-wave or according to whatever function you want. ```c // ... Game Loop Begin ... -myJoint->SetMotorSpeed(cosf(0.5f * time)); +b2RevoluteJoint_SetMotorSpeed(myJointId, cosf(0.5f * time)); // ... Game Loop End ... ``` @@ -1393,9 +1410,9 @@ You can also use joint motors to track a desired joint angle. For example: ```c // ... Game Loop Begin ... -float angleError = myJoint->GetJointAngle() - angleTarget; +float angleError = b2RevoluteJoint_GetAngle(myJointId) - angleTarget; float gain = 0.1f; -myJoint->SetMotorSpeed(-gain * angleError); +b2RevoluteJoint_SetMotorSpeed(myJointId, -gain * angleError); // ... Game Loop End ... ``` @@ -1408,7 +1425,7 @@ 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) +![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. @@ -1446,107 +1463,12 @@ 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: - -```c -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. - -```c -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. - -```c -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 +The mouse joint is used in the smaples 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, +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 @@ -1567,150 +1489,130 @@ for details. ### 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 +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 iterative so the joints are a bit soft. So -chains of bodies connected by weld joints will flex. +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 will flex. -Instead it is better to create breakable bodies starting with a single -body with multiple shapes. When the body breaks, you can destroy a -shape 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. +See the `ContactEvent` sample for an example of how to merge and split bodies. ### 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. +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 b2WheelJoint, wheel_joint.cpp, -and car.cpp for details. - +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 intestigate 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 -### 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: - -```c -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: +### 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 -```c -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. - } -} -``` +#### Query Filtering +An 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. -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. +Just like shapes, queries themselves can have a category. For example, you can have a `CAMERA` +or `PROJECTILE` category. ```c -b2Body* node = myWorld->GetBodyList(); -while (node) +enum MyCategories { - 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. + STATIC = 0x00000001, + PLAYER = 0x00000002, + MONSTER = 0x00000004, + WINDOW = 0x00000008, + CAMERA = 0x00000010, + PROJECTILE = 0x00000020, +}; -```c -b2Body* node = myWorld->GetBodyList(); -while (node) -{ - b2Body* b = node; - node = node->GetNext(); +// 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; - GameActor* myActor = (GameActor*)b->GetUserData().pointer; - if (myActor->IsDead()) - { - bool otherBodiesDestroyed = GameCrazyBodyDestroyer(b); - if (otherBodiesDestroyed) - { - node = myWorld->GetBodyList(); - } - } -} +// The view collides with the static world, monsters, and players. +b2QueryFilter viewFilter; +viewFilter.categoryBits = CAMERA; +viewFilter.maskBits = STATIC | PLAYER | MONSTER; ``` -Obviously to make this work, GameCrazyBodyDestroyer must be honest about -what it has destroyed. +If you want to query everything you can use `b2DefaultQueryFilter()`; -### AABB 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. You provide an AABB in world coordinates and an -implementation of b2QueryCallback. The world calls your3 with each +#### 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 MyQueryCallback(b2Shape* shape, void* context) +bool MyOverlapCallback(b2ShapeId shapeId, void* context) { - b2Body* body = shape->GetBody(); - body->SetAwake(true); + b2BodyId bodyId = b2Shape_GetBody(shapeId); + b2Body_SetAwake(bodyId, true); // Return true to continue the query. return true; } // Elsewhere ... -MyQueryCallback callback; +MyOverlapCallback callback; b2AABB aabb; - -aabb.lowerBound.Set(-1.0f, -1.0f); -aabb.upperBound.Set(1.0f, 1.0f); -myWorld->Query(&callback, 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); ``` -You cannot make any assumptions about the order of the callbacks. +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 a callback function and providing the -start and end points. The world calls your function with each shape +perform a ray cast by implementing the `b2CastResultFcn()` callback +function and providing the +start 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 @@ -1730,34 +1632,71 @@ ray cast will proceed as if the shape does not exist. Here is an example: ```c -// This struct captures the closest hit shape. +// This struct captures the closest hit shape struct MyRayCastContext { - b2Shape* m_shape; - b2Vec2 m_point; - b2Vec2 m_normal; - float m_fraction; + b2ShapeId shapeId; + b2Vec2 point; + b2Vec2 normal; + float fraction; }; -float ReportShape(b2Shape* shape, const b2Vec2& point, - const b2Vec2& normal, float fraction) +float MyCastCallback(b2ShapeId shapeId, b2Vec2 point, b2Vec2 normal, float fraction, void* context) { - m_shape = shape; - m_point = point; - m_normal = normal; - m_fraction = fraction; + 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 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 and 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 ... -MyRayCastCallback callback; -b2Vec2 point1(-1.0f, 0.0f); -b2Vec2 point2(3.0f, 1.0f); -myWorld->RayCast(&callback, point1, point2); +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); ``` -> **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. +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. diff --git a/include/box2d/types.h b/include/box2d/types.h index a18c4e28..efece70f 100644 --- a/include/box2d/types.h +++ b/include/box2d/types.h @@ -323,9 +323,6 @@ typedef struct b2ShapeDef /// Enable hit events for this shape. Only applies to kinematic and dynamic bodies. Ignored for sensors. bool enableHitEvents; - /// Enable custom shape pair collision filtering. - bool enableCustomFiltering; - /// 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. bool enablePreSolveEvents; @@ -509,8 +506,8 @@ typedef struct b2DistanceJointDef /// User data pointer void* userData; - /// A cookie used internally to detect a valid definition - int32_t secretCookie; + /// Used internally to detect a valid definition. DO NOT SET. + int32_t internalValue; } b2DistanceJointDef; /// Use this to initialize your joint definition @@ -550,8 +547,8 @@ typedef struct b2MotorJointDef /// User data pointer void* userData; - /// A cookie used internally to detect a valid definition - int32_t secretCookie; + /// Used internally to detect a valid definition. DO NOT SET. + int32_t internalValue; } b2MotorJointDef; /// Use this to initialize your joint definition @@ -589,8 +586,8 @@ typedef struct b2MouseJointDef /// User data pointer void* userData; - /// A cookie used internally to detect a valid definition - int32_t secretCookie; + /// Used internally to detect a valid definition. DO NOT SET. + int32_t internalValue; } b2MouseJointDef; /// Use this to initialize your joint definition @@ -657,8 +654,8 @@ typedef struct b2PrismaticJointDef /// User data pointer void* userData; - /// A cookie used internally to detect a valid definition - int32_t secretCookie; + /// Used internally to detect a valid definition. DO NOT SET. + int32_t internalValue; } b2PrismaticJointDef; /// Use this to initialize your joint definition @@ -731,8 +728,8 @@ typedef struct b2RevoluteJointDef /// User data pointer void* userData; - /// A cookie used internally to detect a valid definition - int32_t secretCookie; + /// Used internally to detect a valid definition. DO NOT SET. + int32_t internalValue; } b2RevoluteJointDef; /// Use this to initialize your joint definition. @@ -780,8 +777,8 @@ typedef struct b2WeldJointDef /// User data pointer void* userData; - /// A cookie used internally to detect a valid definition - int32_t secretCookie; + /// Used internally to detect a valid definition. DO NOT SET. + int32_t internalValue; } b2WeldJointDef; /// Use this to initialize your joint definition @@ -845,8 +842,8 @@ typedef struct b2WheelJointDef /// User data pointer void* userData; - /// A cookie used internally to detect a valid definition - int32_t secretCookie; + /// Used internally to detect a valid definition. DO NOT SET. + int32_t internalValue; } b2WheelJointDef; /// Use this to initialize your joint definition @@ -1024,7 +1021,7 @@ typedef struct b2ContactData /// 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); +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 diff --git a/samples/sample_joints.cpp b/samples/sample_joints.cpp index 1169c1af..05869d89 100644 --- a/samples/sample_joints.cpp +++ b/samples/sample_joints.cpp @@ -1473,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 @@ -1481,7 +1481,7 @@ class ReactionForce : public Sample e_count = 6 }; - explicit ReactionForce(Settings& settings) + explicit BreakableJoint(Settings& settings) : Sample(settings) { if (settings.restart == false) @@ -1702,7 +1702,7 @@ class ReactionForce : public Sample static Sample* Create(Settings& settings) { - return new ReactionForce(settings); + return new BreakableJoint(settings); } @@ -1710,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/src/joint.c b/src/joint.c index 41b987f6..675a8dea 100644 --- a/src/joint.c +++ b/src/joint.c @@ -21,7 +21,7 @@ b2DistanceJointDef b2DefaultDistanceJointDef() b2DistanceJointDef def = {0}; def.length = 1.0f; def.maxLength = b2_huge; - def.secretCookie = B2_SECRET_COOKIE; + def.internalValue = B2_SECRET_COOKIE; return def; } @@ -31,7 +31,7 @@ b2MotorJointDef b2DefaultMotorJointDef() def.maxForce = 1.0f; def.maxTorque = 1.0f; def.correctionFactor = 0.3f; - def.secretCookie = B2_SECRET_COOKIE; + def.internalValue = B2_SECRET_COOKIE; return def; } @@ -41,7 +41,7 @@ b2MouseJointDef b2DefaultMouseJointDef() def.hertz = 4.0f; def.dampingRatio = 1.0f; def.maxForce = 1.0f; - def.secretCookie = B2_SECRET_COOKIE; + def.internalValue = B2_SECRET_COOKIE; return def; } @@ -49,7 +49,7 @@ b2PrismaticJointDef b2DefaultPrismaticJointDef() { b2PrismaticJointDef def = {0}; def.localAxisA = (b2Vec2){1.0f, 0.0f}; - def.secretCookie = B2_SECRET_COOKIE; + def.internalValue = B2_SECRET_COOKIE; return def; } @@ -57,14 +57,14 @@ b2RevoluteJointDef b2DefaultRevoluteJointDef() { b2RevoluteJointDef def = {0}; def.drawSize = 0.25f; - def.secretCookie = B2_SECRET_COOKIE; + def.internalValue = B2_SECRET_COOKIE; return def; } b2WeldJointDef b2DefaultWeldJointDef() { b2WeldJointDef def = {0}; - def.secretCookie = B2_SECRET_COOKIE; + def.internalValue = B2_SECRET_COOKIE; return def; } @@ -75,7 +75,7 @@ b2WheelJointDef b2DefaultWheelJointDef() def.enableSpring = true; def.hertz = 1.0f; def.dampingRatio = 0.7f; - def.secretCookie = B2_SECRET_COOKIE; + def.internalValue = B2_SECRET_COOKIE; return def; } diff --git a/src/solver.c b/src/solver.c index ca005acd..99fecc6b 100644 --- a/src/solver.c +++ b/src/solver.c @@ -794,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);