Monkey Box2D Port (part 2)
Monkey Forums/User Modules/Monkey Box2D Port (part 2)
| ||
Port of 2D physics engine Box2D by muddy_shoes... Continued from here. |
| ||
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: ' 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 |
| ||
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 |
| ||
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. Local player:b2Body = world.CreateBody(bd) player.SetUserData(New StringObject("player")) Print StringObject(player.GetUserData()) |
| ||
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: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 :( |
| ||
Ok, got it to work with a bunch of experimentation... The key for me was: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 :) |
| ||
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. |
| ||
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. ' 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.) |
| ||
You have the order of vertices in the wrong order, try amending the line to this:Local verts:b2Vec2[]=[vert4,vert3,vert2,vert1] |
| ||
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! |
| ||
Yea that confused me as well! |
| ||
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. |
| ||
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. |
| ||
Thanks. |
| ||
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)? |
| ||
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 |
| ||
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. |
| ||
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 |
| ||
Updating to newest version solved it. Thanks for testing. |
| ||
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 |
| ||
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. |
| ||
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? |
| ||
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. |
| ||
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 |
| ||
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. |
| ||
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 |
| ||
Wow nice update Skn3, would be nice to get the verlet code to add to my Box2D framework along with the new joint type. |
| ||
here you go. (sorry no example code) http://www.monkeycoder.co.nz/Community/posts.php?topic=4025 |
| ||
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! |
| ||
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. |
| ||
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. |
| ||
Good news, Im using it in two projects at the mo and any extra speed would be great... |
| ||
Yes, indeed good news. |
| ||
*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 |
| ||
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. |
| ||
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. |
| ||
Thank you, much appreciated. |
| ||
Great speed improvement. I can pick up an older game which was deferred and finish it. Thanks. |
| ||
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. |
| ||
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? |
| ||
Because that was what they were called in the Flash version: http://www.box2dflash.org/docs/2.1a/reference/Box2D/Dynamics/b2Body.html |
| ||
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) |
| ||
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. |
| ||
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. |
| ||
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. |
| ||
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. |
| ||
Okay, great. I'll see what I can find over the weekend. |
| ||
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. |
| ||
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 m_contactCount -= 1 to m_world.m_contactCount -= 1 |
| ||
Okay, 1.0.12 version uploaded with the fix. Let me know if it does or doesn't solve your problem. |
| ||
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 :) |
| ||
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: 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 |
| ||
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 :) 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 |
| ||
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: 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. |
| ||
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. |
| ||
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. |