diff --git a/examples/game/test_physics.dart b/examples/game/test_physics.dart index aabb773bd2a..137a3257cea 100644 --- a/examples/game/test_physics.dart +++ b/examples/game/test_physics.dart @@ -45,24 +45,11 @@ main() async { } class TestBed extends NodeWithSize { - Sprite _ship; Sprite _obstacle; + PhysicsNode _physicsNode; TestBed() : super(new Size(1024.0, 1024.0)) { - PhysicsNode physicsNode = new PhysicsNode(new Offset(0.0, 100.0)); - - _ship = new Sprite(_spriteSheet["ship.png"]); - _ship.position = new Point(512.0, 512.0); - _ship.size = new Size(64.0, 64.0); - _ship.physicsBody = new PhysicsBody( - new PhysicsShapeGroup([ - new PhysicsShapeCircle(Point.origin, 32.0), - new PhysicsShapePolygon([new Point(0.0, 0.0), new Point(50.0, 0.0), new Point(50.0, 50.0), new Point(0.0, 50.0)]) - ]), - friction: 0.5, - tag: "ship" - ); - physicsNode.addChild(_ship); + _physicsNode = new PhysicsNode(new Offset(0.0, 100.0)); _obstacle = new Sprite(_spriteSheet["ship.png"]); _obstacle.position = new Point(532.0, 800.0); @@ -73,23 +60,47 @@ class TestBed extends NodeWithSize { friction: 0.5, tag: "obstacle" ); - physicsNode.addChild(_obstacle); - physicsNode.addContactCallback(myCallback, "obstacle", "ship", PhysicsContactType.begin); + _physicsNode.addChild(_obstacle); + _physicsNode.addContactCallback(myCallback, "obstacle", "ship", PhysicsContactType.begin); - addChild(physicsNode); + addChild(_physicsNode); userInteractionEnabled = true; } void myCallback(PhysicsContactType type, PhysicsContact contact) { print("CONTACT type: $type"); - contact.nodeB.removeFromParent(); } bool handleEvent(SpriteBoxEvent event) { if (event.type == "pointerdown") { Point pos = convertPointToNodeSpace(event.boxPosition); - _ship.position = pos; + + Sprite shipA; + shipA = new Sprite(_spriteSheet["ship.png"]); + shipA.position = new Point(pos.x - 40.0, pos.y); + shipA.size = new Size(64.0, 64.0); + shipA.physicsBody = new PhysicsBody(new PhysicsShapeCircle(Point.origin, 32.0), + friction: 0.5, + tag: "ship" + ); + _physicsNode.addChild(shipA); + shipA.physicsBody.applyLinearImpulse( + new Offset(randomSignedDouble() * 5.0, randomSignedDouble() * 5.0), + shipA.position + ); + + Sprite shipB; + shipB = new Sprite(_spriteSheet["ship.png"]); + shipB.position = new Point(pos.x + 40.0, pos.y); + shipB.size = new Size(64.0, 64.0); + shipB.physicsBody = new PhysicsBody(new PhysicsShapePolygon([new Point(-25.0, -25.0), new Point(25.0, -25.0), new Point(25.0, 25.0), new Point(-25.0, 25.0)]), + friction: 0.5, + tag: "ship" + ); + _physicsNode.addChild(shipB); + + new PhysicsJointWeld(shipA.physicsBody, shipB.physicsBody); } return true; } diff --git a/skysprites/lib/physics_body.dart b/skysprites/lib/physics_body.dart index 178b1ac48c0..40ee242c0fc 100644 --- a/skysprites/lib/physics_body.dart +++ b/skysprites/lib/physics_body.dart @@ -230,6 +230,8 @@ class PhysicsBody { box2d.Body _body; + List _joints = []; + bool _attached = false; void applyForce(Offset force, Point worldPoint) { @@ -338,6 +340,13 @@ class PhysicsBody { _node = node; _attached = true; + + // Attach any joints + for (PhysicsJoint joint in _joints) { + if (joint.bodyA._attached && joint.bodyB._attached) { + joint._attach(physicsNode); + } + } } void _detach() { diff --git a/skysprites/lib/physics_joint.dart b/skysprites/lib/physics_joint.dart new file mode 100644 index 00000000000..cecdd5c581c --- /dev/null +++ b/skysprites/lib/physics_joint.dart @@ -0,0 +1,90 @@ +part of skysprites; + +abstract class PhysicsJoint { + PhysicsJoint(this.bodyA, this.bodyB) { + bodyA._joints.add(this); + bodyB._joints.add(this); + + if (bodyA._attached && bodyB._attached) { + _attach(bodyA._physicsNode); + } + } + + PhysicsBody bodyA; + PhysicsBody bodyB; + + bool _active = true; + box2d.Joint _joint; + + PhysicsNode _physicsNode; + + void _attach(PhysicsNode physicsNode) { + if (_joint == null) { + _physicsNode = physicsNode; + _joint = _createB2Joint(physicsNode); + } + } + + void _detach() { + if (_joint != null && _active) { + _physicsNode.b2World.destroyJoint(_joint); + _joint = null; + } + _active = false; + } + + box2d.Joint _createB2Joint(PhysicsNode physicsNode); +} + +class PhysicsJointRevolute extends PhysicsJoint { + PhysicsJointRevolute( + PhysicsBody bodyA, + PhysicsBody bodyB, + this.anchorWorld, { + double lowerAngle: 0.0, + double upperAngle: 0.0, + bool enableLimit: false + }) : super(bodyA, bodyB) { + this.lowerAngle = lowerAngle; + this.upperAngle = upperAngle; + this.enableLimit = enableLimit; + } + + Point anchorWorld; + double lowerAngle; + double upperAngle; + bool enableLimit; + + box2d.Joint _createB2Joint(PhysicsNode physicsNode) { + // Create Joint Definition + Vector2 vecAnchor = new Vector2( + anchorWorld.x / physicsNode.b2WorldToNodeConversionFactor, + anchorWorld.y / physicsNode.b2WorldToNodeConversionFactor + ); + + box2d.RevoluteJointDef b2Def = new box2d.RevoluteJointDef(); + b2Def.initialize(bodyA._body, bodyB._body, vecAnchor); + b2Def.enableLimit = enableLimit; + b2Def.lowerAngle = lowerAngle; + b2Def.upperAngle = upperAngle; + + // Create joint + return physicsNode.b2World.createJoint(b2Def); + } +} + +class PhysicsJointWeld extends PhysicsJoint { + PhysicsJointWeld( + PhysicsBody bodyA, + PhysicsBody bodyB) : super(bodyA, bodyB); + + box2d.Joint _createB2Joint(PhysicsNode physicsNode) { + box2d.WeldJointDef b2Def = new box2d.WeldJointDef(); + Vector2 middle = new Vector2( + (bodyA._body.position.x + bodyB._body.position.x) / 2.0, + (bodyA._body.position.y + bodyB._body.position.y) / 2.0 + ); + b2Def.initialize(bodyA._body, bodyB._body, middle); + return physicsNode.b2World.createJoint(b2Def); + } +} diff --git a/skysprites/lib/physics_node.dart b/skysprites/lib/physics_node.dart index 12ea4f8531b..69d74829f0d 100644 --- a/skysprites/lib/physics_node.dart +++ b/skysprites/lib/physics_node.dart @@ -83,6 +83,13 @@ class PhysicsNode extends Node { void _removeBodiesScheduledForDestruction() { for (box2d.Body b2Body in _bodiesScheduledForDestruction) { + // Destroy any joints before destroying the body + PhysicsBody body = b2Body.userData; + for (PhysicsJoint joint in body._joints) { + joint._detach(); + } + + // Destroy the body b2World.destroyBody(b2Body); } _bodiesScheduledForDestruction.clear(); @@ -183,7 +190,57 @@ class PhysicsNode extends Node { } } } + canvas.restore(); + + // Draw contacts + for (box2d.ContactEdge edge = body.getContactList(); edge != null; edge = edge.next) { + box2d.Contact contact = edge.contact; + Vector2 cA = new Vector2.zero(); + Vector2 cB = new Vector2.zero(); + + box2d.Fixture fixtureA = contact.fixtureA; + box2d.Fixture fixtureB = contact.fixtureB; + + fixtureA.getAABB(contact.getChildIndexA()).getCenterToOut(cA); + fixtureB.getAABB(contact.getChildIndexB()).getCenterToOut(cB); + + Point p1 = new Point( + cA.x * b2WorldToNodeConversionFactor, + cA.y * b2WorldToNodeConversionFactor + ); + + Point p2 = new Point( + cB.x * b2WorldToNodeConversionFactor, + cB.y * b2WorldToNodeConversionFactor + ); + + shapePaint.color = new Color(0x33ffffff); + canvas.drawLine(p1, p2, shapePaint); + + box2d.WorldManifold worldManifold = new box2d.WorldManifold(); + contact.getWorldManifold(worldManifold); + + shapePaint.color = new Color(0xffffffff); + + for (Vector2 pt in worldManifold.points) { + Point pCenter = new Point( + pt.x * b2WorldToNodeConversionFactor, + pt.y * b2WorldToNodeConversionFactor + ); + Offset offset = new Offset( + worldManifold.normal.x * 5.0, + worldManifold.normal.y * 5.0 + ); + + Point p2 = pCenter + offset; + Point p1 = new Point(pCenter.x - offset.dx, pCenter.y - offset.dy); + canvas.drawLine(p1, p2, shapePaint); + canvas.drawCircle(pCenter, 5.0, shapePaint); + } + } + + } } } @@ -195,7 +252,9 @@ class PhysicsContact { this.shapeA, this.shapeB, this.isTouching, - this.isEnabled + this.isEnabled, + this.touchingPoints, + this.touchingNormal ); final Node nodeA; @@ -204,6 +263,8 @@ class PhysicsContact { final PhysicsShape shapeB; final isTouching; bool isEnabled; + final List touchingPoints; + final Offset touchingNormal; } class _ContactCallbackInfo { @@ -264,19 +325,35 @@ class _ContactHandler extends box2d.ContactListener { if (match) { // We have contact and a matched callback, setup contact info + List touchingPoints = null; + Offset touchingNormal = null; + + // Fetch touching points, if any + if (b2Contact.isTouching()) { + box2d.WorldManifold manifold = new box2d.WorldManifold(); + b2Contact.getWorldManifold(manifold); + touchingNormal = new Offset(manifold.normal.x, manifold.normal.y); + touchingPoints = []; + for (Vector2 vec in manifold.points) { + touchingPoints.add(new Point( + vec.x * physicsNode.b2WorldToNodeConversionFactor, + vec.y * physicsNode.b2WorldToNodeConversionFactor + )); + } + } + + // Create the contact PhysicsContact contact = new PhysicsContact( bodyA._node, bodyB._node, fixtureA.userData, fixtureB.userData, b2Contact.isTouching(), - b2Contact.isEnabled() + b2Contact.isEnabled(), + touchingPoints, + touchingNormal ); - if (type == PhysicsContactType.postSolve) { - - } - // Make callback info.callback(type, contact); diff --git a/skysprites/lib/skysprites.dart b/skysprites/lib/skysprites.dart index ad662c65d00..6ffd26de3db 100644 --- a/skysprites/lib/skysprites.dart +++ b/skysprites/lib/skysprites.dart @@ -33,6 +33,7 @@ part 'node3d.dart'; part 'node_with_size.dart'; part 'particle_system.dart'; part 'physics_body.dart'; +part 'physics_joint.dart'; part 'physics_node.dart'; part 'physics_shape.dart'; part 'sound.dart';