Monkey Box2D Port (part 2)

Monkey Programming Forums/User Modules/Monkey Box2D Port (part 2)

DruggedBunny(Posted 2012) [#1]
Port of 2D physics engine Box2D by muddy_shoes...


It's out and the project home is here: http://code.google.com/p/monkeybox2d/



Continued from here.


jondecker76(Posted 2012) [#2]
I found one bug so far and have one feature request:

Feature Request:
DrawDebugData shouldn't execute a A CLS - this prevents drawing the debug data over mojo graphics for debugging, for example. (You can set an alpha parameter for the debug data). If anything, at least DrawDebugData could take an optional boolean parameter telling whether or not to perform a CLS automatically.

Bug Report:
I'm playing around with different shape types to learn my way around Box2D. I have found that collision detection between a dynamic body and a static body set to the b2EdgeShape type will cause a runtime error. Here is an example which reproduces this:
format_code('
' Box2D Tests
' Remember, Box2D works with MKS - Meters, Kilograms and Seconds!

'----Includes----'
Import box2d.collision
Import box2d.collision.shapes
Import box2d.common.math
Import box2d.dynamics.contacts
Import box2d.dynamics
Import box2d.flash.flashtypes
Import box2d.common.math.b2vec2
Import mojo

' USEFUL CONVERSIONS
Function RadToDeg:Int(rad:Float)
If rad <> 0.0 Then Return (rad * 180.0) / PI Else Return 0
End Function
Function InchesToMeters:Float(inches:Float)
Return inches*0.0254
End Function
Function MetersToInches:Float(meters:Float)
Return meters*39.3700787
End Function
Function PixelsToMeters:Float(pixels:Float,ratio:Float)
Return pixels/ratio
End Function
Function MetersToPixels:Float(meters:Float,ratio:Float)
Return meters*ratio
End Function


Function Main()
New Box2DLoop
End Function


Class Box2DLoop Extends App

'Box 2D Parameters Set
Field world:b2World 'Box2D physical World Object
Field velocityIterations:Int = 8 'Iterations as suggested in the manual
Field positionIterations:Int = 3 'Iterations as suggested in the manual
Field timeStep:Float = 1.0/60 'Update the physics 60 times per second
Field physScale:Float = 40 'MKS scale to Pixel conversion factor
Field debugdrawscale: Float = 40 'This Affects the size of the physical Body Display

Field ground:b2Body 'A phsyical body Type of box2d decleration.
Field ball:b2Body
Field bumper:b2Body

Method OnCreate()
'Box2D Setups
Local bd:b2BodyDef ' Re-use these to create the Box2D objects
Local fd:b2FixtureDef
Local sd :b2PolygonShape

Local cs:b2CircleShape


Local doSleep:Bool = True

'Creating a new Box2D World
Self.world = New b2World(New b2Vec2(0,0),doSleep)
world.SetGravity(New b2Vec2(0.0,9.7))

'Creating a Ground Box
'From some reason, if you dont create this ground box nothing works...
' Set up a shape definition
sd = New b2PolygonShape()
sd.SetAsBox(PixelsToMeters(640,physScale),0.0001)

' Set up a fixture definition
fd = New b2FixtureDef()
fd.density = 1.0
fd.friction = 0.5
fd.restitution = 0.9 ' Rebounding effect
fd.shape = sd

' Set up a body definition
bd = New b2BodyDef()
bd.type = b2Body.b2_staticBody
bd.position.Set(PixelsToMeters(320,physScale),12)

' Create the body!
ground=Self.world.CreateBody(bd)
ground.CreateFixture(fd)
ground.SetUserData(New StringObject("ground")) 'give object a name for collision detection in contactlistener


'-------------
' SETUP A BALL
'-------------
' First we create the body using CreateBody. By default bodies are static, so we should set the
' b2BodyType at construction time To make the body dynamic.
' (bd=Body Definition)
bd = New b2BodyDef()
bd.type = b2Body.b2_Body
bd.position.Set(0.5,0)
Self.ball=world.CreateBody(bd)

'Next we create And attach a polygon shape using a fixture definition. First we create a shape:
'(sd=Shape Definition)
cs = New b2CircleShape()
cs.m_radius = 0.254
cs.m_p.Set(2,3)


' Next we create a fixture definition using the shape definition. Notice that we set density to 1. The default density is
'zero. Also, the friction on the shape is set To 0.3.
fd = New b2FixtureDef()
fd.shape = cs
fd.density = 1.0
fd.friction = 0.5
fd.restitution = 0.1




' Using the fixture definition we can now create the fixture. This automatically updates the mass of the
' body. You can add as many fixtures as you like To a body. Each one contributes To the total mass.
ball.CreateFixture(fd)
ball.SetUserData(New StringObject("ball")) 'give object a name for collision detection in contactlistener






'---------------
'Set Up A Bumper
'---------------
bd = New b2BodyDef()
bd.type = b2Body.b2_staticBody
bd.position.Set(0,0)
Self.bumper=world.CreateBody(bd)


'This an edge shape.
Local v1:b2Vec2
Local v2:b2Vec2
v1= New b2Vec2(PixelsToMeters(0,physScale), PixelsToMeters(400,physScale))
v2= New b2Vec2(PixelsToMeters(100,physScale), PixelsToMeters(480,physScale))
Local edge:b2EdgeShape
edge = New b2EdgeShape(v1, v2)



fd = New b2FixtureDef()
fd.shape = edge
fd.density = 1.0
fd.friction = 0.5
fd.restitution = 1.5


Self.bumper.CreateFixture(fd)
Self.bumper.SetUserData(New StringObject("bumper"))





'Display Setup
SetUpdateRate(60)

'Box2D Debug Settings 'Delete this section if you dont need to see the physical process in graphics.
Local dbgDraw :b2DebugDraw = New b2DebugDraw()
dbgDraw.SetDrawScale(debugdrawscale)
dbgDraw.SetFillAlpha(0.3)
dbgDraw.SetLineThickness(1.0)
dbgDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit)'| b2DebugDraw.e_pairBit)
Self.world.SetDebugDraw(dbgDraw)

End Method

Method OnUpdate()
'The Stepping of Box2D Engine
world.TimeStep(timeStep,velocityIterations,positionIterations)
world.ClearForces() 'Dont know why you need this.
End Method

Method OnRender()
Cls

'Box2D Display Section
Self.world.DrawDebugData(False)

End Method
End Class
')


jondecker76(Posted 2012) [#3]
I'm not understanding Get/Set userdata. Yes, I understand that you can store arbitrary objects in there, I'm just not getting it from the Monkey language end of things.... So can anybody show how to Set and Get userdata with an example for string, custom class instance, etc? I'm just not understanding the casting involved in both directions...

thanks


muddy_shoes(Posted 2012) [#4]
Thanks for the issue reports. I'll take a look when I get the chance.

As for the UserData reference. Yes, you just have to pass in something that is an object and cast back to whatever specialised class it is when you retrieve it for use.

format_code('
Local player:b2Body = world.CreateBody(bd)
player.SetUserData(New StringObject("player"))
Print StringObject(player.GetUserData())
')


jondecker76(Posted 2012) [#5]
Jesus, all this casting nightmare is killing me! I'm now experimenting with Joints... world.CreateJoint(jointDefinition) returns a b2Joint. However, I need it casted as a b2RevoluteJoint! I've tried this, but of course it doesn't work:
format_code('
Field paddleJoint:b2Joint
...
...
Local jointDef:b2RevoluteJointDef = New b2RevoluteJointDef
jointDef.Initialize(paddle,ground, paddle.GetWorldCenter());
jointDef.enableLimit=True
jointDef.lowerAngle=DegToRad(0)
jointDef.upperAngle=DegToRad(45)
jointDef.enableMotor = True;
jointDef.maxMotorTorque = 20;

b2RevoluteJoint(paddleJoint)=world.CreateJoint(jointDef) 'DING DING DING

')

I must say, the Monkey documentation on casting really sucks :(


jondecker76(Posted 2012) [#6]
Ok, got it to work with a bunch of experimentation... The key for me was:
format_code('
paddleJoint=b2RevoluteJoint(world.CreateJoint(jointDef))
')

I guess in the end I have to keep trying to get my head around how Monkey handles casting - it seems so back wards to me.

Anyways, I almost have a full pinball simulation running now :)


muddy_shoes(Posted 2012) [#7]
I had a look at the edge error. EdgeShapes weren't properly implemented in the version of Box2D that I ported (I don't think they were completed in the main version at the time the AS3 port was done). While I patched it up a bit to get the edge test running, looking into it more deeply there's still a lot of stuff that simply isn't there.

The problem is that the whole library has moved on and it's not straightforward to just find the bit of missing code I need and add it in. I'll have a bit of a look around this week but edges might just have to be left not working until I can find the time to bring the library up to date with the main C++ lib. When that's done then edges and, even better, edge chains will work.

For now you'll have to make do with narrow polygons rather than edges.


jondecker76(Posted 2012) [#8]
Thanks for the update.

I'm still sticking with a simple Pinball example to learn to use Box2D (and Monkey). I'm at another stumbling block. I have a paddle/flipper working - I'm trying to use 3 fixtures to create the tapered and rounded flipper shape. The circle shapes at each end process physics as they should - the polygon shape in the middle does not. Its very easy to see in this example:
(Note: the space key operates the flipper)
Line 230-290 are responsible for setting up the paddle.
format_code('
' Box2D Tests
' Remember, Box2D works with MKS - Meters, Kilograms and Seconds!

'----Includes----'
Import box2d.collision
Import box2d.collision.shapes
Import box2d.common.math
Import box2d.dynamics.contacts
Import box2d.dynamics
Import box2d.flash.flashtypes
Import box2d.common.math.b2vec2
Import mojo

' USEFUL CONVERSIONS
Function RadToDeg:Int(rad:Float)
If rad <> 0.0 Then Return (rad * 180.0) / PI Else Return 0
End Function
Function DegToRad:Float(deg:Float)
Return deg * 0.0174532925
End Function
Function InchesToMeters:Float(inches:Float)
Return inches*0.0254
End Function
Function MetersToInches:Float(meters:Float)
Return meters*39.3700787
End Function
Function PixelsToMeters:Float(pixels:Float,ratio:Float)
Return pixels/ratio
End Function
Function MetersToPixels:Float(meters:Float,ratio:Float)
Return meters*ratio
End Function

Class Collisions Extends b2ContactListener
Method BeginContact : Void (contact:b2Contact)
' Get object "A"
Local A:Object=contact.GetFixtureA().GetBody().GetUserData()
' Get object "B"
Local B:Object=contact.GetFixtureB().GetBody().GetUserData()
'Print A(String)
End
Method EndContact : Void (contact:b2Contact)
End
Method PreSolve : Void (contact:b2Contact, oldManifold:b2Manifold)
End
Method PostSolve : Void (contact:b2Contact, impulse:b2ContactImpulse)
End

End Class

Function Main()
New Box2DLoop
End Function


Class Box2DLoop Extends App

'Box 2D Parameters Set
Field world:b2World 'Box2D physical World Object
Field velocityIterations:Int = 8 'Iterations as suggested in the manual
Field positionIterations:Int = 3 'Iterations as suggested in the manual
Field timeStep:Float = 1.0/60 'Update the physics 60 times per second
Field physScale:Float = 40 'MKS scale to Pixel conversion factor
Field debugdrawscale: Float = 40 'This Affects the size of the physical Body Display

Field ground:b2Body
Field leftWall:b2Body
Field rightWall:b2Body
Field ball:b2Body
Field bumper:b2Body
Field paddle:b2Body
Field paddleJoint:b2RevoluteJoint

Field listener:Collisions

Method OnCreate()
listener = New Collisions

'Box2D Setups
Local bd:b2BodyDef ' Re-use these to create the Box2D objects
Local fd:b2FixtureDef
Local sd :b2PolygonShape

Local cs:b2CircleShape


Local doSleep:Bool = True

'Creating a new Box2D World
Self.world = New b2World(New b2Vec2(0,0),doSleep)
world.SetGravity(New b2Vec2(0.0,9.7))
world.SetContactListener(listener)

'----------------------
'Creating a Ground Box
'----------------------
'From some reason, if you dont create this ground box nothing works...
' Set up a shape definition
sd = New b2PolygonShape()
sd.SetAsBox(PixelsToMeters(640,physScale),0.0001)

' Set up a fixture definition
fd = New b2FixtureDef()
fd.density = 1.0
fd.friction = 0.5
fd.restitution = 0.2 ' Rebounding effect
fd.shape = sd

' Set up a body definition
bd = New b2BodyDef()
bd.type = b2Body.b2_staticBody
bd.position.Set(PixelsToMeters(320,physScale),12)

' Create the body!
ground=Self.world.CreateBody(bd)
ground.CreateFixture(fd)
ground.SetUserData(New StringObject("Ground")) 'give object a name for collision detection in contactlistener
'--------------
'Left Wall
'--------------
'Add it as a fixture to the ground
bd = New b2BodyDef()
bd.type = b2Body.b2_staticBody
bd.position.Set(0,0)
Self.leftWall=world.CreateBody(bd)

sd = New b2PolygonShape()
sd.SetAsBox(0.0001,PixelsToMeters(480,physScale))

fd = New b2FixtureDef()
fd.density = 1.0
fd.friction = 0.5
fd.restitution = 0.2 ' Rebounding effect
fd.shape = sd
leftWall.CreateFixture(fd)

'--------------
'Right Wall
'--------------
'Add it as a fixture to the ground
bd = New b2BodyDef()
bd.type = b2Body.b2_staticBody
bd.position.Set(PixelsToMeters(640,physScale),0)
Self.rightWall=world.CreateBody(bd)

sd = New b2PolygonShape()
sd.SetAsBox(0.0001,PixelsToMeters(480,physScale))

fd = New b2FixtureDef()
fd.density = 1.0
fd.friction = 0.5
fd.restitution = 0.2 ' Rebounding effect
fd.shape = sd
rightWall.CreateFixture(fd)



'-------------
' SETUP A BALL
'-------------
' First we create the body using CreateBody. By default bodies are static, so we should set the
' b2BodyType at construction time To make the body dynamic.
' (bd=Body Definition)
bd = New b2BodyDef()
bd.type = b2Body.b2_Body
bd.position.Set(0.5,0)
Self.ball=world.CreateBody(bd)

'Next we create And attach a polygon shape using a fixture definition. First we create a shape:
'(sd=Shape Definition)
cs = New b2CircleShape()
cs.m_radius = 0.254
cs.m_p.Set(2,3)


' Next we create a fixture definition using the shape definition. Notice that we set density to 1. The default density is
'zero. Also, the friction on the shape is set To 0.3.
fd = New b2FixtureDef()
fd.shape = cs
fd.density = 1.0
fd.friction = 0.5
fd.restitution = 0.1




' Using the fixture definition we can now create the fixture. This automatically updates the mass of the
' body. You can add as many fixtures as you like To a body. Each one contributes To the total mass.
ball.CreateFixture(fd)
ball.SetUserData(New StringObject("ball")) 'give object a name for collision detection in contactlistener






'---------------
'Set Up A Bumper
'---------------

bd = New b2BodyDef()
bd.type = b2Body.b2_staticBody
bd.position.Set(1,10)
Self.bumper=world.CreateBody(bd)


'This an edge shape.
'Local v1:b2Vec2
'Local v2:b2Vec2
'v1= New b2Vec2(PixelsToMeters(0,physScale), PixelsToMeters(400,physScale))
'v2= New b2Vec2(PixelsToMeters(100,physScale), PixelsToMeters(480,physScale))
'Local edge:b2EdgeShape
'edge = New b2EdgeShape(v1, v2)
sd = New b2PolygonShape()
sd.SetAsBox(PixelsToMeters(100,physScale),0.0001)



fd = New b2FixtureDef()
fd.shape = sd
fd.density = 1.0
fd.friction = 0.5
fd.restitution = 1.5


Self.bumper.CreateFixture(fd)
Self.bumper.SetAngle(DegToRad(45))
Self.bumper.SetUserData(New StringObject("bumper"))

'-----------------
' SET UP A PADDLE
'----------------
' A paddle is comprized of multiple shapes/fixtures...
bd = New b2BodyDef()
bd.type = b2Body.b2_Body
bd.position.Set(1.7,6)
Self.paddle=world.CreateBody(bd)

'Big Circle
cs = New b2CircleShape()
cs.m_radius = 0.4
cs.m_p.Set(0,0)
fd = New b2FixtureDef()
fd.shape = cs
fd.density = 0.1
fd.friction = 0.5
fd.restitution = 0.1
paddle.CreateFixture(fd)
'Small Circle
cs = New b2CircleShape()
cs.m_radius = 0.25
cs.m_p.Set(1,0)
fd = New b2FixtureDef()
fd.shape = cs
fd.density = 0.1
fd.friction = 0.5
fd.restitution = 0.1
paddle.CreateFixture(fd)

'Polygon
sd = New b2PolygonShape()

Local vert1:b2Vec2 = New b2Vec2(0,-0.4)
Local vert2:b2Vec2 = New b2Vec2(0,0.4)
Local vert3:b2Vec2 = New b2Vec2(1,0.25)
Local vert4:b2Vec2 = New b2Vec2(1,-0.25)
Local verts:b2Vec2[]=[vert1,vert2,vert3,vert4]
sd.SetAsArray(verts,4)
fd = New b2FixtureDef()
fd.shape = sd
fd.density = 0.05
fd.friction = 0.5
fd.restitution = 0.1
paddle.CreateFixture(fd)

paddle.ResetMassData()


' Joint the paddle
Local jointDef:b2RevoluteJointDef = New b2RevoluteJointDef
jointDef.Initialize(paddle,ground, paddle.GetWorldCenter());
jointDef.enableLimit=True
jointDef.lowerAngle=DegToRad(-10)
jointDef.upperAngle=DegToRad(45)
jointDef.enableMotor = True;
jointDef.maxMotorTorque = 20;

paddleJoint=b2RevoluteJoint(world.CreateJoint(jointDef))




'Display Setup
SetUpdateRate(60)

'Box2D Debug Settings 'Delete this section if you dont need to see the physical process in graphics.
Local dbgDraw :b2DebugDraw = New b2DebugDraw()
dbgDraw.SetDrawScale(debugdrawscale)
dbgDraw.SetFillAlpha(0.3)
dbgDraw.SetLineThickness(1.0)
dbgDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit)'| b2DebugDraw.e_pairBit)
Self.world.SetDebugDraw(dbgDraw)

End Method

Method OnUpdate()
'Operate the paddle
If(KeyDown(KEY_SPACE))
paddleJoint.SetMotorSpeed(DegToRad(720));
Else
paddleJoint.SetMotorSpeed(DegToRad(-720));
End If

'Place the Ball
If(MouseDown(MOUSE_LEFT))
Self.ball.SetPositionAndAngle(New b2Vec2(PixelsToMeters(MouseX(),physScale),PixelsToMeters(MouseY(),physScale)),0)
Self.ball.SetLinearVelocity(New b2Vec2(0,0))
End If

'The Stepping of Box2D Engine
world.TimeStep(timeStep,velocityIterations,positionIterations)
world.ClearForces()
End Method

Method OnRender()
Cls

'Box2D Display Section
Self.world.DrawDebugData()

End Method
End Class
')
Any idea why the polygon isn't colliding?
(Please excuse the messy code, I'm just trying to rapidly prototype and learn so I can so that I can determine the best way to wrap Box2D in my own classes.)


NoOdle(Posted 2012) [#9]
You have the order of vertices in the wrong order, try amending the line to this:
format_code('Local verts:b2Vec2[]=[vert4,vert3,vert2,vert1]')


jondecker76(Posted 2012) [#10]
Thanks, much better!
That's really strange though, the Box2D manual stated that the verts had to be in Counter-Clockwise order (which I did). That's what I get for reading directions!


NoOdle(Posted 2012) [#11]
Yea that confused me as well!


CopperCircle(Posted 2012) [#12]
Hi, I have just tried to compile my box2d code on Monkey v60 but get this error:

Code: Local center :b2Vec2 = b2Math.MulX(xf, circle.m_p)

Error : Unable to find overload for MulX(b2Transform,b2Vec2)

Not sure why, thanks.


muddy_shoes(Posted 2012) [#13]
In the current version of box2D MulX is a function with the signature:

Function MulX:Void (T:b2Transform, v:b2Vec2, out:b2Vec2)

It doesn't return a b2Vec2, it populates a b2Vec2 that the caller provides in order to reduce the number of short-lived objects.


CopperCircle(Posted 2012) [#14]
Thanks.


Kalakian(Posted 2012) [#15]
I can't seem to get a single body with multiple fixtures working. All the fixtures are appearing in the same place, and nothing changes if I set the m_centroid of a polygon shape.

Some sample code:

[monkeycode]
' Create border of boxes
Local wall:b2PolygonShape = New b2PolygonShape()
Local wallBd:b2BodyDef = New b2BodyDef()
Local wallB:b2Body

' Specify some geometry for the borders
' top
wallBd.position.Set(0, 0)
wall.SetAsBox(scaledWidth, 0.05)
wallB = world.CreateBody(wallBd)
wallB.CreateFixture2(wall)
' bottom
wall.SetAsBox(scaledWidth, 0.05)
wall.m_centroid.y = scaledHeight
wallB.CreateFixture2(wall)
[/monkeycode]

Both fixtures are appearing at the same location (0,0) no matter what I set the centroid of the second wall to.

Am I doing something wrong, or is this not supported in the Monkey port (yet)?


Kalakian(Posted 2012) [#16]
Did I bit more digging and I see what I did wrong. If I want to create a box at some point other than (0,0), I should use SetAsOrientedBox() rather than SetAsBox().

Fixed sample code:

[monkeycode]
' Create border of boxes
Local wall:b2PolygonShape = New b2PolygonShape()
Local wallBd:b2BodyDef = New b2BodyDef()
Local wallB:b2Body

' Specify some geometry for the borders
' top
wallBd.position.Set(0, 0)
wall.SetAsBox(scaledWidth, 0.05)
wallB = world.CreateBody(wallBd)
wallB.CreateFixture2(wall)
' bottom
wall.SetAsOrientedBox(scaledWidth, 0.05, New b2Vec2(0, scaledHeight))
wallB.CreateFixture2(wall)
[/monkeycode]

Similarly, I was positioning a circle by accessing its m_p member directly, but I should've been using SetLocalPosition().

Guess that's what I get when I trust google search results rather than reading the documentation myself :P


Volker(Posted 2012) [#17]
Has anyone box2d working with MonkeyV62?
With V56b everything worked fine.

With V62 the demo gives me this error:
C:/Monkey/modules/box2d/demo/tests/testdominostack.monkey<66> : Error : Identifier 'PI' not found.


Kalakian(Posted 2012) [#18]
Working fine for me with V62. I deleted the build folder, checked for updates to the Hg repository (there was none), and ran the demos.

PI is defined in Constants.monkey


Volker(Posted 2012) [#19]
Updating to newest version solved it. Thanks for testing.


Skn3(Posted 2012) [#20]
Not sure if muddy_shoes is still updating the repo, but I added the RopeJoint.

https://code.google.com/r/jonpittock-monkeybox2d/

There is a cloned repo here for anyone who wants to use it.

There is an example of a rope joint here:
http://www.skn3.com/junk/codefun/physics/ropejoint/MonkeyGame.html


muddy_shoes(Posted 2012) [#21]
It's not exactly a flurry of activity seeing as the port appears to be equivalent with 2.1a and working fine but I've not abandoned it. My last commit was at the end of October as you would have seen when you cloned the repo.

You're quite welcome to commit rights if you want.


Skn3(Posted 2012) [#22]
Hey muddy_shoes,

might be safer to merge from my clone, just so I don't end up breaking something.. lol!

:D Out of interest did you write some kind of code parser to do your conversion, or was it by hand?


muddy_shoes(Posted 2012) [#23]
Much of the grunt work was done with a C# program that mostly consisted of a large number of regexes. Then a fair amount of hand-patching to fix-up what was left.

I'll see about pulling your additions over. Are you going to add demo tests for them?

Also, I notice you've pulled in Erin Catto's license text although it's a port. Would you be okay with me replacing the text with the standard port license text and attribution? I did check with Erin Catto and those on the Flash port I could contact and they were okay with this.


Skn3(Posted 2012) [#24]
Hey muddy_shoes,

Yeah no problem with license text I kinda just left it as is from where I got it, so replace-away.

Re the demo tests, I am slightly pressed for time so will have to do the minimum amount of code I am afraid. You don't mind adding one do you? If there is anything major complicated that I add I'll do a demo though.

I just ported over a verlet rope library to monkey (just finishing it off now) probably not a good idea to merge anything like that into box2D, but when combined with the a box2D joint it lets you do some cheap cut the rope style ropage :D


muddy_shoes(Posted 2012) [#25]
Much like yourself I don't really have time to be putting coding time into stuff that's irrelevant to my own needs right now. I'll get to it sometime down the line, I guess.


Skn3(Posted 2012) [#26]
np, its not really a massive requirement as its really just another joint type :)

btw here is a demo of box2D 2 bodies connected via RopeJoint with a verlet rope rendered on the top.

http://www.skn3.com/junk/codefun/physics/ropejoint_verlet/MonkeyGame.html


CopperCircle(Posted 2012) [#27]
Wow nice update Skn3, would be nice to get the verlet code to add to my Box2D framework along with the new joint type.


Skn3(Posted 2012) [#28]
here you go. (sorry no example code)
http://www.monkeycoder.co.nz/Community/posts.php?topic=4025


Skn3(Posted 2012) [#29]
Hey muddy_shoes, maybe it would be a good idea to add me as a committer? I added a few small fixes / tweaks in the past few days so would be easier to just commit them direct. I didn't think I would be doing much!


muddy_shoes(Posted 2012) [#30]
I've been thinking about this and many OS projects work by just issuing pull requests from repo clones these days. If I was using Subversion it would be a different matter, but with Git and Mercurial it's supposedly a relatively easy way of going about working collaboratively without messing in the same repository.

Let me take a look at how that works out with googlecode and I'll get back to you. If it turns out to be a pain then I can give commit rights and we'll can agree to use individual branches to avoid stepping on toes etc.


muddy_shoes(Posted 2012) [#31]
I've been working on performance (specifically Android) for a couple of days. On my phone I've got a 50-100% speed-up depending on the circumstances.

There are a few small API changes that might need a line or two changing in your code and although I've tested it's possible I've broken something. If anyone is using the module on Android (or anything else, I guess) and found it a little chuggy you might want to pull the latest changes from the repository and see how it looks for you.

I'll do a zip release and version update after I've worked with it for a bit longer and had a chance to look over Skn3's proposed additions/fixes. Probably next week sometime.


CopperCircle(Posted 2012) [#32]
Good news, Im using it in two projects at the mo and any extra speed would be great...


Volker(Posted 2012) [#33]
Yes, indeed good news.


Skn3(Posted 2012) [#34]
*thumbs up*,

I spent a chunk of time looking at the possibility of porting over the latest c++ build. I ended up, after much tinkering, thinking that it would be best to wait for the new monkey module/target features that are possibly coming. Porting over the c++ version vs converting the box2dflash would be a giant leap in development time! It is a shame they didn't keep the flash box2d port alive! It makes an easy translation to many languages. Looked at the flash alchemy port and well I didn't even bother wasting time, what a kludgefest! I guess if it works it works, but still!

re: the minor callback interface approach I made in my clone: dev's flixel module uses this approach as does monkey's new stream stuff. It makes it so much cleaner then having to create a separate callback object and passing a pointer to various game bits into it.

Good work on speed improvements btw :D


muddy_shoes(Posted 2012) [#35]
I've looked at moving to porting directly from the C/C++ source before. I even started a branch for it. Not doing it is just a matter of balancing the time required with the fact that I have no current need for the more recent features.

Flash is closer to Monkey in syntax but there's as much effort in reworking the code from Flash that trusts the GC too much as there is in converting C/C++ code that doesn't care about GC issues. Much of the syntax conversion pain was more down to Monkey's "one error report allowed" compilation strategy than the actual problems of figuring out how to port the intention.


muddy_shoes(Posted 2012) [#36]
Finally got around to this. Version 1.0.11 is now in the repo and available as a zip on the downloads page: http://code.google.com/p/monkeybox2d/downloads/list

Just to repeat the warning that there are a couple of interface changes. They should only involve a few minutes work if anything, but just be aware that it may not be a drop-in and recompile update.


MikeHart(Posted 2012) [#37]
Thank you, much appreciated.


Volker(Posted 2012) [#38]
Great speed improvement. I can pick up an older game which was deferred and finish it. Thanks.


muddy_shoes(Posted 2012) [#39]
Glad to get confirmation that someone else sees a noticeable boost. You never know if optimisations are only really of use to your specific use case/platform of interest.


Nobuyuki(Posted 2013) [#40]
question about the box2d port, since I'm just getting started with it: Why are there two different CreateFixture methods (CreateFixture / CreateFixture2)? Is it not possible to overload them?


muddy_shoes(Posted 2013) [#41]
Because that was what they were called in the Flash version:

http://www.box2dflash.org/docs/2.1a/reference/Box2D/Dynamics/b2Body.html


Nobuyuki(Posted 2013) [#42]
I'm guessing that's the same reason for most of the naming convention changes, then. (b2_dynamicBody is called b2_Body ?! Changing const names in stable libs is usually a baaad idea)


muddy_shoes(Posted 2013) [#43]
That's most likely due to an error in the conversion script stripping out the AS3 dynamic keyword. As it doesn't cause an error I didn't catch it. As you point out, if I "fix" it now, it will potentially break code. It's ugly enough for me to maybe do it anyway at some point.

You'll see similar conversion side effects in the documentation, for instance this line:

This method is locked during callbacks

becomes

This locked(Method) during callbacks

I've just never gotten around to working out how to replace all the comments again without spending days on it.


Nobuyuki(Posted 2013) [#44]
oh crap, I think I may have found a memory leak.......

I spawn a body which contains 24 oriented box fixtures to make an enclosed pen for 30 b2CircleShapes, which are all spawned the "correct" way using b2World.CreateBody(). Half of the oriented boxes have a restitution over 1 to provide "bumper" capabilities, while the other half have decimal restitution, to keep the reaction stable.

It seems that the longer the simulation runs, the more the device gets taxed by increasing allocations (and more frequent GCs), and I don't know why. According to the DDMS profiler, a lot of this is occurring in b2ContactManager of the solver, due to these b2Contacts, which keep increasing their allocation size:



5 minutes later, these allocations have more than doubled. I get the feeling that this might not be an error on my part, although any common reasons this would occur and advice to fix would be appreciated.

Is there anything I can do? If it is in fact a leak from the as3 port of box2d, will you try to send it upstream, muddy? This thing is a total blocker for something I need to see released very soon :\

I can describe what I do in more details on request.


muddy_shoes(Posted 2013) [#45]
If you can provide some code that recreates the problem I can take a look at it. My own use has been for fairly short life levels so it's possible I've just never ran a world long enough to see the problem.


Nobuyuki(Posted 2013) [#46]
This code should reproduce the problem when compiled to android using v66b.

[monkeycode]
#ANDROID_APP_LABEL="box2d test"
#ANDROID_APP_PACKAGE="com.test.box2d"
#ANDROID_SCREEN_ORIENTATION="landscape"
#ANDROID_NATIVE_GL_ENABLED="true"
#OPENGL_GLES20_ENABLED="false"
#OPENGL_DEPTH_BUFFER_ENABLED="false"

#GLFW_WINDOW_TITLE="box2d test"
#GLFW_WINDOW_WIDTH=1024
#GLFW_WINDOW_HEIGHT=768


Import box2d.flash.flashtypes
Import box2d.demo.maindemo
Import box2d.dynamics
Import box2d.collision
Import box2d.collision.shapes
Import box2d.dynamics.joints
Import box2d.dynamics.contacts
Import box2d.common
Import box2d.common.math
Import box2d.demo.maindemo

Import mojo
Import monkey

Global World:b2World

Function Main:Int()
New TestApp
Return 0
End Function

Class TestApp Extends App
Const VelocityIterations:Int = 8 'Recommended value is 8
Const PositionIterations:Int = 3 'Recommended value is 3
Field TimeStep:Float = 1.0/60.0

Field Capsules:b2Body[30]

'Testing things
Field machinePanel:b2Body
Field machinePanelPoly:Float[48]
Field m_sprite:FlashSprite

Method OnCreate()
Local Gravity:b2Vec2 = New b2Vec2(0.0, 9.8)
World = New b2World(Gravity, True) 'Objects go to sleep eventually

'Do Debug draw
Local dbgDraw:b2DebugDraw = New b2DebugDraw()
dbgDraw.SetSprite(m_sprite)
dbgDraw.SetDrawScale(32.0)
dbgDraw.SetFillAlpha(0.3)
dbgDraw.SetLineThickness(1.0)
dbgDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit)'| b2DebugDraw.e_pairBit)
World.SetDebugDraw(dbgDraw)

'Make sides of n-gon inscribed within circle.
Local r:Float = 6.5 'Size of the circle radius
Local nSides:Int = 24 'Number of sides of the polygon making up the circle
Local sweepAngle:Float = 360 / nSides

Local sideLen:Float = 1*r * Sin(sweepAngle/2)
Local bDef:b2BodyDef = New b2BodyDef()
bDef.type = b2Body.b2_staticBody
bDef.position = New b2Vec2(15.25, 9.25)
machinePanel = World.CreateBody(bDef)

For Local i:Int = 0 until nSides
Local angle:Float = i * sweepAngle 'Adds up to 360 with 24 segments
Local position:b2Vec2 = New b2Vec2(r * Cos(angle) , r * Sin(angle) )
machinePanelPoly[i*2] = (15.25 + position.x) * 32
machinePanelPoly[i*2 + 1] = (9.25+position.y) *32

Local shape:=New b2PolygonShape
'SetAsOrientedBox takes an angle in radians, so give it that
shape.SetAsOrientedBox(0.25, sideLen, position, DegToRad(angle))

Local fixture:=New b2FixtureDef
fixture.shape = shape
If i > nSides / 2 Then fixture.restitution = 0 Else fixture.restitution = 1.5
if i = Floor(nSides / 4) Then fixture.restitution = 3
fixture.friction = 0.1
fixture.density = 1

machinePanel.CreateFixture(fixture)
Next

'Capsules need body and fixture information to define its shape, density, etc. Do it.
bDef = New b2BodyDef()
bDef.type = b2Body.b2_Body 'wtf. In the box2d specification, this should be b2_dynamicBody.
bDef.linearDamping = 0.01
Local dynamicBox:= New b2CircleShape
dynamicBox.SetRadius(0.5)
Local fixtureDef:= New b2FixtureDef
fixtureDef.shape = dynamicBox
fixtureDef.density = 1
fixtureDef.friction = 0.1
fixtureDef.restitution = 0.9

'--------------------------------------'
Seed = Millisecs() 'DEBUG: REMOVE ME
'--------------------------------------'

For Local i:Int = 0 Until Capsules.Length
bDef.position.Set(14 + Rnd(-1, 2), 9.25 + Rnd(-1, 2))

Capsules[i] = World.CreateBody(bDef)
'Capsules[i].body.SetAngle(PI / 3)
Capsules[i].CreateFixture(fixtureDef)
Next


SetUpdateRate 60
End

Method OnUpdate()
World.TimeStep(TimeStep, VelocityIterations, PositionIterations)
If KeyHit(KEY_ESCAPE) Then Error("")


Local v:b2Vec2
For Local i:Int = 0 Until Capsules.Length
'Do speed dampening
If Capsules[i].IsAwake = False Then Continue
v = Capsules[i].GetLinearVelocity.Copy

If v.Length() > 16
Local percentage:Float = 16 / v.Length()
v.x *= percentage
v.y *= percentage
Capsules[i].SetLinearVelocity(v)
End If

Next
End Method

Method OnRender()

World.ClearForces()
Cls

World.DrawDebugData()
SetColor(255,255,255)

SetColor(0, 0, 255); SetAlpha(0.1)
DrawPoly(machinePanelPoly)
SetColor(255, 255, 255); SetAlpha(1)

End Method
End Class

'Summary: Returns a value in radians when given a value in degrees.
Function DegToRad:Float(theta:Float)
Return (theta * PI) / 180
End Function
[/monkeycode]

Note that I do cull velocity in OnUpdate by checking some b2vec2s and instancing a few of them. This could cause some hiccups in the gc from object instancing but is not the source of the problem according to the profiler. You can remove the code and the size of the b2Contact allocations will still continue to increase over time.


muddy_shoes(Posted 2013) [#47]
Okay, great. I'll see what I can find over the weekend.


Nobuyuki(Posted 2013) [#48]
thanks for the response. I'll be waiting anxiously for what you may find :)

In the meantime, I can give a bit more information about the system I'm running, in case initial checks on my code don't point to an easy workaround. I'm running a Nexus S 4g with Android 4.1.1 running stock/OEM. This device should have 512mb of RAM and a default heapsize of 32mb. There should be a profile for it with the default android emulator if you need to test on a facsimile of this device for comparison purposes, although I'm assuming it won't be necessary.


muddy_shoes(Posted 2013) [#49]
The problem seems to be from some half done refactoring in the flash port (or possibly the problem existed in the original at that time). It's incrementing the world contact count but never decrementing it.

I'll do a new module version later but if you want to try the fix just find the Destroy method in box2d/dynamics/b2contactmanager.monkey and change the last line from

format_code('m_contactCount -= 1')

to

format_code('m_world.m_contactCount -= 1')


muddy_shoes(Posted 2013) [#50]
Okay, 1.0.12 version uploaded with the fix. Let me know if it does or doesn't solve your problem.


Nobuyuki(Posted 2013) [#51]
it works!!!! Thanks so much, the project went from a crawl to a full 60fps. I -really- appreciate the fast turnaround on this one; couldn't have done it without ya :D

By the way, in case this isn't going upstream and you plan on diverging the codebase a tiny bit, maybe consider this tiny change -- On line 1532 of b2world.monkey, I commented out that entire line to perform testing with my graphics in the background so I could see how they "matched up". I don't know if there's a specific reason for m_debugDraw.Clear() to be in there, but by commenting it out, it gives a lot more flexibility for where the debugDraw can be in the render order. You could always change the method signature to add an optional argument to disable to default behavior, but seeing as how most people call Cls in OnRender anyway before calling b2World.DrawDebugData, maybe this little change won't break anything......

Thanks again for the help :)


Nobuyuki(Posted 2013) [#52]
Hello,

This may or may not be a bug, depending on how the as3 port originally handled it, but.... when setting a b2PolygonShape using SetAsOrientedBox, the arguments it takes are like this:

hx:Float, hy:Float, center:b2Vec2 = Null, angle:Float = 0.0

If initialized with no optional arguments, the simulation will crash at runtime, because b2Vec2 is uninitialized and Null. I'm not sure if that's what's intended (I'm guessing not, because it's a port!), but there are two ways to fix this. One fix is to replace SetAsOrientedBox with a couple overloads that change the method signature for optional arguments, but maintain compatibility with existing code. The other way is to check for Null before doing anything else in the method, like this:

format_code('
Method SetAsOrientedBox : void (hx:Float, hy:Float, center:b2Vec2 = null, angle:Float = 0.0)

If center = Null then center = New b2Vec2()
'Rest of the method body here
End
')


Uncle(Posted 2013) [#53]
Hello,

I've been toying with the idea of using Box2d just for collision detection and Ive been looking at the raycast function to find the nearest object. So far Im able to detect some objects, but I understand that the nearest isn't always returned first. I read that to get this you have to return the fraction back in the callback to get the closest object, but for the life of me I can't get my head around how its supposed to work. Can someone shed some light on the subject or even better provide a working example :)

format_codebox('
Import box2d.flash.flashtypes
Import box2d.demo.tests.test
Import box2d.dynamics
Import box2d.collision
Import box2d.collision.shapes
Import box2d.dynamics.joints
Import box2d.dynamics.contacts
Import box2d.common
Import box2d.common.math

Import mojo

Class InnerRayCastToPointCallback Extends InnerRayCastCallback
Field result:b2Vec2
Field fraction:float
Field fixture:b2Fixture
Field normal:b2Vec2

Method Callback:Float(outFixture:b2Fixture, point:b2Vec2, outNormal:b2Vec2, outFraction:Float)
result = point
fraction = outFraction
fixture = outFixture
normal = outNormal
Return fraction
End
End

Class Game Extends App
Global m_world:b2World
Global m_physScale:Float = 30
Global box:b2PolygonShape
Global fd:b2FixtureDef
Global bd:b2BodyDef

Global player:oGameObj
Global ray:oRay
Global rayAngle:Float = 0

Global test:String

Const frameRate:Int = 30
Const physicsRate:Int = 60
Const physicsFrameMS:Float = 1000.0/physicsRate
Const physicsFramesPerRender = Float(physicsRate) / frameRate

Global collisionPoint:b2Vec2 = New b2Vec2
Field m_sprite:FlashSprite

Global collisionResults:FlashArray<b2Fixture>

Method OnRender()
Cls()
m_world.DrawDebugData()
oGameObj.drawAll()
ray.draw()
processCollisions()
If collisionPoint <> Null Then DrawCircle(fromWorld(collisionPoint.x), fromWorld(collisionPoint.y), 2)
DrawText test, 10, 10
End

Method OnCreate()
'SET UP PHYSICS
Local worldAABB :b2AABB = New b2AABB()
worldAABB.lowerBound.Set(-1000.0, -1000.0)
worldAABB.upperBound.Set(1000.0, 1000.0)
'// Define the gravity vector
Local gravity:b2Vec2 = New b2Vec2(0.0, 0.0)
'// Allow bodies to sleep
Local doSleep :Bool = True
'// Construct a world object
m_world = New b2World(gravity, doSleep)
m_world.SetWarmStarting(True)

'// set debug draw
Local dbgDraw :b2DebugDraw = New b2DebugDraw()
dbgDraw.SetSprite(m_sprite)
dbgDraw.SetDrawScale(30.0)
dbgDraw.SetFillAlpha(1)
dbgDraw.SetLineThickness(0.5)
dbgDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit)'| b2DebugDraw.e_pairBit)
m_world.SetDebugDraw(dbgDraw)

'//create physic template for the box collision.
box = New b2PolygonShape()
fd = New b2FixtureDef()
fd.shape = box
fd.density = 1
fd.friction = 0
fd.restitution = 0
bd = New b2BodyDef()
bd.type = b2Body.b2_Body

'// Set up some game obj
For Local x:Int = 0 To 100
oGameObj.create(Rnd(0, 640), Rnd(0, 480), Rnd(10, 40), Rnd(10, 40))
Next

player = oGameObj.create(100, 100, 30, 30)
player.setColor(0, 255, 255)
ray = oRay.create(100, 100, 0, 600)
SetUpdateRate(frameRate)
End


Method OnUpdate()
' Quit App
If KeyHit(KEY_ESCAPE) or KeyHit(KEY_BACK) or KeyHit(KEY_CLOSE)
Error ""
End

If KeyDown(KEY_LEFT) Then player.x = player.x - 2
If KeyDown(KEY_RIGHT) Then player.x = player.x + 2
If KeyDown(KEY_UP) Then player.y = player.y - 2
If KeyDown(KEY_DOWN) Then player.y = player.y + 2
if KeyDown(KEY_X) Then rayAngle=rayAngle+1
If KeyDown(KEY_Z) Then rayAngle = rayAngle - 1

ray.x1 = player.x + (player.w / 2)
ray.y1 = player.y + (player.h / 2)

If rayAngle > 360 Then rayAngle = 0
If rayAngle < 0 Then rayAngle = 360
ray.oSetAngle(rayAngle)

oGameObj.updateAll()
' collisionResults = m_world.RayCastAll(ray.p1, ray.p2)
Local hitFixture:b2Fixture
Local hitPoint:b2Vec2
Local hitNormal:b2Vec2
Local hitFraction:float
Local callback:InnerRayCastToPointCallback = New InnerRayCastToPointCallback()
m_world.RayCast(callback, ray.p1, ray.p2)
If callback.result <> Null Then
collisionPoint = callback.result

While test > 0
callback.Callback(callback.fixture, callback.result, callback.normal, callback.fraction)
test = callback.fraction
Wend
EndIf


m_world.ClearForces()
End


Function processCollisions()

If collisionResults <> Null Then
For Local x:b2Fixture = EachIn collisionResults
Local b:b2AABB = x.GetAABB()
SetColor 255, 0, 0
If b <> Null Then
DrawRect fromWorld(b.upperBound.x), fromWorld(b.upperBound.y), fromWorld(b.lowerBound.x - b.upperBound.x), fromWorld(b.lowerBound.y - b.upperBound.y)
EndIf
SetColor 255, 255, 255
Next
EndIf
End

End

Function toWorld:float(inFloat:float)
Return inFloat / Game.m_physScale
End Function

Function fromWorld:Float(inFloat:Float)
Return inFloat * Game.m_physScale
End

Class oVector
Field x:float
Field y:float

Function create:oVector(inX:float, inY:float)
Local temp:oVector = New oVector
temp.x = inX
temp.y = inY
Return temp
End

Method setPosition(inX:float, inY:float)
Self.x = inX
Self.y = inY
End
End

Class oRay
Field x1:Float
Field y1:Float
Field x2:Float
Field y2:float
Field angle:Float
Field length:float
Field p1:b2Vec2
Field p2:b2Vec2

Function create:oRay(inX1:Float, inY1:Float, inAngle:float, inLength:float)
Local temp:oRay = New oRay
temp.x1 = inX1
temp.y1 = inY1
temp.angle = inAngle
temp.length = inLength
temp.p1 = New b2Vec2()
temp.p2 = New b2Vec2()
temp.oSetAngle(inAngle)
Return temp
End

Method oSetAngle(inAngle:Float)
Self.angle = inAngle
Self.x2 = Self.x1 + (Sin(Self.angle) * Self.length)
Self.y2 = Self.y1 + (Cos(Self.angle) * Self.length)
Self.p1.x = toWorld(x1)
Self.p1.y = toWorld(y1)
Self.p2.x = toWorld(x2)
Self.p2.y = toWorld(y2)
End Method

Method draw()
SetColor(0, 255, 0)
SetAlpha 0.3
DrawLine(Self.x1, Self.y1, Self.x2, Self.y2)
SetAlpha 1
SetColor(255, 255, 255)
End
End

Class oGameObj
Field x:float
Field y:float
Field w:Int
Field h:Int
Field r:Int = 0
Field g:Int = 255
Field b:Int = 0
Field mVelocity:oVector
Field tVector:oVector
Field speed:Float = 0
Field collisionObj:b2Body

Global objList:List<oGameObj> = New List<oGameObj>

Function create:oGameObj(inX:Int, inY:Int, inW:Int, inH:Int)
Local temp:oGameObj = New oGameObj
temp.x = inX
temp.y = inY
temp.w = inW
temp.h = inH
temp.mVelocity = oVector.create(0, 0)
temp.createCollisionObj()
temp.tVector = oVector.create(Rnd(0, 640), Rnd(0, 480))
temp.setmVelocity(angleBetween(temp.x, temp.y, temp.tVector.x, temp.tVector.y))
objList.AddLast(temp)
Return temp
End
Method setColor(inR:Int, inG:Int, inB:Int)
Self.r = inR
Self.g = inG
Self.b = inB
End
Method setmVelocity(inAngle:Float)
inAngle = inAngle
Self.mVelocity.x = Self.speed * Cos(inAngle)
Self.mVelocity.y = Self.speed * Sin(inAngle)
End

Method update()
'update position
Self.x = Self.x + Self.mVelocity.x
Self.y = Self.y + Self.mVelocity.y
'check if we are near the way point and create a new one if we are near
If distance(Self.x, Self.y, Self.tVector.x, Self.tVector.y) < 3 Then
Self.tVector = oVector.create(Rnd(0, 640), Rnd(0, 480))
Self.setmVelocity(angleBetween(Self.x, Self.y, Self.tVector.x, Self.tVector.y))
EndIf
'update the collision box position
Self.setCollisionBoxXY()
End

Method createCollisionObj()
Game.box.SetAsBox(toWorld(Self.w / 2), toWorld(Self.h / 2))
Game.bd.position.Set(toWorld(Self.x + (Self.w / 2)), toWorld(Self.y + (Self.h / 2)))
Self.collisionObj = Game.m_world.CreateBody(Game.bd)
Self.collisionObj.CreateFixture(Game.fd)
End

Method setCollisionBoxXY()
Local x:Float = toWorld(Self.x + (Self.w / 2))
Local y:float = toWorld(Self.y + (Self.h / 2))
Self.collisionObj.SetPosition(New b2Vec2(x, y))
Self.collisionObj.SetAngle(0)
End

Method draw()
SetColor Self.r, Self.g, Self.b
SetAlpha 0.5
DrawRect(Self.x, Self.y, Self.w, Self.h)
SetColor 255, 255, 255
SetAlpha 1
End

Function drawAll()
For Local temp:oGameObj = EachIn objList
temp.draw()
Next
End

Function updateAll()
For Local temp:oGameObj = EachIn objList
temp.update()
Next
End
End

Function angleBetween:float(x1:Float, y1:Float, x2:Float, y2:Float)
Local dy:Float = y2 - y1
Local dx:Float = x2 - x1
Return ATan2(dy, dx)
End

Function distance:float(x1:Float, y1:Float, x2:Float, y2:Float)
Local xd:float = x2 - x1
Local yd:Float = y2 - y1
Return Sqrt(xd * xd + yd * yd)
End

Function Main()
New Game
End Function

')

In the code above the cursor move the cyan box around and Z and X rotate the ray. The collision point is highlighted with a little white ball.

Thanks,


Andrew


muddy_shoes(Posted 2013) [#54]
Looks like you've got a couple of issues

1. You've been tripped up by nicking a vector reference. In your callback you simply assign the point and normal references you're given to your internal values. Unfortunately, those references continue to be used in the underlying raycast code and so the values get changed under your nose. It's a bit of a booby-trap, but the vector re-use is needed to reduce GC issues on Android.

2. Although I've seen docs that suggest if you keep returning the fraction you'll end up with the nearest point that doesn't actually work. Your callback gets wrapped in another one that can reset the raycast maxfraction. The reset may well be some oddity of the way the collision tree gets traversed versus the C++ version but it does happen. To avoid the problem you should track the nearest fraction yourself.

Applying those:

format_code('
Class InnerRayCastToPointCallback Extends InnerRayCastCallback
Field result:b2Vec2 = New b2Vec2()
Field fraction:float = 1.0
Field fixture:b2Fixture
Field normal:b2Vec2 = New b2Vec2()

Method Callback:Float(outFixture:b2Fixture, point:b2Vec2, outNormal:b2Vec2, outFraction:Float)
If outFraction <= fraction
result.SetV(point)
fraction = outFraction
fixture = outFixture
normal.SetV(outNormal)
End
Return fraction
End
End
')

I'll have a think about reworking the callbacks to avoid the stolen references problem but hopefully that should fix your issues for now.


Uncle(Posted 2013) [#55]
Thanks for the explanation and code Muddy. This really helped a lot. I've was scratching my head on this for quite a few hours on this one as I'm new to box2D.


muddy_shoes(Posted 2013) [#56]
It took me an hour or so to figure out where the problems were too. The reference issue is a nasty gotcha introduced by changes I made fairly recently. The changes aren't in the latest release, just in the repo, so I assume you pulled directly or used ModMan to get Box2D.

Fixing the problem is likely to mean breaking changes to the way callbacks work. Callbacks will probably have to provide vector fields to be set rather than having values passed to them. Inconvenient, but the GC impact of potentially creating thousands of vector objects is even more inconvenient.