Skip to content

Physics guide

MelvMay-GG edited this page Feb 20, 2013 · 13 revisions

Introduction

Box2D is a software-only physics engine that provides specialized 2D rigid-body physics. More information on this library can be found here. Most of the physics features exposed by Torque 2D have an analogy in Box2D therefore its manual is a good source of information. Finally, this document won't described what concepts like "density", "restitution" or "collision shapes" mean as you can find those described in the Box2D manual.

World Units

Like other game engines, Torque 2D use logical, floating-point world units. In Torque 2D, just like Box2D, these represent metres i.e. a single world unit is a one metre. This is important to understand as dynamic physics objects need to be within a restricted size to respond correctly as a rigid-body. Here's a quote from the Box2D manual:

"Box2D works with floating point numbers and tolerances have to be used to make Box2D perform well. These tolerances have been tuned to work well with meters-kilogram-second (MKS) units. In particular, Box2D has been tuned to work well with moving shapes between 0.1 and 10 meters. So this means objects between soup cans and buses in size should work well. Static shapes may be up to 50 meters long without trouble."

The physics size on the screen is dependent upon the view size of the camera. If you are having trouble understanding that and create your objects so that each pixel on the screen displays a single world unit then don't be surprised if dynamic rigid-body objects don't respond correctly. This completely down to Box2D and not Torque 2D but Box2D has good reasons for this. If your object is 500x500 world-units in size then the object is 500 metre x 500 metre in size which is huge! Whilst the effects of such large objects is minimal for static objects, dynamic objects can suffer from both accuracy problems as well as the need to apply huge forces to make them move.

Mounting

In your come from the legacy Torque 2D product then you might be wondering where the "mounting" feature for scene objects has gone. Mounting has been replaced by the use of physics joints however it should be clear that while a fully rigid mount does exist in the form of a weld-joint, this may still provide less than satisfactory results in all cases.

For absolutely rigid mounting you should consider using the CompositeSprite as it is essentially a single object that is composed of multiple sprites.

Ticking and Interpolation

It's worth briefly explaining how the simulation is updated within the engine so you get a sense of how a physics update is separated from a frame being rendered.

The physics is updated every 1/60th of a second whereas the frames render as fast as they can. This 1/60th of a second is known as a "tick", in this case a physics tick. This means that in many cases the frame is rendered many times "in between" the physics being updated. This would seem to be useless i.e. why rendering the same objects at the same positions if the positions only change very 1/60th of a second!

The reason for this is that whilst the objects' positions (etc) do indeed only update every 1/60th second, the system takes the opportunity to interpolate state such as an objects position/angle from its last position/angle to the new position/angle. In other words, the actual game world is updating in steps every 1/60th second and in between these steps the engine is rendering things like position/angle using interpolation. The main benefit of this is that it drastically improves performance as interpolation is far cheaper than performing the physics simulation each frame. A secondary benefit is that this render interpolation provides extremely smooth motion. Indeed, the higher the frame-rate, the smoother the motion. A tertiary benefit is that physics simulations typically need a consistent and regular time update and at 1/60th second, it provides a fine enough update interval for a good simulation.

This difference between things like position/angle is exposed in a few places in the API. For instance, you can retrieve an objects position/angle as expected but you can also retrieve its "render" position/angle. The "render" version is the position/angle at that frame and not its real position/angle which it is interpolating to.

Whilst this information is hardly critical in using the API, it's good to understand how it works.

Prerequisite Knowledge

This document assumes knowledge of both the Scene and Scene Objects. Also note that where possible both a method(s) and a field is shown however, if a field is available then an equivalent set/get method will be available with the same name prefixed with "set" or "get" appropriately.

TorqueScript Bindings

Scene and the "world"

The physics system internally has its own "world" in which it processes physics objects. This world is analogous to the Scene, indeed the Scene encapsulates a physics "world" and ensures that the world is kept up-to-date. For this document, a reference to the world refers to the Box2D physics world whereas the Scene refers to the object that contains both the world and Scene Objects. Notionally, you can treat them as one and the same thing.

The world contains several configurable options but by far the most important is gravity. You can change gravity like so:

%scene = new Scene();

// Update gravity using the method.
%scene.setGravity( 0, -9.8 );

// Update gravity using the field.
%scene.Gravity = "0 -9.8";
 

When you set the gravity you set the independent x/y axis. Each axis is specified in metres/sec therefore setting -9.8m/s (Y Axis) gives an approximate earth gravity. Gravity affects all "dynamic" bodies in the world. Dynamic bodies are discussed later in this document.

The are more advanced options that you are free to chaneg but you should try to leave them unchanged unless you have validated that they improve the performance or simulation quality for your game on your specific device or devices. They are:

  • setVelocityIterations() / getVelocityIterations() / "VelocityIterations" field.
  • setPositionIterations() / getPositionIterations() / "PositionIterations" field.

These control how many iterations Box2D should take during the simulation tick. It can do this independently for position and velocity. Reducing the number of iterations improves performance but reduces the quality of the simulation. Obviously increasing the number of iterations reduces performance but improves the quality of the simulation. The defaults chosen are the defaults recommended by Box2D however these defaults are not perfect for situations where a far simpler simulation is required i.e. only simple stacking, low-speed object etc.

Bodies, Fixtures, Joints and the SceneObject

Every feature of Box2D including bodies, fixtures (collision shapes) and joints etc are supported by Torque 2D. However, these features are in a slightly different way than Box2D but the analogies with Box2D features are direct allowing the use of the Box2D manual to be used.

Here's a breakdown of how those Box2D features are used in Torque 2D.

Bodies

The SceneObject is the basis for all objects added to a Scene. All of the Box2D functionality (apart from "joints") is encapsulated within each SceneObject and exposed by its API.

Each SceneObject has a 2D position and orientation in world space. This is achieved by giving each SceneObject a Box2D body. A Box2D body is a position and orientation in space to which collision-shapes can be attached. For instance, when you set or get the position of a SceneObject, you are in-fact communicating with the underlying Box2D body itself.

When a SceneObject is created but not within a Scene, you can still set all of its properties including position, angle, collision-shapes etc. This is because Torque2D stands inbetween you and Box2D and caches these settings. In other words, consider a SceneObject that is not in a Scene as being "offline". This is in-fact why configuring a SceneObject physics-related state is faster if you do so before the SceneObject is added to a Scene. The reason being that if a SceneObject is within a Scene when you make physics changes, Box2D will perform the appropriate updates required to keep the physics simulation correct, flagging contact updates etc.

The body within each SceneObject can be one of three body types:

Static Body Type

Static bodies cannot collide with other static or kinematic bodies (more precisely, collision-shapes on these bodies do not collide with each other). Also, static bodies are immovable. By immovable, it does not mean you cannot set the position of the static body but simply that if you apply forces or directly set either the linear or angular velocity on the object, it will not move due to that change. You are always free to position a static body however it is recommended that you do not do so for performance reasons. In this, static bodies are typically used for static world geometry e.g. the platforms in a platformer game or the table in a card-game.

Because static bodies are not affected by forces they are obviously not affected by the scene-wide gravity shown previously. Static bodies are not processed during the physics simulation therefore they provide the best performance so should be used whenever possible.

Dynamic Body Type

Dynamic bodies are the default body type of any t2dSceneObject you create and are fully processed by the physics simulation. Unlike static bodies, dynamic bodies do collide with all body types and are movable via forces applied to them. These bodies are typically used for the things moving around the scene that need to collide with other objects and move using their linear or angular velocity. Dynamic bodies need the most processing so whilst they are the default body type, you should not use them for objects that never move as you are simply wasting performance.

Because dynamic bodies are affected by forces they are affected by the scene-wide gravity. Indeed, this can be a cause of confusion because when adding objects into the scene they will immediately be affected by the scene-wide gravity. This can be confusing when you wonder why the object may no longer be in the current view; it may have moved a long way away from it under the force of gravity.

You can use the SceneObject "setGravityScale()" to scale how the scene-wide gravity affects the object however you should use this with caution as indicated in the Box2D manual as it can, under some circumstances, cause simulation instability in stacking scenarios. That method is explained later.

Kinematic Body Type

Kinematic bodies are the least used of the body types but are useful in that kinematic bodies do not collide with other kinematic bodies but do collide with static or other dynamic bodies. Kinematic bodies move according to their linear or angular velocities but do not respond to forces applied to them. Kinematic bodies are typically moved by the user by them modifying the velocities directly.

Configuring the body type

If you wish to change the body type, you should try to set it immediately before configuring the object further as this provides the best performance. Modifying the body type can result in many calculations if the body contains collision-shapes and has active contacts in the scene. As has already been said, configuring an object before it is added to the scene provides the best performance.

If you set the position of a SceneObjects that can collide with other SceneObjects in the scene then the system must process any existing contacts. This is true when adding a new SceneObject into the scene. If you add all your SceneObjects to position (0,0) then they will all collide and produce contacts even if you immediately reposition them. For best performance, set the position of the SceneObject before it is added to the Scene. In this case, it will only have contacts that it should have. Ignoring this can result in a lot of wasted processing simply adding objects to the Scene.

Here's how you configure the body type.

%obj = new SceneObject();

%obj.BodyType = static;
%obj.BodyType = dynamic;
%obj.BodyType = kinematic;

%obj.SetBodyType( static );
%obj.SetBodyType( dynamic );
%obj.SetBodyType( kinematic );
 

A SceneObject defaults to a "dynamic" body type i.e. it is affected by gravity. Types that inherit from SceneObject are also dynamic unless they override that. Certain objects default to "static" as it makes sense for their purpose. Refer to each types documentation for this information.

Active

You can set a SceneObject body to be active or inactive. This is a quick way to exclude it from the physics simulation. When it is inactive, irrelevant of its body type, it will not interact with any other objects nor will it move according to its velocity or forces applied to it. It does not relate to whether the object renders anything, simply the physics part of its functionality. This is exposed directly from Box2D but is also slightly duplicated with the "setEnabled( true/false )" and "getEnabled()" methods. If you disable an object, it will also be made inactive. Disabling an object additionally results in it not being rendered as well. For visibility control only, you can use the "setVisible( true/false )" and "getVisible()". These three pairs of methods therefore control physics and rendering either independently or together.

%obj = new SceneObject();

%obj.Active = false;

Awake

You can set a SceneObject body to be awake or asleep or check to see if it is awake or asleep. The physics system uses this feature when the SceneObject has not moved or hasn't had any new contacts for a certain simulation period. If this is the case then the simulation will put the SceneObject body to sleep. Doing this massively reduces the overhead of the object. If the object is collided with or is modified directly by the user i.e. its position is changed, it is automatically woken up. You can therefore take control of this and set its "awake" state at any time. You should typically leave the system to control this but you can check if an object is awake without affecting it at any time.

All scene objects are awake by default. For better performance you can set an object to be initially asleep rather than waiting for the simulation to put it to sleep. This greatly improves initial performance however care should be taken as if an object is initially asleep and has contacts, it will not resolve them. For a many objects however, being initially asleep would not be a problem.

%obj = new SceneObject();

%obj.Awake = false;

If you do not wish to allow an object to sleep you can also control this using:

%obj = new SceneObject();

%obj.SleepingAllowed = false;

Sleeping is allowed by default.

Bullet (Fast Moving Object)

The term "bullet" refers specifically to an object that moves fast. The default collision algorithms that Box2D uses balances performance and accuracy for low to medium speed objects. High speed objects can actually pass directly through each other. Most of the time you will not see this, however if you are encountering this (typically seen for high-speed projectiles in games) then you can flag the object as a bullet. In this case, Box2D will use the much more expensive CCD (continuous collision detection) algorithm which will completely eliminate this issue. By default, the bullet flag is off as using CCD for all objects would soon become prohibitively expensive. Use this feature with care and only when your game absolutely requires it, specifically on objects that require it and certainly not all objects. Do not turn it on and wait until your performance suffers, do the opposite, wait for the problem and turn it on for that specific object.

%obj = new SceneObject();

%obj.Bullet = true;

Position, Angle & Velocities

An object can have a position, angle, linear velocity (translation) and angular velocity (spin), all of which can be modified like so:

%obj = new SceneObject();

// Set position.
%obj.Position = "10 20"

// Set angle (in degrees).
%obj.Angle = 30; 

// Set linear velocity (translation) in metres/sec.
%obj.LinearVelocity = "5 8";

// Set angular velocity (spin) in degrees/sec.
%obj.AngularVelocity = 10;

With the integration of Box2D, an important change from the legacy Torque2D was the change to the coordinate system used. As you have seen, world-units actual refer to a physical unit i.e. the metre. Additionally, legacy Torque 2D had the +Y Axis to mean "down" and the -Y to mean "up". This confused a lot of users. This change introduces an inverted Y meaning that +Y Axis means "up" and -Y means "down". If you think about it, in a game this makes much more sense. If your object needs to jump "up", you give it a positive impulse/force/velocity rather than a negative one.

As mentioned previously, it's worth showing some extra methods provided for render position and angle:

%obj.getRenderPosition();
%obj.getRenderAngle();

These provide the current position and angle being rendered in the current frame (interpolated). If the SceneObject is not moving, these will be identical to the "getPosition()" and "getAngle()" respectively however if the SceneObject is moving, they will differ.

For the most part, you will not use the render versions of position and angle however if you wish for some script code to relate directly to what is on screen at that point in time then you can use the render variants of these methods.

Modifying either the position, angle or size of a SceneObject causes the render position and angle to be made identical to the current position and angle respectively. This is why, when setting these values continuously, the smooth motion that the render interpolation system provides is undermined and smooth motion ceases for that object. Occasionally setting an objects position, angle or size would be fine. You should consider that setting an objects position, angle or size immediately "warps" (think teleportation) the SceneObject to a different position in space without it smoothly moving/resizing there.

Related to angle, you can configure a SceneObject so that its angle cannot be changed by forces applied directly to it or from collisions. This is known as a fixed angle. This does not affect you using "setAngle()" to change the angle however but it can be controlled using:

%obj.FixedAngle = true;

Note that this doesn't actually set the angle, just the fact that the current angle is fixed.

Gravity Scale

Scene-wide gravity is extremely useful in many scenarios however it affects all dynamic bodies equally by default. Sometimes it is neccessary to either scale how the gravity affects a SceneObject or to simply turn off gravity for that SceneObject. The best way to stop gravity from affecting a SceneObject is to make its body type "static" however if that is not wanted then setting the gravity scale is the next best thing.

You can use the following to change the gravity scale:

// This object is affected by half the Scene gravity.
%obj.GravityScale = 0.5;

// This object is now NOT affected by the Scene gravity.
%obj.GravityScale = 0;

As you can see, gravity can be scaled per-SceneObject or completely turned-off by scaling with zero. As indicated previously, caution should be used when scaling gravity as it can cause instabilities in the simulation for that SceneObject under some circumstances. This warning is reproduced in the Box2D manual.

Miscellaneous velocity and force changes

There are many methods available to manipulate both velocities and forces on a SceneObject. Here is a brief list of the most important ones:

  • setLinearVelocityX(velocityX)
  • getLinearVelocityX()
  • setLinearVelocityY(velocityY)
  • getLinearVelocityY()
  • getLinearVelocityFromWorldPoint()
  • getLinearVelocityFromLocalPoint()
  • setLinearVelocityPolar(angle, speed)
  • getLinearVelocityPolar()
  • setLinearDamping(damping)
  • getLinearDamping()
  • setAngularDamping(damping)
  • getAngularDamping()
  • applyForce(force, point)
  • applyTorque(torque)
  • applyLinearImpulse(impulse, point)
  • applyAngularImpulse(impulse)

World-space and Local-space conversions

World space is what you get used to when organizing SceneObjects in a Scene however each SceneObject essentially "lives" in its own local space. Local space is simply where the origin is centered on the SceneObjects position. Additionally, the X/Y axis are not always aligned to the worlds X/Y axis (basis) but are aligned according to the SceneObjects current angle. In other words, if the SceneObject is at zero-degrees angle then the local X axis is "-left/+right" (the same as the world X axis) and the local Y axis is "+up/-down" (the same as the world Y axis). If the SceneObject is at +90 degrees angle (90 degrees anti-clockwise) then the local X axis is "-down/+up" and the local Y axis is "-right/+left".

One of the most common operations related to world and local space conversions is converting either a point or vector between these spaces. These operations are provided by the following methods:

  • getLocalPoint( worldPoint )
  • getWorldPoint( localPoint )
  • getLocalVector( worldVector )
  • getWorldVector( localVector )

As you can see, these methods take a local/world point/vector appropriately. These methods exist on a SceneObject therefore they relate to the position and angle of that object only.

Center of Mass

When you add collision shapes to a SceneObject, they add mass to it assuming they have non-zero density. Because collision shapes are not necessarily symmetric and/or they don't have to be centered at the SceneObjects position and/or they have different masses, the center of mass is not always equal to the SceneObject body position. With this in mind, the center of mass can be queried using the following methods:

  • getLocalCenter()
  • getWorldCenter()

These methods return the center of mass in either local space or world space. The local center is relative to the SceneObject body position i.e. it is in local-space whereas the world center is relative to the world origin i.e. it's in world-space.

It can be useful to know the center of mass if you are applying forces. Applying linear forces to the center of mass does not cause rotation i.e. it applies no torque whereas applying a linear force elsewhere does. In summary; if you apply a linear force to the bodies' position and it is not the center of mass then the SceneObject will rotate.

Collision Shapes

Torque 2D supports all collision shapes that Box2D provides. In legacy Torque 2D it only had a box and circle collision shape.

The supported shapes therefore are:

  • Circle
  • Polygon (convex only)
  • Edge
  • Chain

A SceneObject can have as many of these collision shapes as it requires. This allows you to construct complex collision shape regions. All collision shapes are specified in metres (world-units). In legacy Torque 2D, collision shapes had their own special normalized local-space. This allow the collision shapes to change as an object was resized. This is completely unsupported in Box2D and requires collision shapes to be recreated which is prohibitively expensive therefore Torque 2D does not support this either.

A circle shape is obvious and actually provides the best performing collision shape. It has a radius >0 and can be positioned at any local-space point.

The polygon shape must be a convex shape and is hard-limited to have no more than 8 vertices. This can be changed but requires a recompilation as it is completely contained within a single define within the Box2D code. I ncreasing this is not recommended as it can impact performance greatly for very little return.

The edge shape provides a single edge comprised of two local-space points. This can be handy in many circumstances.

The chain is similar to the edge shape except that it allows multiple continuous edges to be specified. This allows, for instance, the surface (floor) of a tile-layer to be specified or just arbitrary chains of edges for any reason. Both edges and chains provide the opportunity for one-side collisions which can be handy in platformer games.

Collision Shape API

A lot of time was spent trying to design how to expose a flexible enough collision shape API without having to make each collision shape a SimObject to expose it as a discrete object to the script system as this would have been disastrous for memory consumption. To this end, the design is such that special factory functions for each collision shape type exist coupled with a common API that provides common enumeration, configuration, deletion etc.

Collision Shape Factory Methods

There are several methods dedicated to creating either a Circle, Polygon, Edge or Chain polygon shape. There are a few overloads as well to provide common functionality. All these methods have their own arguments and is not described here.

The factory methods are:

  • createCircleCollisionShape( radius, [position] )
  • createPolygonCollisionShape( points )
  • createPolygonBoxCollisionShape(width, height, [position, angle] )
  • createChainCollisionShape( points, [adjacentStartPoint], [adjacentEndPoint] )
  • createEdgeCollisionShape( startPoint, endPoint, [adjacentStartPoint], [adjacentEndPoint] )
%obj = new SceneObject();

%obj.createCircleCollisionShape( 3 );
%obj.createPolygonCollisionShape( %points );
%obj.createPolygonBoxCollisionShape( 4, 3 );
%obj.createChainCollisionShape( %points );
%obj.createEdgeCollisionShape( %startPoint, %endPoint );

Note that the Polygon version has an overload that lets you create a polygon box (actually a square i.e. 4 vertices). When you create a collision shape, these methods return you the collision shape index which is a simple index from 0 onwards. If you do not delete a collision shape then the index is always associated with the collision shape as it is simply an index into an array of collision shapes for the SceneObject. If you delete a collision shape however then any collision shapes with a higher index have their index changed i.e their index is reduced by 1. Typically you do not remove collision shapes due to the performance cost therefore this is unlikely to affect you however you must be aware that the collision shape index is NOT an id but a simple index.

If creating a collision shape fails, an index of -1 is returned in all cases.

If you do want to delete a collision-shape you can delete it, passing its index using:

  • deleteCollisionShape()
%obj.deleteCollisionShape( %shapeIndex );

Note that you do not need to delete collision shapes yourself as they are deleted automatically when the SceneObject is destroyed which typically happens when the Scene they are within is destroyed or the SceneObject is explicitly deleted.

Collision Shape Common Configuration

Creating each collision shape requires several arguments to be specified however all collision shapes have a common set of properties. To reduce the number of arguments that must be specified when creating collision shapes you do not specify them when creating the collision shape but can do so after the shape has been created.

These methods are:

  • setCollisionShapeDensity(index, density)
  • getCollisionShapeDensity(index)
  • setCollisionShapeFriction(index, friction)
  • getCollisionShapeFriction(index)
  • setCollisionShapeRestitution(index, restitution)
  • getCollisionShapeRestitution(index)
  • setCollisionShapeIsSensor(index, isSensor?)
  • getCollisionShapeIsSensor(index)

All these methods require you to specify the collision shape index. As you can see you can change the Density, Friction, Restitution and whether the shape is a sensor (does not react to contacts but still reports them).

The most common ones are Density, Friction and Restitution and typically you want all collision shapes on a SceneObject to have the same values. To aid this and remove the need to set each collision shape you can set the defaults for Density, Friction and Restitution prior to creating the collision shapes.

These methods are:

  • setDefaultDensity(density)
  • getDefaultDensity()
  • setDefaultFriction(friction)
  • getDefaultFriction()
  • setDefaultRestitution(restitution)
  • getDefaultRestitution()

You call these on the SceneObject, not on a collision shape. With these set, any subsequent collision shapes created will use the default values you specify.

If you do not modify these they are:

  • Density = 1.0
  • Friction = 0.2
  • Restitution = 0.0

Collision Shape Type Information

After you create a collision shapes, many of their properties are immutable but you can retrieve them using the following methods:

  • Circle
    • getCircleCollisionShapeRadius(index)
    • getCircleCollisionShapeLocalPosition(index)
  • Polygon
    • getPolygonCollisionShapePointCount(index)
    • getPolygonCollisionShapeLocalPoint(index)
  • Chain
    • getChainCollisionShapePointCount(index)
    • getChainCollisionShapeLocalPoint(index)
    • getChainCollisionShapeHasAdjacentStart(index)
    • getChainCollisionShapeHasAdjacentEnd(index)
    • getChainCollisionShapeAdjacentStart(index)
    • getChainCollisionShapeAdjacentEnd(index)
  • Edge
    • getEdgeCollisionShapePointCount(index)
    • getEdgeCollisionShapeLocalPoint(index)
    • getEdgeCollisionShapeHasAdjacentStart(index)
    • getEdgeCollisionShapeHasAdjacentEnd(index)
    • getEdgeCollisionShapeAdjacentStart(index)
    • getEdgeCollisionShapeAdjacentEnd(index)

Collision Shape Enumeration

You can enumerate the collision shapes on a SceneObject easily by simply using their index values. T o do this however you need to get a count of collision shapes on a SceneObject.

You can do this using:

  • getCollisionShapeCount()

Using this you can iterate indexes from 0 to "getCollisionShapeCount()-1". A quick way of determining what type of collision shape is at any specific index is to use:

  • getCollisionShapeType(index)

This will return you one of the four supported shape types below:

  • circle
  • polygon
  • edge
  • chain

Knowing the shape type at each location you can then perform the appropriate "get" actions shown previously to retrieve information specific to that shape type at that index.

Note that if you perform the incorrect shape type call i.e. try to retrieve the radius of a circle shape but the shape at the specified index is (say) a polygon then you'll simply get a warning in the console.

Copying collision shapes

It's possible to copy either a single collision shape or all collision shapes from one SceneObject to another. You can do this using:

  • copyAllCollisionShapes( %targetSceneObject, [clearTargetShapes?] )
  • copyCollisionShape( %sourceShapeIndex, %targetSceneObject )

Collision Control

For two SceneObjects to collide with each other, it's important to not only understand how to configure that but to actually understand what it is that is colliding.

As we have seen, a SceneObject encapsulates a Box2D body. The body gives the SceneObject a position and orientation in the Scene (world). Bodies can have both linear and angular velocities i.e. they can translate and rotate.

None of this provides the ability for things to collide. In other words, bodies don't collide with each other directly. This happens but adding collision shapes to a body. The collision shapes themselves can be configured to collide with other collision shapes on other bodies.

When two shapes collide, they produce what is known as a "contact". A contact is created when collision shapes touch and is destroyed when they separate therefore the physics simulation is constantly creating and destroying contacts as collision start and end.

Configuring what collides with what can therefore be thought of as "filtering potential contacts". The reason to think like this is that in 2D space, things can collide i.e. overlap each other but whether they produce a "contact" is what is important.

To configure what the collision shape(s) on a SceneObject collide with (technically produce contacts) you specify which scene group(s) and scene layer(s) they collide with.

A scene group and scene layer is a property of each SceneObject where each SceneObject can be placed in a specific Scene layer and Scene group. Scene layers range from 0 to 31 as do Scene groups. The Scene group of a SceneObject is a general purpose way of grouping objects. This can be used when picking objects from the Scene or as we'll see here for selecting collisions. The Scene layer is the rendering order of the object with 0 being the front-most and 31 being the back-most. Again, this can be used for selecting collisions. Whilst it may at first seem strange that collisions be controlled via a rendering layer, it's actually a natural way of filtering contacts.

By default, a newly created SceneObject exists in SceneLayer zero and SceneGroup zero. Note that a SceneObject can only exist in a single layer and single group. You set these using the following methods:

  • setSceneLayer( layer ) or "%obj.SceneLayer = %layer"
  • getSceneLayer()
  • setSceneGroup( group ) or "%obj.SceneGroup = %group"
  • getSceneGroup()

To configure which Scene group(s) and Scene layer(s) a SceneObject can collide with you use the following methods:

  • setCollisionLayers( %layers ) or "%obj.CollisionLayers = %layers"
  • setCollisionGroups( %groups ) or "%obj.CollisionGroups = %groups"
%obj = new SceneObject();

// These all do the same thing.
%obj.setCollisionLayers( 5, 6, 7 );
%obj.setCollisionLayers( "5 6 7" );
%obj.CollisionLayers = "5 6 7";

// These all do the same thing.
%obj.setCollisionGroups( 20, 30 );
%obj.setCollisionGroups( "20 30" );
%obj.CollisionGroups = "20 30";

By default, a SceneObject will collide with all Scene layers and all Scene groups i.e. SceneObjects collide with all other SceneObjects by default.

You are free to configure both Scene layer(s) and Scene group(s) that an object collides with or you can simply use just the Scene layer(s) or Scene group(s). In other words, you may only wish to allow a SceneObject to collide with a specific Scene group, irrelevant of the Scene layer it is on. To do this you would just leave the collision layers at default i.e. to collide with all Scene layers) and just configured which particular Scene group(s) are to be collided with.

How you organize your Scene, which Scene layers and Scene groups your objects are organized in is totally up to you. It's typical however to use Scene groups to organize collisions rather than Scene layers. You can apply meaning to each Scene group i.e. "0 = Aliens", "1 = Projectiles", "2 = Pickups" etc. You can logically do the same for Scene layers however those obviously also affect the order of rendering whereas modifying the Scene group has no impact on the render order or anything else so you are free to apply any meaning to the 32 available groups you wish.

It's worth planning out your Scene groups (and possibly Scene layers if you intend to use them for collisions) before you start the game construction or at least have a place where you note what each group means. For simply games you can get away with a few groups so their meaning is easy to remember however for more complex games it's worth planning ahead.

Here's a quick example that uses Scene groups. In this example, Scene group 10 means player and Scene group 20 means enemy:

// Configure the player to be in Scene group 10.
%player = new SceneObject();
%player.createCircleCollisionShape( 1 );
%player.SceneGroup = 10;

// Configure the enemy to be in Scene group 20.
%enemy = new SceneObject();
%enemy.createCircleCollisionShape( 1 );
%enemy.SceneGroup = 20;

// Configure the player to collide with anything in Scene group 20.
%player.setCollisionGroups( 20 );

// Configure the enemy to collide with anything in Scene group 10.
%enemy.setCollisionGroups( 10 );

In this example, the "player" object in Scene group 10 collides with the "enemy" object in Scene group 20 and likewise the "enemy" object collides with the "player" object in Scene group 10. It doesn't matter what Scene layers they are in as the default is to collide with all Scene layers.

What is particularly interesting here is that we had to allow both the player to collide with the enemy and the enemy collide with the player. This is known as a mutual collision. In reality, there's no such thing as one object colliding with another, simply two objects colliding.

To determine if two objects should collide, Torque 2D uses this mutual collision algorithm however, you are free to change this as it is located with in a single method located here

Even with a plan of each group or layer meaning, it can be useful to not have to spread this knowledge throughout your scripts. To help with this, you can configure a SceneObject to collide with another SceneObject without knowing the details of each SceneObject using the folllowing method:

  • setCollisionAgainst( %sceneObject, [clearMasks?] )

You would use it like this:

%objA.setCollisionAgainst( %objB );

In the above example, "%objA" would append to its collision Scene layers and collision Scene groups (the layers and groups it collides with) the Scene layer and Scene group from "%objB". This allows you to effectively "point" to a SceneObject and configure another object to collide with it (specifically any object in its Scene layer or Scene group).

The optional second argument allows you to clear the collision masks. By default this is false meaning the call will append the collision Scene layers and groups. When true, any existing collision Scene layers and groups are cleared first then the assignment is made therefore changing it into a replace operation rather than an append operation.

The meaning of "collision mask" comes from the fact that you can, in parallel, select any/all of both the Scene layers and Scene groups. This produces an unsigned 32-bit value for the Scene layers and another for the Scene groups. These are known as "collision masks" because they filter (mask) collisions.

You can set both the collision Scene group and collision Scene layers directly using makes with the following:

  • setCollisionMasks( %groupMask, [%layerMask] )
  • getCollisionMasks()

Each bit in each mask represents the respective group or layer i.e. Bit#0 in the group mask represents group 0, Bit#31 in the group mask represents group 31, Bit#5 in the layer mask represents layer 0 etc.

If you don't want a SceneObject participating in any collisions whatsoever, irrelevant of which Scene groups or layers it's in or what its collision groups or layers are currently set to, you can simply use the following:

  • setCollisionSuppress( true/false )
  • getCollisionSuppress()

The reason this is known as "supressing" collision comes from the fact that you can configure an object to collide with other objects but can temporarily supress the collisions using this method. You can turn off the supression at any time to resume collisions with affecting any of the existing collision configuration. It should be noted however that whether collisions are suppressed or not is persisted should you persist the SceneObject itself.

Collision Callback

When a collision occurs between two objects, the physics system will produce a "response" to the contact. Box2D is a rigid-body physics system and so produces a rigid-body response. The exception to this is if either of the collision shapes involved in the collision are sensors. A sensor does not produce a collision response but does allow you to receive a callback that it occurred. More details of this can be found in the Box2D manual.

For your custom gameplay however, you'll probably be interested in knowing when specific objects collide with other objects. To know this, you can use the collision callback system. This system is configured on a per-SceneObject basis. The reason for this is that the physics system can produce a huge amount of contacts and producing a callback to the scripts for each and every one of them, irrelevant of whether they are required would be prohibitively expensive.

Turning on callbacks for an object is done like this:

// These do the same thing.
%obj.setCollisionCallback( true );
%obj.CollisionCallback = true;

So with collision callbacks on, it's important to know how to set up the callback in TorqueScript. A change to how legacy Torque 2D handled this is that previously, the callback was made on the SceneObject namespace. This has now changed and for good reason. Previously you could receive two callbacks, one for each SceneObject involved in the contact which was extremely wasteful, hindered performance and caused confusion.

Now the collision callback method occurs on the Scene namespace i.e. "Scene::OnCollision(...)" which makes much more sense as it does not relate to any one object but a pair of objects in the Scene like so:

function Scene::onCollision( %sceneObjectA, %sceneObjectB, %collisionDetails )
{
}

As you can see, you simply get the two scene objects involved in the contact. The "%collisionDetails" returns extra details about the collision. There are actually two formats for the collision details depending on whether one or two contact points occurred.

The format is as a space separated list containing the following items in the specified order:

  1. Collision Shape Index A
  2. Collision Shape Index B
  3. Collision Normal
  4. Contact World Point #1
  5. Collision Normal Impulse #1
  6. Collision Tangent Impulse #1
  7. Contact World Point #2 (Only if two contacts)
  8. Collision Normal Impulse #2 (Only if two contacts)
  9. Collision Tangent Impulse #2 (Only if two contacts)

As you see, the first two items are the collision shape indexes that collided within each SceneObject. These two items are the only two that are guaranteed to be returned. If this happens then it signifies that there are no contact points. Whilst this may sound strange, Box2D doesn't produce contact points for collisions involving sensors. In this instance you'll only know the two SceneObject and their collision shapes indexes.

If you have at least a single contact point then you'll always get Collision Normal (item#3).

For each contact point you'll get a Contact World Point, Normal Impulse and Tangent Impulse. In the case of a single contact point this means you'll get items 4-6. For two contact points you'll not only get items 4-6 but an additional contact of items 7-9.

An example might be:

function Scene::onCollision( %sceneObjectA, %sceneObjectB, %collisionDetails )
{
   echo( %collisionDetails );
}

... where, assuming this has a single contact point, might echo ...

// Collision Shape A Index = 0
// Collision Shape B Index = 2
// Collision Normal = (0.0, 1.0)
// Contact World Point = (100.0, 50.0)
// Normal Impulse = 30.0
// Tangent Impulse = 0.0
"0 2 0.0 1.0 100.0 50.0 30.0 0.0"

These are the details of each item are:

  • Collision Shape Index A & B - Refers to the collision shapes involved in the collision. You can use this to retrieve specific details about those collision shapes such as whether they are sensors, their friction, restitution etc.
  • Normal - This normal relates to the direction that an impulse will be applied to separate the two collision shapes. In the case where either collision shape is a sensor, no impulse is applied however the collision normal is still calculated. You could conceivably use this to apply your own impulse along the collision normal.
  • Contact World Point - These are known as support points and relate to the calculation of the collision normal and therefore the separating impulse. Further information can be found in the Box2D manual.
  • Normal Impulse - Is the impulse force used to separate the objects during the collision. It is indicative of the forces involved in the collision.
  • Tangent Impulse - Is the impulse force used when calculating contact friction.

Both the contact normals and contact points come directly from Box2D itself and Torque 2D does not manipulate them in any way. If CCD (bullet) is not active then the contact points may not be on the perimeter of the collision shapes but in-fact are at the points where an impulse is applied to separate the objects. With CCD active, these are always on the perimeter of the collision shape. More information can be found in the Box2D documentation.

As with all TorqueScript callbacks, extreme care should be taken on what actions occur during the callback. If you wish to destroy an object involved in the collision you must always use the "safe delete" option and never immediately destroy an object as this can cause an exception. Performing a safe delete operation will ensure no further collision callbacks occur should the object be involved in multiple contacts during that game loop however.

With the collision callback on, you'll not only get a callback when a contact is made between two objects but you'll also get a callback for when the contact is lost between two objects like so:

function Scene::onEndCollision( %sceneObjectA, %sceneObjectB, %collisionDetails )
{
}

In this case, the "%collisionDetails" always contains only the collision shape indexes i.e. items #1 and #2 in the "onCollision()" callback collision details.

This therefore informs you when two scene objects stop contacting each other as well as the specific collision shape indexes.

Contact Gathering

There are times when you want, for a specific object, to know all the contacts that the object has at any point in time without having to accumulate them yourself using the "onCollision()" and "onEndCollisionCallbacks()".

You can turn this on for any SceneObject using:

  • setGatherContacts(true/false)
  • getGatherContacts()
  • "GatherContacts" field

Contact gathering is off by default as it requires extra memory and processing to store all the contact information. Contact gathering is still pretty efficient however having this feature on all the time for all objects would be extremely wasteful.

When contact gathering is enabled, you can retrieve contacts using two SceneObject methods:

  • getContactCount()
  • getContact(contactIndex)

If contact gathering is disabled, the contact count will always be zero.

Here's an example of retrieving all contacts on an object:

%count = %obj.getContactCount();

for( %i = 0; %i < %count; %i++ )
{
   %contact = %obj.getContact( %i );
   echo( %contact );
}

The contact string returned is almost identical to the "onCollision()" collision details string (items #1 to #9 with all the contact point rules described there) however this string is prefixed with the SceneObject that this object is colliding with.

Joints

Torque 2D supports nearly all the joints provided by Box2D. I say nearly because only a single joint is not supported (the gear joint). The reason this joint is not supported is simply because it's fairly complex to configure making the arguments needed to configure it pretty overwhelming and it's rarely used so it was omitted. If it becomes important in the future to use it, it can be added relatively easily without affecting anything else. The same obviously goes for your own custom joints.

The complete joint list is therefore:

  • distance
  • rope
  • revolute
  • weld
  • wheel
  • friction
  • prismatic
  • pulley
  • target
  • motor

The "target" joint above is in actual fact Box2Ds "mouse" joint. It was renamed "target" as that better indicates what it is meant to do i.e. move a SceneObject towards a target point. The fact that it is typically used in the Box2D test-bed to allow the user to use the mouse to drag objects to target points is a poor reason to name it "mouse" joint.

This document does not go into detail for each joint as that would be duplicating what is already in the Box2D documentation. It will however list the methods involved in creating, configuring, enumerating and destroying joints.

Joints, the Scene and Legacy Mounting

The legacy Torque 2D "mounting" functionality has been removed. That functionality was implemented in the SceneObject API as it involved a simple mounting of one SceneObject to another. Joints completely replace that functionality.

Unlike the legacy Torque 2D "mounting" feature, the API for joints is against the Scene itself and not the SceneObject. Indeed, the SceneObject knows nothing about joints. This might seem odd to some but it makes sense in that joints are not owned by any single SceneObject. All joints bind two scene objects together so it makes complete sense that the joint cannot be owned by either but a third party; in this case the Scene.

Joint Ids

It's worth pointing out something that's extremely important to know about how you refer to joints that have been created. If you recall, collision shapes live on a SceneObject to which they are attached. They are addressed by a simple index that can potentially change if other collision shapes on the same SceneObject are deleted: something that is rarely done due to performance reasons.

With joints, the situation is quite different. For starters, all joints live in the Scene itself. Also, it is fairly common to destroy joints during game-play, even expected. In this, it makes sense to use something more sophisticated than an index into an array of joints. For joints, when you create them, they each get a unique serial Id. Unlike collision shapes however it will never change during the lifetime of a joint. This means that once you create a joint, you can always refer to it by its Id. Additionally, if you delete the joint, that Id will never be used again during the game which makes it easy to spot bugs related to addressing incorrect joints.

Basic Joint Operations

Although it's kind of the wrong way around i.e. we've not yet covered how to create joints, let's start by showing some common joint operations:

  • getJointCount()
  • getJointType( jointId )
  • deleteJoint( jointId )

The "getJointCount()" operation gives a count of all the joints in the Scene. This doesn't really provide massive utility as you cannot enumerate all joints in the scene simply because that isn't useful in any real gaming scenarios. Also, joints are not referred to by any index, only by a unique Id. It would be possible to return a string containing all the joints in the Scene and you could, using TorqueScript, iterate them but that would not only be expensive, it would beg the question why?

The "getJointType( jointId )" is similar to the "getCollisionShapeType( index )" method for collision shapes in that it returns you the type name of the "jointId" you specify.

This will return one of the following:

  • distance
  • rope
  • revolute
  • weld
  • wheel
  • friction
  • prismatic
  • pulley
  • target
  • motor

... which is the complete joint list shown previously.

The "deleteJoint( jointId )" will simply delete the joint referred to by the specified "jointId".

Joint Factory Methods

To create a joint you can use one of the following joint factory methods:

  • createDistanceJoint()
  • createRopeJoint()
  • createRevoluteJoint()
  • createWeldJoint()
  • createWheelJoint()
  • createFrictionJoint()
  • createPrismaticJoint()
  • createPulleyJoint()
  • createTargetJoint()
  • createMotorJoint();

All of these methods either return the new joints "jointId" (which is always a positive integer) or "-1" to indicate that there was a problem creating the joint (at which point you should check the log for warnings).

// Assume we have some scene objects already added to the scene "%scene" as "%obj1" and "%obj2".
%jointId = %scene.createDistanceJoint( %obj1, %obj2 );
if ( %jointId == -1 )
{
   // Joint failed to be created !
}

The basic example above shows adding a "distance" joint being created between two scene objects. It also shows that a return value of "-1" indicates a problem creating the joint. In this case and assuming the joint creation worked, the two objects would stay separated by their current distance apart; the joint would ensure that until it is deleted. As with all the other joint factory methods, they have mandatory arguments (in the above example it's only the two scene objects that the joint works with) and optional arguments that allow you to immediately configure other options. In the case of the "distance" joint you can also, amongst other things, specific the actual distance you want the objects to achieve etc.

Unlike collision shapes, joints are able to change some of their parameters after they have been created. We have exposed all of these parameters for all joints. This produced a lot of additional methods however they are named consistently (against the joint type) and so it should become easy to memorize the joint API after several uses.

To suppliement the joint creation methods above, here's the list of methods that allowyou to modify each joint type after it has been created:

  • createDistanceJoint()
    • setDistanceJointLength()
    • getDistanceJointLength()
    • setDistanceJointFrequency()
    • getDistanceJointFrequency()
    • setDistanceJointDampingRatio()
    • getDistanceJointDampingRatio()
  • createRopeJoint()
    • setRopeJointMaxLength()
    • getRopeJointMaxLength()
  • createRevoluteJoint()
    • setRevoluteJointLimit()
    • getRevoluteJointLimit()
    • setRevoluteJointMotor()
    • getRevoluteJointMotor()
  • createWeldJoint()
    • setWeldJointFrequency()
    • getWeldJointFrequency()
    • setWeldJointDampingRatio()
    • getWeldJointDampingRatio()
  • createWheelJoint()
    • setWheelJointMotor()
    • getWheelJointMotor()
    • setWheelJointFrequency()
    • getWheelJointFrequency()
    • setWheelJointDampingRatio()
    • getWheelJointDampingRatio()
  • createFrictionJoint()
    • setFrictionJointMaxForce()
    • getFrictionJointMaxForce()
    • setFrictionJointMaxTorque()
    • getFrictionJointMaxTorque()
  • createPrismaticJoint()
    • setPrismaticJointLimit()
    • getPrismaticJointLimit()
    • setPrismaticJointMotor()
    • getPrismaticJointMotor()
  • createPulleyJoint()
  • createTargetJoint()
    • setTargetJointTarget()
    • getTargetJointTarget()
    • setTargetJointFrequency()
    • getTargetJointFrequency()
    • setTargetJointDampingRatio()
    • getTargetJointDampingRatio()
  • createMotorJoint()
    • setMotorJointLinearOffset()
    • getMotorJointLinearOffset()
    • setMotorJointAngularOffset()
    • getMotorJointAngularOffset()
    • setMotorJointMaxForce()
    • getMotorJointMaxForce()
    • setMotorJointMaxTorque()
    • getMotorJointMaxTorque()

Implicit Joint Destruction

Joints will obviously be destroyed when you call the "deleteJoint( jointId )" method however joints will also be destroyed if you destroy a SceneObject to which a joint is connected. In the case of all joints, a joint is always connected to two scene objects thus if either of those scene objects is destroyed the joint itself will be destroyed. This is done internally within Box2D and stops you from having degenerate joints.

Physics Limitations

These limitations relate directly to the limitations of Box2D itself and are documented in the Box2D manual but they are important enough to mention here:

  • Stacking heavy bodies on top of much lighter bodies is not stable. Stability degrades as the mass ratio passes 10:1.
  • 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.
  • There is typically around 0.5cm of slop in shape versus shape collision.
  • Continuous collision does not handle joints. So you may see joint stretching on fast moving objects.
  • 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.
  • 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.

References

Clone this wiki locally