09 April 2013

## Tutorial: Radial Gravity and Predicting Trajectory

Today’s tutorial features two more physics methods for your bag of tricks. In this tutorial, we’ll discuss **“radial gravity”** and **predicting launch trajectory.**

Both of these methods are included as **downloadable projects** — please reference the link at the end of this tutorial.

### Radial Gravity

**Radial gravity** was recently made famous in Angry Birds: Space™, in which the wild crew of avian aeronauts had to contend no longer with Earth-based gravity, but rather with gravitational pull. While Corona’s physics engine doesn’t yet feature a native “radialGravity” setting, it’s relatively simple to mimic using sensors and **touch joints**.

This functionality could also be used for such things as **magnet behavior** — in fact, you might have seen this exhibited in the Corona-powered game Major Magnet.

#### Creating the Radial Field

The first step is to create the “radial field” that will exert a pull force on objects that enter it. Consider this the atmosphere of a planet, or the sensory range around a magnet. In Corona, we can create this field as a simple **radial body** set as a **sensor**, as follows:

local field = display.newCircle( 0, 0, 320 ) ; field.alpha = 0.3 field.name = "field" field.x = display.contentCenterX ; field.y = display.contentCenterY physics.addBody( field, "static", { isSensor=true, radius=320 } )

#### Object Collision (with the Field)

How you create the object(s) that will enter this field is largely up to you, so I won’t go into detail about that. What I will discuss is the common method behind the **collision** with this field. Essentially, when an object *enters* the field — collision phase of “began” — we’ll create a physics **touch joint** between the object and the center of the field. Note that in the case of a touch joint, you’re not actually joining the object to another object as you would with most physical joints. In the case of a touch joint, the “target” is simply a point in world coordinates. In this tutorial, that point will simply be the center of the radial field.

--COLLISION HANDLER function objectCollide( self, event ) local otherName = event.other.name local function onDelay( event ) local action = "" if ( action == "makeJoint" ) then self.hasJoint = true self.touchJoint = physics.newJoint( "touch", self, self.x, self.y ) self.touchJoint.frequency = 0.25 self.touchJoint.dampingRatio = 0.0 self.touchJoint:setTarget( event.other.x, event.other.y ) . . . end end if ( event.phase == "began" and otherName == "field" and self.hasJoint == false ) then local tr = timer.performWithDelay( 10, onDelay ) ; tr.action = "makeJoint" end end

Note that we’ve created an **onDelay** function within the collision function to handle actions that can’t be executed in the same game cycle as the collision itself. In this example, we also assign a property named **action** to the timer itself, so when it executes, we know the action that should be taken (more timers will be needed as we progress through this tutorial, so it’s useful to identify the action associated with them).

Another important note is that you should assign a flag to the object itself — I’m using **hasJoint** here — as a further safety check that the object entering the field doesn’t already have a touch joint applied to it.

#### Post-Collision Behavior

So what happens here, in actual practice? Well, the object enters the radial field and the touch joint is applied after 10 milliseconds. The **frequency** of the touch joint determines the “pull” of the joint itself, and in effect, how much power or pull your planet/magnet will appear to have. Be careful to test this value repeatedly, as a minor adjustment can affect the behavior significantly. Finally, the touch joint :setTarget call sets the destination in world coordinates that the object will attempt to reach — and I must emphasize that it will only *attempt* to reach that point. Objects under the influence of touch joints can be interrupted by collision with other objects, or the joint itself can be explicitly broken/removed (we’ll actually do this in the next step).

#### Exiting the Field

In most cases, you’ll want the objects to have the capability of exiting the radial field and continuing on their expected path, especially fast-moving objects that might “skirt” the outside of the field. When this occurs, we’ll need to remove the touch joint so that the object no longer attempts to reach the center of the radial field. This is easily handled with the collision **ended** phase as follows:

--COLLISION HANDLER function objectCollide( self, event ) local otherName = event.other.name local function onDelay( event ) local action = "" . . . elseif ( action == "leftField" ) then self.hasJoint = false ; self.touchJoint:removeSelf() ; self.touchJoint = nil end end . . . elseif ( event.phase == "ended" and otherName == "field" and self.hasJoint == true ) then local tr = timer.performWithDelay( 10, onDelay ) ; tr.action = "leftField" end end

And that’s basically it. If the object exits the field, the touch joint is removed and the object simply goes on its way. Of course, the full sample project involves a bit more than we’ve discussed up to this point, but this is the essential method behind radial gravity in Corona. Quite simple!

### Predicting Trajectory

*This part of the tutorial comes to you courtesy of Matt Webster, a.k.a. HoraceBury. Matt is a Corona Ambassador with a keen interest in physics, and he volunteers his skills and advice in the Corona Code Exchange and the forums.*

Predicting trajectory has traditionally been a confusing endeavor, and some developers simply guess and hope for the best. Today we’ll discuss the real solution!

The core task of predictive trajectory is gathering some basic data like the starting position and starting velocity of the projectile you want to launch. In regards to **touch events** in Corona, it might look like this:

local startingVelocity = { x=event.x-event.xStart, y=event.y-event.yStart } for i = 1,180 do local s = { x=event.xStart, y=event.yStart } local trajectoryPosition = getTrajectoryPoint( s, startingVelocity, i ) local circ = display.newCircle( trajectoryPosition.x, trajectoryPosition.y, 5 ) end

This loop draws the predicted trajectory path in 180 steps — 3 seconds of launch time at 60 fps — using vector circles. Essentially, we calculate the difference between the **event x/y** starting and ending positions as the “velocity.” Then, the position of each circle, and thus the trajectory plotted by the path, is gathered from the **getTrajectoryPoint** function as illustrated in the next step.

The core calculation is done in the following function, which uses the frames per second in “time steps” along with the assumed gravity and the values we gathered above.

function getTrajectoryPoint( startingPosition, startingVelocity, n ) --velocity and gravity are given per second but we want time step values here local t = 1/display.fps --seconds per time step at 60fps local stepVelocity = { x=t*startingVelocity.x, y=t*startingVelocity.y } local stepGravity = { x=t*0, y=t*9.8 } return { x = startingPosition.x + n * stepVelocity.x + 0.25 * (n*n+n) * stepGravity.x, y = startingPosition.y + n * stepVelocity.y + 0.25 * (n*n+n) * stepGravity.y } end

#### Firing the Projectile

On touch release, firing the projectile is simple. We generate the projectile (assuming it doesn’t already exist), assign the velocity values that we gathered, and send it flying across the trajectory path in a perfect match.

local function fireProj( event ) local proj = display.newImageRect( "object.png", 64, 64 ) physics.addBody( proj, { bounce=0.2, density=1.0, radius=14 } ) proj.x, proj.y = event.xStart, event.yStart local vx, vy = event.x-event.xStart, event.y-event.yStart proj:setLinearVelocity( vx,vy ) end

And that’s it! Based on a scenario set at 60 frames per second, we can accurately predict the trajectory of a flying object. Note that if you wish to use 30 frames per second, the only required adjustment is to change both instances of **0.25** in the **getTrajectoryPoint** function to **0.5**, and everything should mesh up perfectly.

### In Summary

That’s it for today’s tutorial. To see both of these examples in action, please access the PhysicsDemo folder on Dropbox. And, as always, please provide your feedback in the comments section below.

## Chris

Posted at 14:54h, 09 AprilWow great tutorials.

Haven’t thought about using Joints for the radial gravity. Have been using other methods and I feel I’ve overcomplicated things now 😉

Great read – thanks again!

## Mark

Posted at 10:46h, 10 AprilBrent – this is RIGHT ON TIME!

🙂

## Star Crunch

Posted at 16:01h, 10 AprilHi.

In case it’s useful to anybody, this complements the trajectory code:

If you have a launch speed and a target, you can use this to get the (0, 1, or 2) viable launch angles.

(Apologies if it’s incomplete… it was camped in a C# file waiting (and waiting and waiting…) to be converted and I don’t quite remember which project the original is from.)

[lua]

— Helper for special case: vertical trajectories

— Solve for t: y = v0 * t – g * t^2 / 2

local function VerticalLaunch (y, gravity, v0, get_times)

local disc = v0 * v0 – 2 * gravity * y

local a1, a2, t1, t2 = pi / 2, pi / 2

if disc >= 0 then

local root = sqrt(disc)

— If the target is below or coincident with the the launch point, it will always

— be a hit, given by the positive of the two times: when fired straight up, this

— is the time coming back down; when fired straight down, this is the expected

— drop time. In both cases the negative time is a virtual hit going up.

if y

— v0^2, sqrt(discriminant) > abs(v0). Accounting for this, determine the

— positive roots.

if get_times then

t1 = (v0 + root) / gravity

t2 = (root – v0) / gravity

end

— Otherwise, get the times going up and coming down when the point above is hit.

elseif get_times then

t1 = (v0 – root) / gravity

t2 = (v0 + root) / gravity

end

return a1, a2, t1, t2

end

end

— Computes the launch angles needed to hit a target along a parabolic trajectory.

— Cf. “Mechanics”, J.P. Den Hartog, pp. 187-9

— @param point Launch point.

— @param target Target point.

— @param gravity Gravity constant.

— @param v0 Launch speed.

— @param get_times If true, return times after the angles.

— @return If @e target can be hit, an angle that will yield a hit; otherwise, @b nil.

— @return If @e target can be hit, another angle that will yield a hit; may be the same angle as the first.

— @return Time lapse to hit with angle #1.

— @return Time lapse to hit with angle #2; may differ even with same angles, i.e. when firing straight up.

function GetLaunchAngles (point, target, gravity, v0, get_times)

assert(gravity > 0)

assert(v0 > 0)

local dx, dy, y = target[1] – point[1], target[2] – point[2], target[3] – point[3]

local x2 = dx * dx + dy * dy

local a1, a2, t1, t2

— If the target is above or below, do special vertical case.

if x2 1, the sine argument was invalid: the shot will miss.

—

— Wikipedia offers the transformation:

— alpha = atan((v^2 +- sqrt(v^4 – g * (g * x^2 + 2 * y * v^2))) / (g * x))

else

local sin_numer = y + gravity * x2 / (v0 * v0)

local dist = sqrt(x2 + y * y)

if sin_numer <= dist then

local phi = asin(y / dist)

— 2 * alpha – phi = asin(RHS)

— 2 * alpha = asin(RHS) + phi

— alpha = (asin(RHS) + phi) / 2

local angle = asin(sin_numer / dist)

a1 = (phi + angle) / 2

— sin(X) = sin(pi – X)

— sin(pi – (2 * alpha – phi)) = RHS

— pi – 2 * alpha + phi = asin(RHS)

— -2 * alpha = asin(RHS) – pi – phi

— 2 * alpha = phi + pi – asin(RHS)

— alpha = (phi + pi – asin(RHS)) / 2

a2 = (phi – angle + pi) / 2

— Get times by solving for t: x(t) = v0 * cos(alpha) * t.

if get_times then

local x = sqrt(x2)

t1 = x / (v0 * cos(a1))

t2 = x / (v0 * cos(a2))

end

end

end

— Return desired set of results.

if t1 then

return a1, a2, t1, t2

elseif a1 then

return a1, a2

else

return nil

end

end

[/lua]

## Patrick Waldner

Posted at 15:37h, 28 NovemberIs there any way to calculate the angle when I don’t want to shoot from a Point, but rather from the surface of a Sphere with radius r ? Imagine a spherical turret that rotates around its centre and fires out of a barrel that comes straight out of it.

So with the angle and radius of the turret you actually define the starting point of the projectile.

## Philipp Lenssen

Posted at 12:09h, 11 AprilGreat, more tutorials like these please!

Two small notes. In the game Spikeball and in other apps like Color Sound Machine, I did a simpler version of magnets by just having them screen-globally look for sprites. This also works in reverse with fans. I’ve a little sprite class for which I call self:magneticTowards(goal.x, goal.y), and then it does something like

function self:magneticTowards(x, y)

local strength = 1000

local distance = misc.getDistance( {x = x, y = y}, self )

local power = app.magneticMaxValue / distance / strength

local pushX = (x – self.x) * power

local pushY = (y – self.y) * power

self:applyForce(pushX, pushY, self.x, self.y)

end

For the path prediction, I’m currently doing something in which I need a full path including bounces off of walls and such. So I’m sending an invisible bullet item which uses a phase timing to draw its path in decreasing opacity and with a bit of a glow. (Such path could also be precached for quicker loading if the options and placeable positions are limited.) This bullet has a special collision setting to not touch enemies*, and only walls, and it doesn’t make sounds when it bumps and such, but otherwise it reuses the same routines for handling this attackObject.

*

local categoryAttackObject = ‘ X ‘

local categoryEnemy = ‘ X ‘

local maskIgnoresAttackObjectsAndEnemies = ‘ XXXX X’

(I’m using a string to binary conversion to visually lay out bit collision maps.)

## Philipp Lenssen

Posted at 12:10h, 11 AprilThe strings got eaten up by the comment functionality here, but basically it’s

‘______X_’

‘_____X__’

‘_XXXX__X’

## Star Crunch

Posted at 14:34h, 11 AprilOh wow, the formatting got put through the wringer. 🙂

The – glyphs should be Lua comments. Where they aren’t minus signs, anyway. :/

Looking over it a bit… If anybody does end up using what I posted, that code was written with 3D in mind, over the XY plane. For basic 2D with display objects for point and target, you can just substitute

local dx, y = target.x – point.x, target.y – point.y

local x2 = x * x

in the obvious places and everything else is the same. (Non-straight down gravity remains an exercise for the reader.)

Following what Matt wrote, once you’ve got your angle, you can put it together with your launch speed to get the linear velocity as

local vx, vy = speed * math.cos(angle), speed * math.sin(angle)

proj:setLinearVelocity(vx, vy)

(I think this came from the AI in an old demo for a basketball-like game (Tlachtli): trying to aim for the “baskets”, given an average ball knock-away speed. Another application is aiming at falling objects, though you have to make sure to aim at a FUTURE position.)

## Ryan King

Posted at 11:47h, 15 AprilI was wondering how you would touch and drag both the magnet and the field together, would love some help, thanks Ryan

## Brent Sorrentino

Posted at 18:50h, 17 AprilHi Ryan,

You could accomplish this in three ways, depending on your preference:

1) Make the field a secondary body of the same physics object, and build your collision filters so that the joints are only attached when objects hit the field part (not the magnet part)

2) Attach the field to the magnet using a weld joint.

3) Make the magnet draggable, and then constantly update the field to the same position while the user is dragging the magnet around.

Have fun!

Brent

## Byron

Posted at 12:22h, 02 MayIs there a way to incorporate LinearDamping into the projected trajectory calculation?

This would solve a ton of issues I am having with my app…

Thanks in advance.

## johannes_lalala

Posted at 04:11h, 13 JanuaryHi Brent,

The documentation is a bit unclear about how the force of a touch joint is modelled:

The documentation says it creates a “temporary elastic force”, which I interpret as “spring” force (i.e. x” = -d* x, when the center of the force is at 0). In other words: the force is proportional to the distance between field center and object, like there was a spring between them.

Whereas an actual “gravitational” force is indirecly proportional to the square distance of object and center, (i.e. x” = -G/ |x|^2). The force is weaker the bigger the distance of center and object.

Could you verify which one (if any) applies here?

Thanks! Johannes

## Brent Sorrentino

Posted at 10:27h, 13 JanuaryHi Johannes,

This demo isn’t “scientifically perfect” if that’s what you’re seeking… it merely uses the Box2D mouse joint (in Corona, “touch joint”) to exert force on the object toward a specific point (in this case, toward the center of the “planet”). You can read more about the mouse joint is the Box2D manual here:

http://www.box2d.org/manual.html#_Toc258082974

Best regards,

Brent

## johannes_lalala

Posted at 08:28h, 15 JanuaryThanks Brent!

seems to me like the touch/mouse thing acts in fact like a spring (but the original docs aren’t very accurate too :).

just in case somebody also wants more control over the nature of the force, here’s how I did the radial force affecting a body:

f_centerx=somex

f_centery=somey

physics.addBody( body, ‘dynamic’ )

— then set up a frame listener which just does this:

runtime:addEventListener( ‘enterFrame’, function()

— this gives us an undampened spring movement, the body will be pulled stronger to f_center the greater the distance

local fx =( f_centerx – body.x ) * c

local fy =( f_centery – body.y ) * c

— with this modification to fx and fy we get a constant force to the center, independent of the distance

fx = fx / sqrt( fx^2 + fy^2 )

fy = fy / sqrt( fy^2 + fy^2 )

— ..and this gives a gravitational field

fx = fx / ( fx^2 + fy^2 )

fy = fy / ( fy^2 + fy^2 )

body:applyForce( fx, fy, body.x, body.y )

end

if you use the latter, depending on circumstances, perhaps you don’t need the field to be activated by a certain distance because it’s very weak at greater distances.

regards, Johannes

## Chris

Posted at 12:11h, 29 MayJohannes,

Great input, as this is what I was looking for! However, I just wanted to double check that your equations are accurate, specifically:

fx = fx / ( fx^2 + fy^2 )

fy = fy / ( fy^2 + fy^2 )

fx = fx / sqrt( fx^2 + fy^2 )

fy = fy / sqrt( fy^2 + fy^2 )

You can see two of these equations does not use fx at all. I was not sure if this was a typo, and needed fx instead of fy. Where can I find more information about exactly what these equations are doing? Also, I am guessing that the variable ‘c’ in the first equation is just a constant value?

Finally, not sure if you would know how to do this, but would it be possible to increase/decrease the force depending on the size of the object? For instance if I have a ball and two different size magnets – how would I apply a stronger “gravitational pull” toward the larger magnet? This would be similar to what you are doing in your last two equations, but most likely with another variable.

Hopefully you see this reply…as it has been a couple of months!

Thank you!

Chris

## Nick

Posted at 16:31h, 12 MarchOn a connected topic it would be great to get som esuport for enabling Box2D getMass function for even more control over objects. see http://feedback.coronalabs.com/forums/188732-corona-sdk-feature-requests-feedback/suggestions/5625479-box2d-getmass

## Mark

Posted at 22:11h, 03 JulyHi Brent,

I have managed to get this working but I have an issue when transitioning from the timer to the OnDelay function. For some reason the timerit does not pass the event.other into the function so you get a nil index value (the event.other is nil within the OnDelay function). If I change the event.other.x to field.x it will work but this make it hard when creating multiple fields. Any ideas?