REM 'BBC BASIC for SDL 2.0' implementation of the Hello Box2D example REM by Richard Russell and Svein Svensson. Version 1.0, April 2023. REM (also runs in 'BBC BASIC for Windows'). REM This program demonstrates dropping a small Dynamic Box onto an REM inclined Ground Box, using the 'Box2Dgfx' library for graphics REM and optionally displaying Debug Draw graphics when D is pressed. REM Comments adapted from https://box2d.org/documentation/index.html MODE 8 : OFF INSTALL @lib$+"box2dlib" : PROC_b2Init INSTALL @lib$+"box2dgfx" INSTALL @lib$+"box2ddbg" ON ERROR PROCcleanup : IF ERR=17 CHAIN @lib$+"../examples/tools/touchide" ELSE MODE 8:REPORT:END ON CLOSE PROCcleanup : QUIT REM ----------------------- Creating a World ------------------------ REM Every Box2D program begins with the creation of a b2World object. REM The b2World is the physics hub that manages memory, objects, and REM simulation. Note that Box2D uses Cartesian coordinates, so the REM positive direction of y-coordinates is upwards, from bottom to top. REM Box2D uses S.I. Units: metres, kilograms and seconds. REM It is easy to create a Box2D world. First, we define the gravity: gravity_x = 0.0 gravity_y = -9.8 : REM 9.8 metres/second², negative for downwards REM Now we create the world object: myWorld%% = FN_b2CreateWorld(gravity_x, gravity_y) REM Box2D has been tuned to work well with moving objects in the size REM range 0.1 to 10 metres. With a MODE 8 window (640 x 512 pixels) REM a scaling factor of 100 pixels/metre will give a world dimension REM of 6.4 x 5.1 metres, and the smallest objects should be around 10 REM pixels across. With a higher resolution window, say 1920 x 1200 REM pixels, a scaling factor of 200 pixels per metre might be better. REM Due to the limitations of floating point arithmetic, using Box2D REM to model glaciers or dust particles is not a good idea! scale = 100 REM Initialise the Debug Draw and Box2Dgfx libraries for graphics: PROC_b2DebugInit(myWorld%%, %01011, scale) PROC_gfxInit(gfx{}, @size.x%, @size.y%, scale) REM Now we have our physics world, let's start adding some stuff to it. REM --------------------- Creating a Ground Box ---------------------- REM Most Box2D simulations will have a Ground Box, which represents REM the floor or other surface through which objects cannot penetrate. REM The Ground Box is a static body; static bodies don't collide with REM other static bodies and are immovable. To create the Ground Box REM we need to specify its position, orientation and size: angle = RAD(7.5) : REM angle in radians centre_x = 3.2 : REM 640/100/2 metres centre_y = centre_x * TAN(angle) : REM bottom of window half_width = 3.2 : REM Width 6.4 metres half_height = 0.1 : REM Height 0.2 metres (can be zero if invisible) groundBox%% = FN_b2StaticBox(myWorld%%, centre_x, centre_y, angle, half_width, half_height) REM Load an image which will be used to draw the Ground Box. The REM final parameter is a scaling factor in pixels-per-metre; since REM the object dimensions are 6.4 metres by 0.2 metres and the image REM dimensions are approximately 960 pixels by 30 pixels (visible) REM the appropriate scale is 150 pixels/metre. Once the image has REM been loaded it is associated with the object. PROC_gfxLoad(girder{}, @dir$ + "../strip.png", 150) PROC_b2UserDataBody(groundBox%%, girder{}) REM In BBC BASIC for Windows (only) the image must be pre-multiplied REM (i.e. the RGB channels must be multiplied by the alpha channel). IF INKEY$(-256) = "W" PROC_gfxMultiply(girder{}) REM --------------------- Creating a Dynamic Box --------------------- REM A Dynamic Box represents a rigid object that can move, it consists REM of a 'body' plus one or more 'fixtures'. The 'body' represents REM the object's position, velocity, orientation and damping. The REM 'fixtures' give the object a shape and a mass, they determine how REM it interacts with other objects, e.g. as the result of a collision REM Dynamic boxes are built using the following steps: REM 1. Define a body with position, orientation damping, etc. REM 2. Use the world object to create the body. REM 3. Define fixtures with a shape, friction, density, etc. REM 4. Create fixtures on the body. REM Step 1: Define the initial position, orientation, velocity etc: REM MODE 8 = 640,512 centre_x = 3.2 centre_y = 5.12 : REM place the body at top of the window angle = 0.0 velocity_x = 0.0 velocity_y = 0.0 velocity_a = 0.0 damping_lin = 0.0 damping_ang = 0.0 REM Step 2: Create the dynamic body using the world object: dynamicBody%% = FN_b2DynamicBody(myWorld%%, centre_x, centre_y, angle, \ \ velocity_x, velocity_y, velocity_a, damping_lin, damping_ang) REM Step 3: Define a fixture in the shape of a box. Notice that REM we set the density to 1.0 and the friction to 0.3: half_width = 0.1 half_height = 0.1 friction = 0.3 restitution = 0.0 density = 1.0 REM Step 4. Create and attach the fixture. This updates the mass REM of the body. You can add as many fixtures as you like REM to a body. Each one contributes to the total mass: REM The x,y,angle of the fixture are relative to the body's x,y,angle. REM You can't have a body without a fixture attached to it (no mass). offx = 0.0 offy = 0.0 offangle = 0.0 myFixture%% = FN_b2BoxFixture(dynamicBody%%, offx, offy, offangle, \ \ half_width, half_height, friction, restitution, density) REM Load an image which will be drawn to represent the Dynamic Box, in REM the same way as we did for the Ground Box. This time the object REM dimensions are 0.2 by 0.2 metres and the visible image dimensions REM are 32 x 32 pixels so the scale factor is 160 pixels-per-metre. PROC_gfxLoad(crate{}, @dir$ + "../crate32a.png", 160) PROC_b2UserDataBody(dynamicBody%%, crate{}) REM In BBC BASIC for Windows (only) the image must be pre-multiplied: IF INKEY$(-256) = "W" PROC_gfxMultiply(crate{}) REM That's it for initialization; we are now ready to begin simulating. REM --------------------- Simulating the World ---------------------- REM We have initialized the Ground Box and a Dynamic Box. Now we REM are ready to set Newton loose to do his thing. We just have a REM couple more issues to consider. REM Box2D uses a computational algorithm called an integrator. REM Integrators simulate the physics equations at discrete points of REM time. This goes along with the traditional game loop where we REM essentially have a flip book of movement on the screen. REM So we need to pick a time step. Generally physics engines for REM games like a time step at least as fast as 60 Hz or 1/60 seconds. REM You can get away with larger time steps, but you will have to be REM more careful about setting up the definitions for your world. REM We also don't like the time step to change much. A variable time REM step produces variable results, which makes it difficult to debug. REM So don't tie the time step to your frame rate (unless you really, REM really have to). Without further ado, here is the time step: timeStep = 1/60 REM In addition to the integrator, Box2D uses a larger piece of code REM called a constraint solver. The constraint solver solves all the REM constraints in the simulation, one at a time. A single constraint REM can be solved perfectly. However, when we solve one constraint, REM we slightly disrupt other constraints. To get a good solution, REM we need to iterate over all constraints a number of times. REM There are two phases in the constraint solver: a velocity phase REM and a position phase. In the velocity phase the solver computes REM the impulses necessary for the bodies to move correctly. In the REM position phase the solver adjusts the positions of the bodies to REM reduce overlap and joint detachment. Each phase has its own REM iteration count. In addition, the position phase may exit REM iterations early if the errors are small. REM The suggested iteration count for Box2D is 8 for velocity and REM 3 for position. You can tune this number to your liking, just REM keep in mind that this has a trade-off between speed and accuracy. REM Using fewer iterations increases performance but accuracy suffers. REM Likewise, using more iterations decreases performance but improves REM the quality of your simulation. For this simple example, we don't REM need much iteration. Here are our chosen iteration counts: velIterations% = 6 posIterations% = 2 REM Note that the time step and the iteration count are completely REM unrelated. An iteration is not a sub-step. One solver iteration REM is a single pass over all the constraints within a time step; REM you can have multiple passes over the constraints within a single REM time step. REM We are now ready to begin the simulation loop. In your game the REM simulation loop can be merged with your game loop. In each pass REM through your game loop you call PROC_b2WorldStep(). Just one call REM is usually enough, depending on your frame rate and your time step. REM Here is the simulation loop: @% = &20204 : REM set print to two decimals *REFRESH OFF REPEAT CLS PROC_b2WorldStep(myWorld%%, timeStep, velIterations%, posIterations%) PROC_gfxRender(gfx{}, myWorld%%) : REM draw all the objects IF INKEY(-51) PROC_b2DebugDraw(myWorld%%) : REM Debug Draw graphics REM Get object position in world dimensions: PROC_b2GetBody(dynamicBody%%, xpos, ypos, angle) PRINTTAB(0,0) " xpos = ";xpos " ypos = ";ypos " angle = ";DEG(angle); PRINT " Press the D key for Debug Draw graphics" *REFRESH WAIT 1 UNTIL FALSE REM The output shows the box falling and sliding on the ground box. REM The box colour turns grey when it is no longer affected by forces. END REM When a world is destroyed, all the memory reserved for bodies, REM fixtures and joints is freed. This is to improve performance REM and make your life easier. However any body, fixture, or joint REM pointers you created will have become invalid: DEF PROCcleanup ON ERROR OFF *REFRESH ON myWorld%% += 0 : IF myWorld%% PROC_b2DestroyWorld(myWorld%%) : myWorld%% = 0 PROC_b2DebugExit PROC_gfxExit PROC_b2Exit ENDPROC