├── .gitignore ├── scalabox2d-testbed ├── src │ ├── main │ │ ├── resources │ │ │ └── themes │ │ │ │ └── QtCurve │ │ │ │ └── pixmaps.png │ │ └── scala │ │ │ └── org │ │ │ └── villane │ │ │ └── box2d │ │ │ └── testbed │ │ │ ├── TestbedMain.scala │ │ │ ├── ExampleContactPoint.scala │ │ │ ├── scenes │ │ │ ├── TestbedScene.scala │ │ │ ├── VaryingRestitution.scala │ │ │ ├── Pyramid.scala │ │ │ ├── Overhang.scala │ │ │ ├── Chain.scala │ │ │ ├── PlanetGravity.scala │ │ │ ├── Domino.scala │ │ │ ├── VaryingFriction.scala │ │ │ ├── Bridge.scala │ │ │ ├── VerticalStack.scala │ │ │ ├── CCDTest.scala │ │ │ ├── Gears.scala │ │ │ ├── Circles.scala │ │ │ ├── Pistons.scala │ │ │ └── DominoTower.scala │ │ │ ├── tests │ │ │ ├── VerticalStack.scala │ │ │ ├── SimpleTest.scala │ │ │ └── PlanetGravity.scala │ │ │ ├── TestSettings.scala │ │ │ └── slick │ │ │ ├── SlickDisplayWorld.scala │ │ │ └── SlickDebugDraw.scala │ └── test │ │ └── scala │ │ └── org │ │ └── villane │ │ └── box2d │ │ └── testbed │ │ ├── CirclesPerfTest.scala │ │ ├── PyramidPerfTest.scala │ │ ├── SVGApp.scala │ │ ├── Pyramid4PerfTest.scala │ │ └── HeadlessPerformanceTest.scala └── pom.xml ├── scalabox2d ├── src │ └── main │ │ └── scala │ │ └── org │ │ └── villane │ │ ├── box2d │ │ ├── dsl │ │ │ ├── SceneFactory.scala │ │ │ ├── JointBuilders.scala │ │ │ └── DSL.scala │ │ ├── dynamics │ │ │ ├── joints │ │ │ │ ├── JointEdge.scala │ │ │ │ ├── JointDef.scala │ │ │ │ ├── Jacobian.scala │ │ │ │ ├── PointingDeviceJointDef.scala │ │ │ │ ├── DistanceJointDef.scala │ │ │ │ ├── GearJointDef.scala │ │ │ │ ├── PrismaticJointDef.scala │ │ │ │ ├── Joint.scala │ │ │ │ ├── RevoluteJointDef.scala │ │ │ │ ├── PointingDeviceJoint.scala │ │ │ │ ├── DistanceJoint.scala │ │ │ │ └── GearJoint.scala │ │ │ ├── contacts │ │ │ │ ├── EventType.scala │ │ │ │ ├── NullContact.scala │ │ │ │ ├── package.html │ │ │ │ ├── ContactEdge.scala │ │ │ │ ├── ContactResult.scala │ │ │ │ ├── ContactConstraint.scala │ │ │ │ ├── ContactPoint.scala │ │ │ │ ├── ContactListener.scala │ │ │ │ ├── MultiPointSingleManifoldContact.scala │ │ │ │ ├── SingleManifoldContact.scala │ │ │ │ └── Contact.scala │ │ │ ├── BoundaryListener.scala │ │ │ ├── TimeStep.scala │ │ │ ├── controllers │ │ │ │ ├── ConstantForceController.scala │ │ │ │ ├── ConstantAccelController.scala │ │ │ │ ├── GravityController.scala │ │ │ │ ├── PlanetGravityController.scala │ │ │ │ ├── TensorDampingController.scala │ │ │ │ ├── Controller.scala │ │ │ │ └── BuoyancyController.scala │ │ │ ├── Steppable.scala │ │ │ ├── ContactFilter.scala │ │ │ ├── Material.scala │ │ │ ├── IslandSolverWorker.scala │ │ │ ├── FilterData.scala │ │ │ ├── DefaultContactFilter.scala │ │ │ ├── DestructionListener.scala │ │ │ ├── FixtureDef.scala │ │ │ ├── BodyDef.scala │ │ │ ├── Sweep.scala │ │ │ ├── Fixture.scala │ │ │ ├── ContactManager.scala │ │ │ └── Island.scala │ │ ├── shapes │ │ │ ├── OBB.scala │ │ │ ├── Mass.scala │ │ │ ├── SupportsGenericDistance.scala │ │ │ ├── Point.scala │ │ │ ├── AABB.scala │ │ │ ├── Segment.scala │ │ │ ├── Shape.scala │ │ │ ├── Edge.scala │ │ │ ├── Circle.scala │ │ │ └── ShapeDef.scala │ │ ├── collision │ │ │ ├── package.html │ │ │ ├── broadphase │ │ │ │ ├── Proxy.scala │ │ │ │ ├── PairListener.scala │ │ │ │ ├── Bound.scala │ │ │ │ └── Pair.scala │ │ │ ├── ContactID.scala │ │ │ ├── PointCircleCollider.scala │ │ │ ├── Manifold.scala │ │ │ ├── CircleCollider.scala │ │ │ ├── Collider.scala │ │ │ ├── EdgeCircleCollider.scala │ │ │ ├── PolygonCircleCollider.scala │ │ │ ├── TOI.scala │ │ │ └── PolygonEdgeCollider.scala │ │ ├── draw │ │ │ ├── Color3f.scala │ │ │ ├── DebugDrawHandler.scala │ │ │ └── DebugDraw.scala │ │ ├── util │ │ │ └── Timing.scala │ │ └── Settings.scala │ │ └── vecmath │ │ ├── CommonMath.scala │ │ ├── Vector2Times.scala │ │ ├── FloatExtensions.scala │ │ ├── Transform2.scala │ │ ├── Vector2Range.scala │ │ ├── DoubleMath.scala │ │ ├── Preamble.scala │ │ ├── FloatMath.scala │ │ ├── Matrix22.scala │ │ └── Vector2.scala └── pom.xml ├── Readme.txt ├── License.txt ├── scalabox2d-svg ├── pom.xml └── src │ └── main │ └── scala │ └── org │ └── villane │ └── box2d │ └── svg │ ├── ConvexHull.scala │ ├── PureScalaSVGParser.scala │ └── SlickSVGSceneLoader.scala └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse 2 | .settings 3 | .classpath 4 | .project 5 | 6 | # Scala IDE 7 | .manager 8 | 9 | # Maven 10 | target 11 | -------------------------------------------------------------------------------- /scalabox2d-testbed/src/main/resources/themes/QtCurve/pixmaps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Villane/scalabox2d/HEAD/scalabox2d-testbed/src/main/resources/themes/QtCurve/pixmaps.png -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dsl/SceneFactory.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dsl 2 | 3 | import dynamics.World 4 | 5 | trait SceneFactory { 6 | def create: World 7 | } 8 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/joints/JointEdge.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics.joints 2 | 3 | case class JointEdge( 4 | /** Provides quick access to the other body attached. */ 5 | var other: Body, 6 | /** The joint. */ 7 | var joint: Joint 8 | ) 9 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/joints/JointDef.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics.joints 2 | 3 | import dynamics.Body 4 | 5 | class JointDef { 6 | var body1: Body = null 7 | var body2: Body = null 8 | var collideConnected = false 9 | var userData: AnyRef = null 10 | } 11 | -------------------------------------------------------------------------------- /scalabox2d-testbed/src/test/scala/org/villane/box2d/testbed/CirclesPerfTest.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.testbed 2 | 3 | object CirclesPerfTest { 4 | 5 | def main(args: Array[String]) { 6 | val test = new HeadlessPerformanceTest(scenes.Circles) 7 | test.run(true, 300, 150, true) 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/shapes/OBB.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.shapes 2 | 3 | import vecmath._ 4 | 5 | /** An oriented bounding box. */ 6 | case class OBB( 7 | /** The rotation matrix. */ 8 | rot: Matrix22, 9 | /** The local centroid. */ 10 | center: Vector2, 11 | /** The half-widths. */ 12 | extents: Vector2 13 | ) -------------------------------------------------------------------------------- /scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/TestbedMain.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.testbed 2 | 3 | import draw._ 4 | 5 | trait TestbedMain { 6 | /** Drawing handler to use. */ 7 | def dd: DebugDraw 8 | def mouseX: Float 9 | def mouseY: Float 10 | def shiftKey: Boolean 11 | def height: Int 12 | def width: Int 13 | } 14 | -------------------------------------------------------------------------------- /scalabox2d-testbed/src/test/scala/org/villane/box2d/testbed/PyramidPerfTest.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.testbed 2 | 3 | object PyramidPerfTest { 4 | 5 | def main(args: Array[String]) { 6 | val test = new HeadlessPerformanceTest(scenes.Pyramid) 7 | test.run(true, 1000, 100, false) 8 | //test.run(true, 50, 10, true) 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/contacts/EventType.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics.contacts 2 | 3 | /** 4 | * Contact event type. 5 | * 6 | * TODO perhaps move this to a preamble? 7 | */ 8 | object EventType { 9 | object Add extends EventType 10 | object Persist extends EventType 11 | object Remove extends EventType 12 | } 13 | sealed trait EventType 14 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/vecmath/CommonMath.scala: -------------------------------------------------------------------------------- 1 | package org.villane.vecmath 2 | 3 | /** 4 | * Math functions not differing for different Floating Point types. 5 | */ 6 | trait CommonMath { 7 | 8 | final def distance(a: Vector2, b: Vector2) = (a - b).length 9 | 10 | final def distanceSquared(a: Vector2, b: Vector2) = { 11 | val d = a - b 12 | d dot d 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/BoundaryListener.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics 2 | 3 | /** This is called when a body's shape passes outside of the world boundary. */ 4 | trait BoundaryListener { 5 | /** 6 | * This is called for each body that leaves the world boundary. 7 | *

Warning: you can't modify the world inside this callback. 8 | */ 9 | def violation(body: Body) 10 | } 11 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/TimeStep.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics 2 | 3 | /** 4 | * A holder for time step information. 5 | */ 6 | class TimeStep( 7 | // time step 8 | var dt: Float 9 | ) { 10 | // inverse time step (0 if dt == 0). 11 | var invDt = if (dt > 0f) 1 / dt else 0f 12 | var dtRatio = 0f 13 | var warmStarting = false 14 | var positionCorrection = false 15 | var maxIterations = 0 16 | } 17 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/controllers/ConstantForceController.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics.controllers 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | 6 | /** Applies a force every frame */ 7 | abstract class ConstantForceController extends Controller { 8 | /* The force to apply */ 9 | var F = Vector2.Zero 10 | 11 | def step(step: TimeStep) = 12 | forAwakeBodies(b => b.applyForce(F, b.worldCenter)) 13 | 14 | } 15 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/contacts/NullContact.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics.contacts 2 | 3 | import collision.Manifold 4 | 5 | // This lets us provide broadphase proxy pair user data for 6 | // contacts that shouldn't exist. 7 | object NullContact extends Contact(null, null) { 8 | def evaluate(listener: ContactListener) {} 9 | val manifolds = Array[Manifold]() 10 | 11 | override def computeTOI(sweep1: Sweep, sweep2: Sweep) = 1f 12 | } 13 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/controllers/ConstantAccelController.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics.controllers 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | 6 | /** Applies acceleration every frame */ 7 | abstract class ConstantAccelController extends Controller { 8 | /* The acceleration to apply */ 9 | var A = Vector2.Zero 10 | 11 | def step(step: TimeStep) = 12 | forAwakeBodies(_.linearVelocity += step.dt * A) 13 | 14 | } 15 | -------------------------------------------------------------------------------- /Readme.txt: -------------------------------------------------------------------------------- 1 | ScalaBox2D version 2.0.1 2 | 3 | ScalaBox2D is a 2D physics engine for games. 4 | It is a Scala port of JBox2D, which is a Java port of Box2D. 5 | 6 | Copyright (c) 2009, Erkki Lindpere 7 | All rights reserved. 8 | 9 | This software is distributed under the zlib license. See the file License.txt for the full text. 10 | 11 | JBox2D is a port of Box2D 12 | http://www.jbox2d.org/ 13 | 14 | Box2D is a 2D physics engine for games created by Erin Catto 15 | http://www.box2d.org 16 | -------------------------------------------------------------------------------- /scalabox2d-testbed/src/test/scala/org/villane/box2d/testbed/SVGApp.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.testbed 2 | 3 | import svg._ 4 | import draw.DrawFlags._ 5 | 6 | object SVGApp { 7 | val scale = 15 8 | 9 | def main(args: Array[String]) { 10 | val loader = new SlickSVGSceneLoader("C:/drawing-1.svg", scale) 11 | val world = loader.create 12 | println("Done!") 13 | val flags = Shapes 14 | slick.SlickDisplayWorld.runWithSimulation(world, false, flags) 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/collision/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Insert title here 6 | 7 | 8 | This package contains broad and narrow phase collision detection. 9 | 10 | In the Java version, this also contained shapes, which are now in a separate package. 11 | 12 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/Steppable.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics 2 | 3 | /** 4 | * This interface allows registration within a JBox2d World 5 | * to be run immediately after the physics step. This is 6 | * useful if you need to do something every step, but would 7 | * prefer not to have to manually code your step routine 8 | * differently, instead letting the engine handle the calling. 9 | */ 10 | trait Steppable { 11 | def step(dt: Float, iterations: Int) 12 | } -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/joints/Jacobian.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics.joints 2 | 3 | import vecmath.Vector2 4 | 5 | object Jacobian { 6 | val Zero = Jacobian(Vector2.Zero, 0, Vector2.Zero, 0) 7 | } 8 | 9 | case class Jacobian( 10 | linear1: Vector2, 11 | angular1: Float, 12 | linear2: Vector2, 13 | angular2: Float 14 | ) { 15 | def compute(x1: Vector2, a1: Float, x2: Vector2, a2: Float) = 16 | linear1 ∙ x1 + angular1 * a1 + linear2 ∙ x2 + angular2 * a2 17 | } 18 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/shapes/Mass.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.shapes 2 | 3 | import vecmath.Vector2 4 | 5 | object Mass { 6 | val Zero = Mass(0f, Vector2.Zero, 0f) 7 | } 8 | 9 | /** This holds the mass data computed for a shape. */ 10 | case class Mass( 11 | /** The mass of the shape, usually in kilograms. */ 12 | mass: Float, 13 | /** The position of the shape's centroid relative to the shape's origin. */ 14 | center: Vector2, 15 | /** The rotational inertia of the shape. */ 16 | I: Float 17 | ) 18 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/contacts/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Insert title here 6 | 7 | 8 | This package performs contact handling, and is essentially internal to JBox2d. 9 | 10 | The only reason you would ever need to dip into the package is to add additional shapes or object types to JBox2d. 11 | 12 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/collision/broadphase/Proxy.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.collision.broadphase 2 | 3 | class Proxy { 4 | val lowerBounds = Array(0,0) 5 | val upperBounds = Array(0,0) 6 | var overlapCount = BroadPhase.Invalid 7 | var timeStamp = 0 8 | var categoryBits = 0 9 | var maskBits = 0 10 | var groupIndex = 0 11 | var userData: AnyRef = null 12 | 13 | def next = lowerBounds(0) 14 | def next_=(next: Int) = lowerBounds(0) = next 15 | 16 | def isValid = overlapCount != BroadPhase.Invalid 17 | } 18 | -------------------------------------------------------------------------------- /scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/ExampleContactPoint.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.testbed 2 | 3 | import box2d.collision.ContactID; 4 | import box2d.shapes.Shape; 5 | import vecmath.Vector2; 6 | 7 | /** 8 | * Holder for storing contact information. 9 | */ 10 | class ExampleContactPoint { 11 | var shape1: Shape = null 12 | var shape2: Shape = null 13 | var normal = Vector2.Zero 14 | var position = Vector2.Zero 15 | var velocity = Vector2.Zero 16 | var id = ContactID.Zero 17 | var state = 0 // 0-add, 1-persist, 2-remove 18 | } -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/vecmath/Vector2Times.scala: -------------------------------------------------------------------------------- 1 | package org.villane.vecmath 2 | 3 | /** 4 | * TODO inclusive / exclusive 5 | */ 6 | class Vector2Times(start: Vector2, times: Int, step: Vector2) 7 | extends Iterable[Vector2] { 8 | 9 | def elements = new Iterator[Vector2] { 10 | var v = start 11 | var n = times 12 | def hasNext = n >= 0 13 | def next = { 14 | var ov = v 15 | v += step 16 | n -= 1 17 | ov 18 | } 19 | } 20 | 21 | def by(step: Vector2) = new Vector2Times(start, times, step) 22 | } 23 | -------------------------------------------------------------------------------- /scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/scenes/TestbedScene.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.testbed.scenes 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | import shapes._ 6 | import dynamics._ 7 | import dsl._ 8 | 9 | trait TestbedScene extends SceneFactory { 10 | final def create = { 11 | val world = createWorld 12 | createScene(world) 13 | world 14 | } 15 | 16 | def createScene(implicit world: World) 17 | 18 | protected def createWorld = new World( 19 | AABB((-200, -100), (200, 200)), 20 | (0, -10), 21 | true 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/collision/broadphase/PairListener.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.collision.broadphase 2 | 3 | trait PairListener { 4 | // This should return the new pair user data. It is okay if the 5 | // user data is null. 6 | def pairAdded(proxyUserData1: AnyRef, proxyUserData2: AnyRef): AnyRef 7 | 8 | // This should free the pair's user data. In extreme circumstances, it is 9 | // possible 10 | // this will be called with null pairUserData because the pair never 11 | // existed. 12 | def pairRemoved(proxyUserData1: AnyRef, proxyUserData2: AnyRef, pairUserData: AnyRef) 13 | } 14 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/ContactFilter.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics 2 | 3 | import shapes.Shape 4 | 5 | /** 6 | * Implement this class to provide collision filtering. In other words, you can implement 7 | * this class if you want finer control over contact creation. 8 | */ 9 | trait ContactFilter { 10 | /** 11 | * Return true if contact calculations should be performed between these two shapes. 12 | * @warning for performance reasons this is only called when the AABBs begin to overlap. 13 | */ 14 | def shouldCollide(fixture1: Fixture, fixture2: Fixture): Boolean 15 | } 16 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/contacts/ContactEdge.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics.contacts 2 | 3 | /** 4 | * A contact edge is used to connect bodies and contacts together 5 | * in a contact graph where each body is a node and each contact 6 | * is an edge. A contact edge belongs to a doubly linked list 7 | * maintained in each attached body. Each contact has two contact 8 | * nodes, one for each attached body. 9 | */ 10 | case class ContactEdge( 11 | /** Provides quick access to the other body attached. */ 12 | other: Body, 13 | /** The contact. */ 14 | contact: Contact 15 | ) 16 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/Material.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics 2 | 3 | object Material { 4 | val Default = Material(0.2f, 0f, 0f) 5 | } 6 | 7 | /** 8 | * A material that can be applied to a fixture definition. 9 | */ 10 | case class Material( 11 | /** The friction coefficient, usually in the range [0,1]. */ 12 | friction: Float, 13 | /** The restitution (elasticity) usually in the range [0,1]. */ 14 | restitution: Float, 15 | /** The density, usually in kg/m^2. */ 16 | density: Float 17 | ) { 18 | def depth(z: Float) = Material(friction, restitution, density * z) 19 | } 20 | -------------------------------------------------------------------------------- /scalabox2d/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.villane.box2d 6 | scalabox2d-parent 7 | 2.0.1-SNAPSHOT 8 | 9 | org.villane.box2d 10 | scalabox2d 11 | ScalaBox2D - 2D Physics Engine for Games 12 | 2.0.1-SNAPSHOT 13 | 14 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/shapes/SupportsGenericDistance.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.shapes 2 | 3 | import vecmath._ 4 | 5 | /** 6 | * A shape that implements this interface can be used in distance calculations 7 | * for continuous collision detection. This does not remove the necessity of 8 | * specialized penetration calculations when CCD is not in effect, however. 9 | */ 10 | trait SupportsGenericDistance { 11 | def support(xf: Transform2, v: Vector2): Vector2 12 | def getFirstVertex(xf: Transform2): Vector2 13 | } 14 | 15 | trait SupportsNewDistance extends Shape { 16 | def vertex(index: Int) = Vector2.Zero 17 | def support(v: Vector2) = 0 18 | } 19 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/collision/broadphase/Bound.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.collision.broadphase 2 | 3 | case class Bound( 4 | var value: Int, 5 | var proxyId: Int 6 | ) { 7 | 8 | def this(b: Bound) = { 9 | this(b.value, b.proxyId) 10 | stabbingCount = b.stabbingCount 11 | } 12 | 13 | var stabbingCount = 0 14 | 15 | def isLower = (value & 1) == 0 16 | 17 | def isUpper = (value & 1) == 1 18 | 19 | override def toString = { 20 | var ret = "Bound variable:\n"; 21 | ret += "value: " + value + "\n"; 22 | ret += "proxyId: " + proxyId + "\n"; 23 | ret += "stabbing count: " + stabbingCount + "\n"; 24 | ret 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/scenes/VaryingRestitution.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.testbed.scenes 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | import dsl.DSL._ 6 | 7 | object VaryingRestitution extends TestbedScene { 8 | 9 | def createScene(implicit world: dynamics.World) { 10 | body { 11 | pos(0.0f, -10.0f) 12 | box(50.0f, 10.0f) 13 | } 14 | 15 | val sd = circle(0.6f) density 5.0f 16 | val restitution = Array(0.0f, 0.1f, 0.3f, 0.5f, 0.75f, 0.9f, 1.0f) 17 | 18 | for (i <- 0 until restitution.length) body { 19 | pos(-10.0f + 3.0f * i, 10.0f) 20 | fixture(sd) restitution restitution(i) 21 | massFromShapes 22 | } 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/IslandSolverWorker.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics 2 | 3 | import java.util.concurrent._ 4 | 5 | object IslandSolverWorker { 6 | // Single work queue 7 | val workQueue = new LinkedBlockingQueue[FutureTask[Int]] 8 | 9 | var workers: List[IslandSolverWorker] = Nil 10 | for (i <- 1 until Settings.numThreads) { 11 | val worker = new IslandSolverWorker() 12 | workers ::= worker 13 | worker.start 14 | } 15 | def stopWorkers = workers foreach { w => w.shouldStop = true; w.interrupt } 16 | } 17 | 18 | class IslandSolverWorker extends Thread { 19 | var shouldStop = false 20 | override def run = while (!shouldStop) IslandSolverWorker.workQueue.take.run 21 | } 22 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/draw/Color3f.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.draw 2 | 3 | // make sure we use actual Float even if Float is replaced with Double 4 | import scala.Float 5 | 6 | /** TODO rename to Color4 ?*/ 7 | object Color3f { 8 | def apply(r: Float, g: Float, b: Float): Color3f = Color3f(r, g, b, 255) 9 | /** Create a color specifying RGB components from 0.0f to 1.0f */ 10 | def ratio(r: Float, g: Float, b: Float) = Color3f(255 * r, 255 * g, 255 * b) 11 | def ratio(r: Float, g: Float, b: Float, a: Float) = Color3f(255 * r, 255 * g, 255 * b, 255 * a) 12 | } 13 | 14 | /** 15 | * Color of RGB components where white is (255,255,255) and black is (0,0,0) 16 | */ 17 | case class Color3f(r: Float, g: Float, b: Float, a: Float) 18 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/FilterData.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics 2 | 3 | object FilterData { 4 | val Default = FilterData(0x0001, 0xFFFF, 0) 5 | } 6 | 7 | /** This holds contact filtering data. */ 8 | case class FilterData( 9 | /** The collision category bits. Normally you would just set one bit. */ 10 | categoryBits: Int, 11 | /** 12 | * The collision mask bits. This states the categories that this 13 | * shape would accept for collision. 14 | */ 15 | maskBits: Int, 16 | /** 17 | * Collision groups allow a certain group of objects to never collide (negative) 18 | * or always collide (positive). Zero means no collision group. Non-zero group 19 | * filtering always wins against the mask bits. 20 | */ 21 | groupIndex: Int 22 | ) -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/contacts/ContactResult.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics.contacts 2 | 3 | import vecmath.Vector2 4 | import shapes.Shape 5 | import collision.ContactID 6 | 7 | /** This structure is used to report contact point results. */ 8 | case class ContactResult( 9 | /** The first shape */ 10 | fixture1: Fixture, 11 | /** The second shape */ 12 | fixture2: Fixture, 13 | /** Position in world coordinates */ 14 | pos: Vector2, 15 | /** Points from shape1 to shape2 */ 16 | normal: Vector2, 17 | /** The normal impulse applied to body2 */ 18 | normalImpulse: Float, 19 | /** The tangent impulse applied to body2 */ 20 | tangentImpulse: Float, 21 | /** The contact id identifies the features in contact */ 22 | id: ContactID 23 | ) 24 | -------------------------------------------------------------------------------- /scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/scenes/Pyramid.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.testbed.scenes 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | import dsl.DSL._ 6 | 7 | object Pyramid extends TestbedScene { 8 | 9 | def createScene(implicit world: dynamics.World) { 10 | body { 11 | pos(0, -10) 12 | box(50, 10) 13 | } 14 | 15 | val box1 = box(0.5f, 0.5f) density 5 restitution 0 friction 0.9f 16 | 17 | val sx = (-10.0f, 0.75f) 18 | val Δx = (0.5625f, 2.0f) 19 | val ex = sx + Δx * 25 20 | val Δy = (1.125f, 0.0f) 21 | 22 | for { 23 | x <- sx to ex by Δx 24 | y <- x to (x + Δy * (ex.y - x.y) / Δx.y) by Δy 25 | } body { 26 | pos(y) 27 | fixture(box1) 28 | massFromShapes 29 | } 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/tests/VerticalStack.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.testbed.tests 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | 6 | class VerticalStack(_parent: TestbedMain) extends AbstractExample(_parent) { 7 | var firstTime = true 8 | val name = "Vertical Stack" 9 | override val getExampleInstructions = "Press , to shoot sideways bullet\n" 10 | 11 | override def postStep() { 12 | if (newKeyDown(0x33)) { 13 | val rnd = new scala.util.Random 14 | launchBomb((-20.0f, 1f + rnd.nextFloat * 9), 15 | (750.0f, rnd.nextFloat * 10 - 5)) 16 | } 17 | } 18 | 19 | def create = { 20 | if (firstTime) { 21 | setCamera(0f, 10f, 15f) 22 | firstTime = false 23 | } 24 | 25 | scenes.VerticalStack.create 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/vecmath/FloatExtensions.scala: -------------------------------------------------------------------------------- 1 | package org.villane.vecmath 2 | 3 | import Preamble._ 4 | 5 | /** 6 | * Extension methods for Float, mostly binary operators taking a float and a vector. 7 | * 8 | * This class is not meant to be used explicitly. 9 | */ 10 | final class FloatExtensions(a: Float) { 11 | final def +(v: Vector2) = v + a 12 | final def -(v: Vector2) = Vector2(a - v.x, a - v.y) 13 | final def *(v: Vector2) = v * a 14 | final def /(v: Vector2) = Vector2(a / v.x, a / v.y) 15 | 16 | final def cross(v: Vector2) = Vector2(-a * v.y, a * v.x) 17 | final def ×(v: Vector2) = Vector2(-a * v.y, a * v.x) 18 | 19 | final def clamp(low: Float, high: Float) = Preamble.clamp(a, low, high) 20 | 21 | final def isValid = a != Float.NaN && a != Float.NegativeInfinity && a != Float.PositiveInfinity 22 | } 23 | -------------------------------------------------------------------------------- /scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/scenes/Overhang.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.testbed.scenes 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | import dsl.DSL._ 6 | 7 | object Overhang extends TestbedScene { 8 | 9 | def createScene(implicit world: dynamics.World) { 10 | body { 11 | pos(0.0f, -10.0f) 12 | box(50.0f, 10.0f) 13 | } 14 | 15 | val w = 4.0f 16 | val h = 0.25f 17 | val sd = box(w, h) density 1 friction 0.3f restitution 0 18 | 19 | val numSlats = 8 20 | val eps = 0.14f 21 | var lastCMX = 0.0f 22 | for (i <- 0 until numSlats) body { 23 | val newX = lastCMX + w - eps 24 | lastCMX = (i * lastCMX + newX) / (i + 1) 25 | pos(newX, 0.25f + 2 * h * (numSlats - i - 1)) 26 | fixture(sd) 27 | massFromShapes 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/contacts/ContactConstraint.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics.contacts 2 | 3 | import vecmath.Vector2 4 | import collision.Manifold 5 | import dynamics.Body 6 | 7 | class ContactConstraint( 8 | val points: Array[ContactConstraintPoint], 9 | val normal: Vector2, 10 | val manifold: Manifold, 11 | val body1: Body, 12 | val body2: Body, 13 | val friction: Float, 14 | val restitution: Float 15 | ) 16 | 17 | class ContactConstraintPoint { 18 | var localAnchor1 = Vector2.Zero 19 | var localAnchor2 = Vector2.Zero 20 | var r1 = Vector2.Zero 21 | var r2 = Vector2.Zero 22 | var normalImpulse = 0f 23 | var tangentImpulse = 0f 24 | var positionImpulse = 0f 25 | var normalMass = 0f 26 | var tangentMass = 0f 27 | var equalizedMass = 0f 28 | var separation = 0f 29 | var velocityBias = 0f 30 | } 31 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/joints/PointingDeviceJointDef.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics.joints 2 | 3 | import vecmath.Vector2 4 | 5 | class PointingDeviceJointDef extends JointDef { 6 | /** 7 | * The initial world target point. This is assumed 8 | * to coincide with the body anchor initially. 9 | */ 10 | var target = Vector2.Zero 11 | 12 | /** 13 | * The maximum constraint force that can be exerted 14 | * to move the candidate body. Usually you will express 15 | * as some multiple of the weight (multiplier * mass * gravity). 16 | */ 17 | var maxForce = 0f 18 | 19 | /** The response speed. */ 20 | var frequencyHz = 5f 21 | 22 | /** The damping ratio. 0 = no damping, 1 = critical damping. */ 23 | var dampingRatio = 0.7f 24 | 25 | /** The time step used in the simulation. */ 26 | var timeStep = 1f / 60f 27 | } 28 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/contacts/ContactPoint.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics.contacts 2 | 3 | import vecmath.Vector2 4 | import shapes.Shape 5 | import collision.ContactID 6 | 7 | case class ContactPoint( 8 | /** The first shape */ 9 | fixture1: Fixture, 10 | /** The second shape */ 11 | fixture2: Fixture, 12 | /** Position in world coordinates */ 13 | pos: Vector2, 14 | /** Velocity of point on body2 relative to point on body1 (pre-solver) */ 15 | velocity: Vector2, 16 | /** Points from shape1 to shape2 */ 17 | normal: Vector2, 18 | /** The separation is negative when shapes are touching */ 19 | separation: Float, 20 | /** The combined friction coefficient */ 21 | friction: Float, 22 | /** The combined restitution coefficient */ 23 | restitution: Float, 24 | /** The contact id identifies the features in contact */ 25 | id: ContactID 26 | ) 27 | -------------------------------------------------------------------------------- /scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/scenes/Chain.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.testbed.scenes 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | import dsl.DSL._ 6 | 7 | object Chain extends TestbedScene { 8 | 9 | def createScene(implicit world: dynamics.World) { 10 | val ground = body { 11 | pos(0.0f, -10.0f) 12 | box(50.0f, 10.0f) 13 | } 14 | 15 | val sd = box(0.6f, 0.125f) density 20.0f friction 0.2f 16 | body { 17 | val y = 25.0f 18 | var prevBody = ground 19 | for (i <- 0 until 30) { 20 | val bd = body { 21 | pos(0.5f + i, y) 22 | fixture(sd) 23 | massFromShapes 24 | } 25 | 26 | joint ( 27 | revolute(prevBody -> bd) 28 | anchor(i, y) 29 | collideConnected(false) 30 | ) 31 | prevBody = bd 32 | } 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/vecmath/Transform2.scala: -------------------------------------------------------------------------------- 1 | package org.villane.vecmath 2 | 3 | object Transform2 { 4 | val Identity = Transform2(Vector2.Zero, Matrix22.Identity) 5 | 6 | /** 7 | * Creates a transform from a position and an angle. 8 | */ 9 | def apply(pos: Vector2, angle: Float): Transform2 = Transform2(pos, Matrix22.rotation(angle)) 10 | } 11 | 12 | case class Transform2( 13 | /** Translation component of the transformation */ 14 | pos: Vector2, 15 | /** Rotation component of the transformation */ 16 | rot: Matrix22 17 | ) { 18 | def *(v: Vector2) = Vector2( 19 | pos.x + rot.a11 * v.x + rot.a12 * v.y, 20 | pos.y + rot.a21 * v.x + rot.a22 * v.y 21 | ) // = pos + (rot * v) 22 | def **(v: Vector2) = { 23 | val v1x = v.x - pos.x 24 | val v1y = v.y - pos.y 25 | Vector2(v1x * rot.a11 + v1y * rot.a21, v1x * rot.a12 + v1y * rot.a22) 26 | } // = rot ** (v - pos) 27 | } 28 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/DefaultContactFilter.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics 2 | 3 | import shapes._ 4 | 5 | /** 6 | * Default contact filter, using groupIndex, maskBits and categoryBits as detailed 7 | * in Box2d manual. 8 | */ 9 | object DefaultContactFilter extends ContactFilter { 10 | /** 11 | * Return true if contact calculations should be performed between these two shapes. 12 | * If you implement your own collision filter you may want to build from this implementation. 13 | */ 14 | def shouldCollide(fixture1: Fixture, fixture2: Fixture) = { 15 | val filter1 = fixture1.filter 16 | val filter2 = fixture2.filter 17 | 18 | if (filter1.groupIndex == filter2.groupIndex && filter1.groupIndex != 0) 19 | filter1.groupIndex > 0 20 | else 21 | (filter1.maskBits & filter2.categoryBits) != 0 && (filter1.categoryBits & filter2.maskBits) != 0 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software 13 | in a product, an acknowledgment in the product documentation would be 14 | appreciated but is not required. 15 | 2. Altered source versions must be plainly marked as such, and must not be 16 | misrepresented as being the original software. 17 | 3. This notice may not be removed or altered from any source distribution. 18 | 19 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/DestructionListener.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics 2 | 3 | import shapes.Shape 4 | import joints.Joint 5 | import controllers.Controller 6 | 7 | /** 8 | * Joints and shapes are destroyed when their associated 9 | * body is destroyed. Implement this listener so that you 10 | * may nullify references to these joints and shapes. 11 | */ 12 | trait DestructionListener { 13 | /** 14 | * Called when any joint is about to be destroyed due 15 | * to the destruction of one of its attached bodies. 16 | */ 17 | def sayGoodbye(joint: Joint) 18 | 19 | /** 20 | * Called when any fixture is about to be destroyed due 21 | * to the destruction of its parent body. 22 | */ 23 | def sayGoodbye(fixture: Fixture) 24 | 25 | /** 26 | * Called when any controller is about to be destroyed due 27 | * to the destruction of its parent body. 28 | */ 29 | def sayGoodbye(controller: Controller) 30 | } 31 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/collision/broadphase/Pair.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.collision.broadphase 2 | 3 | object Pair { 4 | val Buffered = 0x0001 5 | val Removed = 0x0002 6 | val Final = 0x0004 7 | } 8 | 9 | case class Pair(var proxyId1: Int, var proxyId2: Int) extends Ordered[Pair] { 10 | var userData: AnyRef = null 11 | var status = 0 12 | 13 | def setBuffered() = status |= Pair.Buffered 14 | def clearBuffered() = status &= ~Pair.Buffered 15 | def isBuffered = (status & Pair.Buffered) == Pair.Buffered 16 | 17 | def setRemoved() = status |= Pair.Removed 18 | def clearRemoved() = status &= ~Pair.Removed 19 | def isRemoved = (status & Pair.Removed) == Pair.Removed 20 | 21 | def setFinal() = status |= Pair.Final 22 | def isFinal = (status & Pair.Final) == Pair.Final 23 | 24 | def compare(p: Pair) = { 25 | val d = proxyId1 - p.proxyId1 26 | if (d != 0) 27 | d 28 | else 29 | proxyId2 - p.proxyId2 30 | } 31 | } -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/shapes/Point.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.shapes 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | import Settings.ε 6 | 7 | /** 8 | * Like a circle shape of zero radius, except that it has a finite mass. 9 | */ 10 | class Point(val pos: Vector2, val mass: Float) extends Shape { 11 | // TODO Erkki is this good? Should ask on forums 12 | val radius = Settings.polygonRadius 13 | 14 | def testPoint(t: Transform2, p: Vector2) = false 15 | def testSegment(t: Transform2, segment: Segment, maxLambda: Float) = 16 | SegmentCollide.Miss 17 | 18 | def computeSubmergedArea(normal: Vector2, offset: Float, t: Transform2) = 19 | (0f,Vector2.Zero) 20 | 21 | def computeAABB(t: Transform2) = { 22 | val p = t * pos 23 | AABB(p - ε, p + ε) 24 | } 25 | 26 | def computeSweepRadius(pivot: Vector2) = (pos - pivot).length + Settings.toiSlop 27 | 28 | def computeMass(density: Float) = Mass(mass, pos, 0) 29 | 30 | } 31 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/controllers/GravityController.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics.controllers 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | 6 | /** Applies simplified gravity between every pair of bodies */ 7 | abstract class GravityController extends Controller { 8 | /** Specifies the strength of the gravitiation force */ 9 | var G = 0f 10 | 11 | /** If true, gravity is proportional to r^-2, otherwise r^-1 */ 12 | var invSqr = false 13 | 14 | def step(step: TimeStep) = 15 | for (b1 <- bodies; b2 <- bodies) if (b1 != b2) { 16 | val d = b2.worldCenter - b1.worldCenter 17 | val r2 = d.lengthSquared 18 | if (r2 >= Settings.Epsilon) { 19 | val f = if (invSqr) 20 | G / r2 / sqrt(r2) * b1.mass * b2.mass * d 21 | else 22 | G / r2 * b1.mass * b2.mass * d 23 | b1.applyForce(f, b1.worldCenter) 24 | b2.applyForce(-1.0f * f, b2.worldCenter) 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/collision/ContactID.scala: -------------------------------------------------------------------------------- 1 | /* TODO HEADER */ 2 | package org.villane.box2d.collision 3 | 4 | object ContactID { 5 | val Zero = ContactID(0, 0, 0, false) 6 | val NullFeature = Int.MaxValue 7 | } 8 | 9 | /** 10 | * Contact ids to facilitate warm starting. 11 | * The features that intersect to form the contact point 12 | */ 13 | case class ContactID( 14 | /** The edge that defines the outward contact normal. */ 15 | referenceEdge: Int, 16 | /** The edge most anti-parallel to the reference edge. */ 17 | incidentEdge: Int, 18 | /** The vertex (0 or 1) on the incident edge that was clipped. */ 19 | incidentVertex: Int, 20 | /** True indicates that the reference edge is on shape2. */ 21 | flip: Boolean 22 | ) { 23 | /** Returns a new ContactID based on this one, but with the features.flip value changed. */ 24 | def withFlip(flip: Boolean) = ContactID( 25 | this.referenceEdge, 26 | this.incidentEdge, 27 | this.incidentVertex, 28 | flip 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/controllers/PlanetGravityController.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics.controllers 2 | 3 | import vecmath.Preamble._ 4 | import dsl.DSL._ 5 | 6 | class PlanetGravityController extends Controller with SensorManagedBodies { 7 | /** Specifies the strength of the gravitiation force */ 8 | var G = 0f 9 | 10 | /** If true, gravity is proportional to r^-2, otherwise r^-1 */ 11 | var invSqr = false 12 | 13 | def createSensor(body: Body, radius: Float) = { 14 | body.createFixture(circle(radius) sensor true) 15 | sensor = body 16 | } 17 | 18 | def step(step: TimeStep) = forAwakeBodies { body => 19 | val d = sensor.worldCenter - body.worldCenter 20 | val r2 = d.lengthSquared 21 | if (r2 >= Settings.Epsilon) { 22 | val sm = if (sensor.isDynamic) sensor.mass else 1f 23 | val f = if (invSqr) 24 | G / r2 / sqrt(r2) * body.mass * sm * d 25 | else 26 | G / r2 * body.mass * sm * d 27 | body.applyForce(f, body.worldCenter) 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/vecmath/Vector2Range.scala: -------------------------------------------------------------------------------- 1 | package org.villane.vecmath 2 | 3 | /** 4 | * TODO inclusive / exclusive, negative step 5 | */ 6 | class Vector2Range(start: Vector2, end: Vector2, step: Vector2) 7 | extends Iterable[Vector2] { 8 | 9 | def elements = new Iterator[Vector2] { 10 | var v = start 11 | def hasNext = inInterval(v) 12 | def next = { 13 | var n = v 14 | v += step 15 | n 16 | } 17 | } 18 | 19 | def by(step: Vector2) = new Vector2Range(start, end, step) 20 | 21 | /*override def foreach(f: Vector2 => Unit) { 22 | var i = this.start 23 | val until = if (inInterval(end)) end + 1 else end 24 | 25 | while (i.x < until.x || i.y < until.y) { 26 | f(i) 27 | i += step 28 | } 29 | }*/ 30 | 31 | /** Is the argument inside the interval defined by `start' and `end'? 32 | * Returns true if `v' is inside [start, end). 33 | */ 34 | protected def inInterval(v: Vector2): Boolean = 35 | (v.x >= start.x && v.x < end.x) || 36 | (v.y >= start.y && v.y < end.y) 37 | } 38 | -------------------------------------------------------------------------------- /scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/tests/SimpleTest.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.testbed.tests 2 | 3 | import vecmath._ 4 | import scenes._ 5 | 6 | object SimpleTest { 7 | def apply(parent: TestbedMain, 8 | name: String, 9 | sf: TestbedScene, 10 | offset: Vector2, scale: Float) = 11 | new SimpleTest(parent, name, sf, offset, scale) 12 | 13 | def apply(parent: TestbedMain, name: String, sf: TestbedScene, scale: Float) = 14 | new SimpleTest(parent, name, sf, Vector2.Zero, scale) 15 | } 16 | 17 | /** 18 | * Simple test without custom instructions or custom key mapping. 19 | * Can be created given a scene. 20 | */ 21 | class SimpleTest( 22 | _parent: TestbedMain, 23 | val name: String, 24 | scene: TestbedScene, 25 | offset: Vector2, 26 | scale: Float 27 | ) extends AbstractExample(_parent) { 28 | var firstTime = true 29 | 30 | def create = { 31 | if (firstTime) { 32 | setCamera(offset.x, offset.y, scale) 33 | firstTime = false 34 | } 35 | 36 | scene.create 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /scalabox2d-testbed/src/test/scala/org/villane/box2d/testbed/Pyramid4PerfTest.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.testbed 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | import dsl.DSL._ 6 | 7 | object Pyramid4PerfTest extends scenes.TestbedScene { 8 | 9 | def main(args: Array[String]) { 10 | val test = new HeadlessPerformanceTest(this) 11 | test.run(true, 1000, 100, false) 12 | } 13 | 14 | def createScene(implicit world: dynamics.World) { 15 | body { 16 | pos(0, -10) 17 | box(50, 10) 18 | } 19 | 20 | val box1 = box(0.5f, 0.5f) density 5f restitution 0f friction 0.9f 21 | var x = Vector2(-30.0f, 0.75f); 22 | var y = Vector2.Zero 23 | val deltaX = Vector2(0.5625f, 2.0f); 24 | val deltaY = Vector2(1.125f, 0.0f); 25 | 26 | val num = 17 27 | def loop = for (i <- 0 until num) { 28 | y = x 29 | for (j <- i until num) body { 30 | pos(y) 31 | fixture(box1) 32 | massFromShapes 33 | 34 | y += deltaY 35 | } 36 | x += deltaX 37 | } 38 | loop 39 | x = (-10f, 0.75f) 40 | loop 41 | x = (10f, 0.75f) 42 | loop 43 | x = (30f, 0.75f) 44 | loop 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/FixtureDef.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics 2 | 3 | import shapes.ShapeDef 4 | 5 | /** 6 | * A fixture definition is used to create a fixture. This class defines an 7 | * abstract fixture definition. You can reuse fixture definitions safely. 8 | */ 9 | case class FixtureDef(var shapeDef: ShapeDef) { 10 | /** The friction coefficient, usually in the range [0,1]. */ 11 | var friction = 0.2f 12 | 13 | /** The restitution (elasticity) usually in the range [0,1]. */ 14 | var restitution = 0f 15 | 16 | /** The density, usually in kg/m^2. */ 17 | var density = 0f 18 | 19 | /** A sensor collects contact information but never generates a collision response. */ 20 | var isSensor = false 21 | 22 | /** Contact filtering data. */ 23 | var filter = FilterData.Default 24 | 25 | /** Use this to store application specific fixture data. */ 26 | var userData: AnyRef = null 27 | 28 | /** Apply the given material to this fixture definition. */ 29 | def apply(m: Material) = { 30 | friction = m.friction 31 | density = m.density 32 | restitution = m.restitution 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/scenes/PlanetGravity.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.testbed.scenes 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | import shapes.AABB 6 | import dsl.DSL._ 7 | import dynamics._ 8 | import controllers._ 9 | 10 | object PlanetGravity extends TestbedScene { 11 | 12 | var planet: Body = null 13 | var player: Body = null 14 | 15 | def createScene(implicit world: dynamics.World) { 16 | planet = body { 17 | circle(5) 18 | } 19 | 20 | player = body { 21 | pos(0f, 7f) 22 | box(0.4f, 1.2f) density 1 23 | massFromShapes 24 | } 25 | 26 | val satellite = body { 27 | pos(10, 10) 28 | circle(0.5f) density 2 29 | massFromShapes 30 | } 31 | satellite.applyForce((150f, -150f), satellite.worldCenter) 32 | satellite.angularVelocity = -0.4f 33 | 34 | val gc = new PlanetGravityController 35 | gc.createSensor(planet, 15) 36 | gc.G = 9.81f 37 | world.addController(gc) 38 | } 39 | 40 | override protected def createWorld = new World( 41 | AABB((-200, -100), (200, 200)), 42 | (0, 0), 43 | true 44 | ) 45 | 46 | } 47 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/collision/PointCircleCollider.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.collision 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | import shapes._ 6 | 7 | object PointCircleCollider extends Collider[Point, Circle] { 8 | 9 | def collide(point: Point, xf1: Transform2, 10 | circle: Circle, xf2: Transform2): Option[Manifold] = { 11 | // TODO reuse Circle.testPoint? 12 | var p1 = xf1 * point.pos 13 | var p2 = xf2 * circle.pos 14 | val d = p2 - p1 15 | val distSqr = d dot d 16 | 17 | val r = circle.radius 18 | 19 | if (distSqr > r * r) return None 20 | 21 | var separation = 0f 22 | var normal: Vector2 = null 23 | if (distSqr < Settings.Epsilon) { 24 | separation = -r 25 | normal = Vector2.YUnit 26 | } else { 27 | val dist = sqrt(distSqr) 28 | separation = dist - r 29 | val a = 1 / dist 30 | normal = d * a 31 | } 32 | val points = new Array[ManifoldPoint](1) 33 | val id = ContactID.Zero 34 | val p = p2 - normal * r 35 | points(0) = ManifoldPoint(xf1 ** p, xf2 ** p, separation, id) 36 | Some(Manifold(points, normal)) 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/scenes/Domino.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.testbed.scenes 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | import dsl.DSL._ 6 | 7 | object Domino extends TestbedScene { 8 | 9 | def createScene(implicit world: dynamics.World) { 10 | // Floor 11 | body { 12 | pos(0, -10) 13 | box(50.0f, 10.0f) 14 | } 15 | 16 | // Platforms 17 | for (i <- 0 to 3) body { 18 | pos(0.0f, 5f + 5f * i) 19 | box(15.0f, 0.125f) 20 | } 21 | 22 | val sd = box(0.125f, 2f) density 25.0f friction 0.5f 23 | var numPerRow = 25 24 | 25 | for (i <- 0 to 3) { 26 | for (j <- 0 until numPerRow) body { 27 | var p = Vector2(-14.75f + j * (29.5f / (numPerRow - 1)), 28 | 7.3f + 5f * i) 29 | if (i == 2 && j == 0) { 30 | angle(-0.1f) 31 | p += (0.1f, 0) 32 | } else if (i == 3 && j == numPerRow - 1) { 33 | angle(0.1f) 34 | p -= (0.1f, 0) 35 | } else { 36 | angle(0f) 37 | } 38 | pos(p) 39 | fixture(sd) 40 | massFromShapes 41 | } 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /scalabox2d-svg/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.villane.box2d 7 | scalabox2d-parent 8 | 2.0.1-SNAPSHOT 9 | 10 | org.villane.box2d 11 | scalabox2d-svg 12 | ScalaBox2D - 2D Physics Engine for Games (SVG Module) 13 | 2.0.1-SNAPSHOT 14 | 15 | 16 | 17 | slick-repo 18 | http://slick.cokeandcode.com/mavenrepo/ 19 | 20 | 21 | 22 | 23 | 24 | org.villane.box2d 25 | scalabox2d 26 | 2.0.1-SNAPSHOT 27 | 28 | 29 | slick 30 | slick 31 | 264 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/scenes/VaryingFriction.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.testbed.scenes 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | import dsl.DSL._ 6 | 7 | object VaryingFriction extends TestbedScene { 8 | 9 | def createScene(implicit world: dynamics.World) { 10 | body { 11 | pos(0.0f, -20.0f) 12 | box(100.0f, 20.0f) 13 | } 14 | 15 | body { 16 | pos(-4.0f, 22.0f) 17 | angle(-0.25f) 18 | box(13.0f, 0.25f) 19 | } 20 | 21 | body { 22 | pos(10.5f, 19.0f) 23 | box(0.25f, 1.0f) 24 | } 25 | 26 | body { 27 | pos(4.0f, 14.0f) 28 | angle(0.25f) 29 | box(13.0f, 0.25f) 30 | } 31 | 32 | body { 33 | pos(-10.5f, 11.0f) 34 | box(0.25f, 1.0f) 35 | } 36 | 37 | body { 38 | pos(-4.0f, 6.0f) 39 | angle(-0.25f) 40 | box(13.0f, 0.25f) 41 | } 42 | 43 | val sd = box(0.5f, 0.5f) density 25.0f 44 | val friction = Array(0.75f, 0.5f, 0.35f, 0.1f, 0.0f) 45 | 46 | for (i <- 0 until 5) body { 47 | pos(-15.0f + 4.0f * i, 28.0f) 48 | fixture(sd) friction friction(i) 49 | massFromShapes 50 | } 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/scenes/Bridge.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.testbed.scenes 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | import dsl.DSL._ 6 | 7 | object Bridge extends TestbedScene { 8 | 9 | def createScene(implicit world: dynamics.World) { 10 | val ground = body { 11 | pos(0,0) 12 | box(50.0f, 0.2f) 13 | } 14 | 15 | val sd = box(0.65f, 0.125f) density 20.0f friction 0.2f 16 | 17 | val numPlanks = 30 18 | 19 | var prevBody = ground 20 | for (i <- 0 until numPlanks) { 21 | val bd = body { 22 | pos(-14.5f + 1.0f * i, 5.0f) 23 | fixture(sd) 24 | massFromShapes 25 | } 26 | joint(revolute(prevBody -> bd) anchor(-15.0f + 1.0f * i, 5.0f)) 27 | prevBody = bd 28 | } 29 | 30 | joint(revolute(prevBody -> ground) anchor(-15.0f + 1.0f * numPlanks, 5.0f)) 31 | 32 | body { 33 | pos(0, 10) 34 | box(1,1) density 5 friction 0.2f restitution 0.1f 35 | massFromShapes 36 | } 37 | 38 | body { 39 | pos(0, 12) 40 | circle((0, 0), 0.9f) density 5 friction 0.2f 41 | circle((0, 1), 0.9f) density 5 friction 0.2f 42 | massFromShapes 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/TestSettings.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.testbed 2 | 3 | import org.villane.vecmath.Vector2 4 | 5 | /** 6 | * Settings for the current test. Mostly self-explanatory. 7 | *

8 | * The settings from here are applied during AbstractExample::step(). 9 | * 10 | */ 11 | class TestSettings { 12 | var hz = 60 13 | var iterationCount = 10 14 | var gravity = Vector2(0f, -9.81f) 15 | 16 | var enableWarmStarting = true 17 | var enablePositionCorrection = true 18 | var enableTOI = true 19 | var enableSleeping = true 20 | 21 | /** Pause the simulation */ 22 | var pause = false 23 | /** Take a single timestep */ 24 | var singleStep = false 25 | /** True if we should reset the demo for the next frame. */ 26 | var reset = false 27 | 28 | var drawShapes = true 29 | var drawSensors = true 30 | var drawJoints = true 31 | var drawCoreShapes = false 32 | var drawCOMs = false 33 | var drawStats = false 34 | var drawImpulses = false 35 | var drawAABBs = false 36 | var drawPairs = false 37 | var drawContactPoints = false 38 | var drawContactNormals = false 39 | var drawContactForces = false 40 | var drawFrictionForces = false 41 | 42 | var testIndex = 0 43 | } 44 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/collision/Manifold.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.collision 2 | 3 | import vecmath.Vector2 4 | 5 | /** A manifold for two touching convex shapes. */ 6 | case class Manifold( 7 | /** The points of contact. */ 8 | points: Seq[ManifoldPoint], 9 | /** The shared unit normal vector. */ 10 | normal: Vector2 11 | ) { 12 | assert(points.length > 0, "Can't create a manifold without points!") 13 | } 14 | 15 | /** 16 | * A manifold point is a contact point belonging to a contact 17 | * manifold. It holds details related to the geometry and dynamics 18 | * of the contact points. 19 | * The point is stored in local coordinates because CCD 20 | * requires sub-stepping in which the separation is stale. 21 | */ 22 | case class ManifoldPoint( 23 | /** Local position of the contact point in body1 */ 24 | localPoint1: Vector2, 25 | /** Local position of the contact point in body2 */ 26 | localPoint2: Vector2, 27 | /** The separation of the shapes along the normal vector */ 28 | separation: Float, 29 | /** Uniquely identifies a contact point between two shapes */ 30 | id: ContactID 31 | ) { 32 | /** The non-penetration force */ 33 | var normalImpulse = 0f 34 | /** The friction force */ 35 | var tangentImpulse = 0f 36 | } 37 | -------------------------------------------------------------------------------- /scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/scenes/VerticalStack.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.testbed.scenes 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | import dsl.DSL._ 6 | 7 | import scala.util.Random 8 | 9 | object VerticalStack extends TestbedScene { 10 | 11 | def createScene(implicit world: dynamics.World) { 12 | body { 13 | pos(0.0f, 0.0f) 14 | box(50.0f, 10.0f, (0.0f, -10.0f), 0.0f) 15 | box(0.1f, 10.0f, (20.0f, 10.0f), 0.0f) 16 | } 17 | 18 | val xs = Array(0.0f, -10.0f, -5.0f, 5.0f, 10.0f) 19 | val sd = box(0.5f, 0.5f) density 1.0f friction 0.3f 20 | 21 | for { 22 | j <- 0 until xs.length 23 | i <- 0 until 12 24 | } body { 25 | //float32 x = b2Random(-0.1f, 0.1f); 26 | //float32 x = i % 2 == 0 ? -0.025f : 0.025f; 27 | //bd.position.Set(xs[j], 2.51f + 4.02f * i); 28 | pos(xs(j) + (new Random().nextFloat * 0.1f - 0.05f), 29 | 0.752f + 1.54f * i) 30 | fixture(sd) 31 | // For this test we are using continuous physics for all boxes. 32 | // This is a stress test, you normally wouldn't do this for 33 | // performance reasons. 34 | bullet(true) 35 | sleepingAllowed(true) 36 | massFromShapes 37 | } 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/joints/DistanceJointDef.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics.joints 2 | 3 | import vecmath.Vector2 4 | 5 | /** 6 | * Definition for a distance joint. A distance joint 7 | * keeps two points on two bodies at a constant distance 8 | * from each other. 9 | */ 10 | class DistanceJointDef extends JointDef { 11 | /** The local anchor point relative to body1's origin. */ 12 | var localAnchor1 = Vector2.Zero 13 | 14 | /** The local anchor point relative to body2's origin. */ 15 | var localAnchor2 = Vector2.Zero 16 | 17 | /** The equilibrium length between the anchor points. */ 18 | var length = 1.0f 19 | 20 | var frequencyHz = 0f 21 | 22 | var dampingRatio = 0f 23 | 24 | /** 25 | * Initialize the bodies, anchors, and length using the world 26 | * anchors. 27 | * @param b1 First body 28 | * @param b2 Second body 29 | * @param anchor1 World anchor on first body 30 | * @param anchor2 World anchor on second body 31 | */ 32 | def this(b1: Body, b2: Body, anchor1: Vector2, anchor2: Vector2) = { 33 | this() 34 | body1 = b1 35 | body2 = b2 36 | localAnchor1 = body1.toLocalPoint(anchor1) 37 | localAnchor2 = body2.toLocalPoint(anchor2) 38 | length = (anchor2 - anchor1).length 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/scenes/CCDTest.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.testbed.scenes 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | import dsl.DSL._ 6 | 7 | import scala.util.Random 8 | 9 | object CCDTest extends TestbedScene { 10 | 11 | def createScene(implicit world: dynamics.World) { 12 | val k_restitution = 1.4f 13 | 14 | body { 15 | pos(0, 20) 16 | box(0.1f, 10.0f, (-10.0f, 0.0f), 0.0f) density 0 restitution k_restitution 17 | box(0.1f, 10.0f, (10.0f, 0.0f), 0.0f) density 0 restitution k_restitution 18 | box(0.1f, 10.0f, (0.0f, -10.0f), 0.5f * π) density 0 restitution k_restitution 19 | box(0.1f, 10.0f, (0.0f, 10.0f), -0.5f * π) density 0 restitution k_restitution 20 | } 21 | 22 | body { 23 | pos(0.0f, 15.0f) 24 | // bottom 25 | box(1.5f, 0.15f) density 4.0f 26 | // left 27 | box(0.15f, 2.7f, (-1.45f, 2.35f), 0.2f) density 4.0f 28 | // right 29 | box(0.15f, 2.7f, (1.45f, 2.35f), -0.2f) density 4.0f 30 | bullet(true) 31 | massFromShapes 32 | } 33 | 34 | for (i <- 0 until 10) body { 35 | pos(0.0f, 15.5f + i) 36 | circle(0.25f) density 1.0f restitution 0.0f friction 0.05f 37 | bullet(true) 38 | massFromShapes 39 | } angularVelocity = new Random().nextFloat * 100 - 50 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/collision/CircleCollider.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.collision 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | import shapes.Circle 6 | 7 | /** 8 | * Circle/circle overlap solver - for internal use only. 9 | */ 10 | object CircleCollider extends Collider[Circle, Circle] { 11 | 12 | def collide(circle1: Circle, xf1: Transform2, 13 | circle2: Circle, xf2: Transform2): Option[Manifold] = { 14 | var p1 = xf1 * circle1.pos 15 | var p2 = xf2 * circle2.pos 16 | val d = p2 - p1 17 | 18 | val distSqr = d dot d 19 | 20 | val r1 = circle1.radius 21 | val r2 = circle2.radius 22 | val radiusSum = r1 + r2 23 | 24 | if (distSqr > radiusSum * radiusSum) return None 25 | 26 | var separation = 0f 27 | var normal: Vector2 = null 28 | if (distSqr < Settings.Epsilon) { 29 | separation = -radiusSum 30 | normal = Vector2.YUnit 31 | } else { 32 | val dist = sqrt(distSqr) 33 | separation = dist - radiusSum 34 | val a = 1 / dist 35 | normal = d * a 36 | } 37 | 38 | p1 += (normal * r1) 39 | p2 -= (normal * r2) 40 | val p = (p1 + p2) * 0.5f 41 | 42 | val points = new Array[ManifoldPoint](1) 43 | val id = ContactID.Zero 44 | points(0) = ManifoldPoint(xf1 ** p, xf2 ** p, separation, id) 45 | Some(Manifold(points, normal)) 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/shapes/AABB.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.shapes 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | 6 | object AABB { 7 | def centered(center: Vector2, size: Vector2) = AABB( 8 | center - size / 2, center + size / 2 9 | ) 10 | } 11 | 12 | /** 13 | * An axis-aligned bounding box 14 | */ 15 | case class AABB(lowerBound: Vector2, upperBound: Vector2) { 16 | def size = upperBound - lowerBound 17 | def center = lowerBound + (upperBound - lowerBound) / 2 18 | 19 | def combineWith(other: AABB) = AABB( 20 | min(lowerBound, other.lowerBound), 21 | max(upperBound, other.upperBound) 22 | ) 23 | 24 | /** Does this aabb contain the provided AABB. */ 25 | def contains(other: AABB) = { 26 | lowerBound.x <= other.lowerBound.x && 27 | lowerBound.y <= other.lowerBound.y && 28 | upperBound.x >= other.upperBound.x && 29 | upperBound.y >= other.upperBound.y 30 | } 31 | 32 | /** Check if AABBs overlap. */ 33 | def overlaps(other: AABB) = { 34 | val d1 = other.lowerBound - upperBound 35 | val d2 = lowerBound - other.upperBound 36 | ! (d1.x > 0.0f || d1.y > 0.0f || d2.x > 0.0f || d2.y > 0.0f) 37 | } 38 | 39 | /** Verify that the bounds are sorted. */ 40 | def isValid = { 41 | val d = upperBound - lowerBound 42 | val valid = (d.x >= 0 && d.y >= 0) 43 | valid && lowerBound.isValid && upperBound.isValid 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/tests/PlanetGravity.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.testbed.tests 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | 6 | class PlanetGravity(_parent: TestbedMain) extends AbstractExample(_parent) { 7 | var firstTime = true 8 | val name = "Planet Gravity" 9 | 10 | val scene = scenes.PlanetGravity 11 | 12 | override def postStep() { 13 | /*if (newKeyDown(0x24)) { // J 14 | 15 | } 16 | val force = 3f 17 | if (keyDown(0xCB)) { // Left 18 | if (!scene.player.contactList.isEmpty) { 19 | val c = scene.player.contactList.first.contact 20 | if (!c.manifolds.isEmpty) { 21 | val m = c.manifolds.first 22 | if (m.points.size == 2) { 23 | val f = force * m.normal.normal 24 | scene.player.applyForce(f, scene.player.worldCenter) 25 | } 26 | } 27 | } 28 | } 29 | if (keyDown(0xCD)) { // Right 30 | if (!scene.player.contactList.isEmpty) { 31 | val c = scene.player.contactList.first.contact 32 | if (!c.manifolds.isEmpty) { 33 | val m = c.manifolds.first 34 | val f = -force * m.normal.normal 35 | scene.player.applyForce(f, scene.player.worldCenter) 36 | } 37 | } 38 | }*/ 39 | } 40 | 41 | def create = { 42 | if (firstTime) { 43 | setCamera(0f, 0f, 15f) 44 | firstTime = false 45 | } 46 | 47 | scenes.PlanetGravity.create 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/contacts/ContactListener.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics.contacts 2 | 3 | /** 4 | * Implement this class to get collision results. You can use these results for 5 | * things like sounds and game logic. You can also get contact results by 6 | * traversing the contact lists after the time step. However, you might miss 7 | * some contacts because continuous physics leads to sub-stepping. 8 | * Additionally you may receive multiple callbacks for the same contact in a 9 | * single time step. 10 | * You should strive to make your callbacks efficient because there may be 11 | * many callbacks per time step. 12 | *

Warning: The contact separation is the last computed value. 13 | *

Warning: You cannot create/destroy Box2D entities inside these callbacks. 14 | * Buffer any such events and apply them at the end of the time step. 15 | */ 16 | trait ContactListener { 17 | /** 18 | * Called when a contact point is added. This includes the geometry 19 | * and the forces. 20 | */ 21 | def add(point: ContactPoint) 22 | 23 | /** 24 | * Called when a contact point persists. This includes the geometry 25 | * and the forces. 26 | */ 27 | def persist(point: ContactPoint) 28 | 29 | /** 30 | * Called when a contact point is removed. This includes the last 31 | * computed geometry and forces. 32 | */ 33 | def remove(point: ContactPoint) 34 | 35 | def result(point: ContactResult) 36 | } 37 | -------------------------------------------------------------------------------- /scalabox2d-svg/src/main/scala/org/villane/box2d/svg/ConvexHull.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.svg 2 | 3 | import vecmath.Vector2 4 | 5 | /** 6 | * @author Mason Green 7 | */ 8 | object ConvexHull { 9 | /** 10 | * Melkman's Algorithm 11 | * http://www.ams.sunysb.edu/~jsbm/courses/345/melkman.pdf 12 | * Returns a convex hull in CCW order 13 | * 14 | * The input has to be a polygon in either CCW or CW order. 15 | */ 16 | def convexHull(V: Array[Vector2]) = { 17 | def left(a: Vector2, b: Vector2, c: Vector2) = 18 | (b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y) > 0 19 | 20 | val n = V.length 21 | val D = new Array[Vector2](2 * n + 1) 22 | var bot = n - 2 23 | var top = bot + 3 24 | 25 | D(bot) = V(2) 26 | D(top) = V(2) 27 | 28 | if (left(V(0), V(1), V(2))) { 29 | D(bot+1) = V(0) 30 | D(bot+2) = V(1) 31 | } else { 32 | D(bot+1) = V(1) 33 | D(bot+2) = V(0) 34 | } 35 | 36 | var i = 3 37 | while(i < n) { 38 | while (left(D(bot), D(bot+1), V(i)) && left(D(top-1), D(top), V(i))) { 39 | i += 1 40 | } 41 | while (!left(D(top-1), D(top), V(i))) top -= 1 42 | top += 1; D(top) = V(i) 43 | while (!left(D(bot), D(bot+1), V(i))) bot += 1 44 | bot -= 1; D(bot) = V(i) 45 | i += 1 46 | } 47 | 48 | val H = new Array[Vector2](top - bot) 49 | var h = 0 50 | while(h < (top - bot)) { 51 | H(h) = D(bot + h) 52 | h += 1 53 | } 54 | H 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/vecmath/DoubleMath.scala: -------------------------------------------------------------------------------- 1 | package org.villane.vecmath 2 | 3 | /** 4 | * Mirrors FloatMath, use when replacing Float with Double 5 | */ 6 | trait DoubleMath { 7 | type Float = Double 8 | implicit def double2float(d: Double) = d.toFloat 9 | 10 | final val Pi = Math.Pi 11 | final val π = Pi 12 | 13 | final val Infinity = Double.PositiveInfinity 14 | final val ∞ = Infinity 15 | 16 | // Max/min rewritten here because for some reason Math.max/min 17 | // can run absurdly slow for such simple functions... 18 | // TODO: profile, see if this just seems to be the case or is actually causing issues... 19 | final def max(a: Double, b: Double) = if (a > b) a else b 20 | final def max(as: Double*): Double = as reduceLeft max 21 | final def min(a: Double, b: Double) = if (a < b) a else b 22 | final def min(as: Double*): Double = as reduceLeft min 23 | final def clamp(a: Double, low: Double, high: Double) = 24 | if (a < low) low 25 | else if (a > high) high 26 | else a 27 | 28 | final def sqrt(a: Double) = Math.sqrt(a) 29 | final def √(a: Double) = sqrt(a) 30 | 31 | final def sin(a: Double) = Math.sin(a) 32 | final def cos(a: Double) = Math.cos(a) 33 | final def tan(a: Double) = Math.tan(a) 34 | final def asin(a: Double) = Math.asin(a) 35 | final def acos(a: Double) = Math.acos(a) 36 | final def atan(a: Double) = Math.atan(a) 37 | final def atan2(a: Double, b: Double) = Math.atan2(a, b) 38 | 39 | final def pow(a: Double, b: Double) = Math.pow(a, b) 40 | } 41 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/vecmath/Preamble.scala: -------------------------------------------------------------------------------- 1 | package org.villane.vecmath 2 | 3 | /** 4 | * The vecmath package includes geometry classes useful for 2D graphics and 5 | * physics engines. 6 | * 7 | * This preamble contains implicit conversions from tuples to vectors and matrices 8 | * and extension methods for Float. 9 | * 10 | * Vector2 is an immutable implementation of a 2D vector of two floats 11 | * Matrix22 is an immutable implementation of a 2D matrix of two vectors 12 | * Transform2 is a transformation, consisting of a point (vector) and it's rotation (matrix) 13 | * MathUtil contains useful math with floats. 14 | * FloatExtensions allows some operations to be used with the float on the left side. 15 | */ 16 | object Preamble extends FloatMath with CommonMath { 17 | implicit def tuple2fToVector2(xy: (Float, Float)) = Vector2(xy._1, xy._2) 18 | implicit def tuple2vector2fToMatrix22(m: (Vector2, Vector2)) = Matrix22(m._1, m._2) 19 | implicit def floatToFloatExtensions(a: Float) = new FloatExtensions(a) 20 | 21 | def min(a: Vector2, b: Vector2): Vector2 = Vector2(min(a.x, b.x), min(a.y, b.y)) 22 | def min(a: Vector2, b: Vector2, c: Vector2, d: Vector2): Vector2 = Vector2( 23 | min(a.x, b.x, c.x, d.x), 24 | min(a.y, b.y, c.y, d.y) 25 | ) 26 | def max(a: Vector2, b: Vector2): Vector2 = Vector2(max(a.x, b.x), max(a.y, b.y)) 27 | def max(a: Vector2, b: Vector2, c: Vector2, d: Vector2): Vector2 = Vector2( 28 | max(a.x, b.x, c.x, d.x), 29 | max(a.y, b.y, c.y, d.y) 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/joints/GearJointDef.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics.joints 2 | 3 | /** 4 | * Gear joint definition. This definition requires two existing 5 | * revolute or prismatic joints (any combination will work). 6 | * The provided joints must attach a dynamic body to a static body. 7 | * 8 | * A gear joint is used to connect two joints together. Either joint 9 | * can be a revolute or prismatic joint. You specify a gear ratio 10 | * to bind the motions together: 11 | * coordinate1 + ratio * coordinate2 = constant 12 | * The ratio can be negative or positive. If one joint is a revolute joint 13 | * and the other joint is a prismatic joint, then the ratio will have units 14 | * of length or units of 1/length. 15 | *
Warning: The revolute and prismatic joints must be attached to 16 | * fixed bodies (which must be body1 on those joints). 17 | */ 18 | class GearJointDef extends JointDef { 19 | /** The first revolute/prismatic joint attached to the gear joint. */ 20 | private[this] var _joint1: Joint = null 21 | def joint1 = _joint1 22 | def joint1_=(joint: Joint) = { 23 | _joint1 = joint 24 | body1 = joint.body2 25 | } 26 | 27 | /** The second revolute/prismatic joint attached to the gear joint. */ 28 | private[this] var _joint2: Joint = null 29 | def joint2 = _joint2 30 | def joint2_=(joint: Joint) = { 31 | _joint2 = joint 32 | body2 = joint.body2 33 | } 34 | 35 | /** The gear ratio. @see GearJoint for explanation. */ 36 | var ratio = 1f 37 | } 38 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/BodyDef.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics 2 | 3 | import vecmath.Vector2 4 | import shapes.Mass 5 | 6 | class BodyDef { 7 | var userData: AnyRef = null 8 | var mass = Mass.Zero 9 | var pos = Vector2.Zero 10 | var angle = 0f 11 | 12 | /** 13 | * Linear damping is use to reduce the linear velocity. The damping parameter 14 | * can be larger than 1.0f but the damping effect becomes sensitive to the 15 | * time step when the damping parameter is large. 16 | */ 17 | var linearDamping = 0f 18 | 19 | /** 20 | * Angular damping is use to reduce the angular velocity. The damping parameter 21 | * can be larger than 1.0f but the damping effect becomes sensitive to the 22 | * time step when the damping parameter is large. 23 | */ 24 | var angularDamping = 0f 25 | 26 | /** 27 | * Set this flag to false if this body should never fall asleep. Note that 28 | * this increases CPU usage. 29 | */ 30 | var allowSleep = true 31 | 32 | /** Is this body initially sleeping? */ 33 | var isSleeping = false 34 | 35 | /** Should this body be prevented from rotating? Useful for characters. */ 36 | var fixedRotation = false 37 | 38 | /** 39 | * Is this a fast moving body that should be prevented from tunneling through 40 | * other moving bodies? Note that all bodies are prevented from tunneling through 41 | * static bodies. 42 | *

Warning: You should use this flag sparingly since it increases processing time. 43 | */ 44 | var isBullet = false 45 | 46 | } 47 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/controllers/TensorDampingController.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics.controllers 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | 6 | /** 7 | * Applies top down linear damping to the controlled bodies 8 | * The damping is calculated by multiplying velocity by a matrix in local co-ordinates. 9 | * Some examples: 10 | * 11 | * |-a 0| Standard isotropic damping with strength a 12 | * | 0 -a| 13 | * 14 | * | 0 a| Electron in fixed field - a force at right angles to velocity with proportional magnitude 15 | * |-a 0| 16 | * 17 | * |-a 0| Differing x and y damping. Useful e.g. for top-down wheels 18 | * | 0 -b| 19 | * 20 | */ 21 | abstract class TensorDampingController extends Controller { 22 | /** Tensor to use in damping model. Tensor means Matrix here. */ 23 | var T = Matrix22.Zero 24 | 25 | /** 26 | * Set this to a positive number to clamp the maximum amount of damping done. 27 | * 28 | * Typically one wants maxTimestep to be 1/(max eigenvalue of T), 29 | * so that damping will never cause something to reverse direction 30 | */ 31 | var maxTimestep = 0f 32 | 33 | def step(step: TimeStep): Unit = { 34 | var timestep = step.dt 35 | if (timestep <= Settings.Epsilon) 36 | return 37 | if (timestep > maxTimestep && maxTimestep > 0) 38 | timestep = maxTimestep 39 | 40 | forAwakeBodies { body => 41 | val damping = body.toWorldVector(T * body.toLocalVector(body.linearVelocity)) 42 | body.linearVelocity += timestep * damping 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/scenes/Gears.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.testbed.scenes 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | import dsl.DSL._ 6 | import shapes._ 7 | 8 | object Gears extends TestbedScene { 9 | 10 | def createScene(implicit world: dynamics.World) { 11 | val ground = body { 12 | pos(0.0f, -10.0f) 13 | box(50.0f, 10.0f) 14 | } 15 | 16 | val circle1 = circle(1) density 5 define 17 | val r1 = circle1.shapeDef.asInstanceOf[CircleDef].radius 18 | val circle2 = circle(2) density 5 define 19 | val r2 = circle1.shapeDef.asInstanceOf[CircleDef].radius 20 | 21 | val box1 = box(0.5f, 5.0f) density 5 22 | 23 | val body1 = body { 24 | pos(-3.0f, 12.0f) 25 | fixture(circle1) 26 | massFromShapes 27 | } 28 | 29 | val rev1 = joint( 30 | revolute(ground -> body1) anchor(body1.pos) 31 | ) 32 | 33 | val body2 = body { 34 | pos(0.0f, 12.0f) 35 | fixture(circle2) 36 | massFromShapes 37 | } 38 | 39 | val rev2 = joint( 40 | revolute(ground -> body2) anchor body2.pos 41 | ) 42 | 43 | val body3 = body { 44 | pos(2.5f, 12.0f) 45 | fixture(box1) 46 | massFromShapes 47 | } 48 | 49 | val prism = joint( 50 | prismatic(ground -> body3) 51 | anchor(body3.pos) 52 | axis(Vector2.YUnit) 53 | lowerTranslation -5.0f 54 | upperTranslation 5.0f 55 | enableLimit true 56 | ) 57 | 58 | joint( 59 | gear(rev1 -> rev2) ratio(r2 / r1) 60 | ) 61 | 62 | joint( 63 | gear(rev2 -> prism) ratio(-1 / r2) 64 | ) 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/vecmath/FloatMath.scala: -------------------------------------------------------------------------------- 1 | package org.villane.vecmath 2 | 3 | /** 4 | * A trait implementing math functions with Float signatures. 5 | * 6 | * Replacable with DoubleMath. 7 | * 8 | * TODO: for Scala 2.8, make FloatMath and DoubleMath extend 9 | * MathFunctions[@specialized T] to enforce a common interface. 10 | */ 11 | trait FloatMath { 12 | final val Pi = Math.Pi.toFloat 13 | final val π = Pi 14 | 15 | final val Infinity = Float.PositiveInfinity 16 | final val ∞ = Infinity 17 | 18 | // Max/min rewritten here because for some reason Math.max/min 19 | // can run absurdly slow for such simple functions... 20 | // TODO: profile, see if this just seems to be the case or is actually causing issues... 21 | final def max(a: Float, b: Float) = if (a > b) a else b 22 | final def max(as: Float*): Float = as reduceLeft max 23 | final def min(a: Float, b: Float) = if (a < b) a else b 24 | final def min(as: Float*): Float = as reduceLeft min 25 | final def clamp(a: Float, low: Float, high: Float) = 26 | if (a < low) low 27 | else if (a > high) high 28 | else a 29 | 30 | final def sqrt(a: Float) = Math.sqrt(a).toFloat 31 | final def √(a: Float) = sqrt(a) 32 | 33 | final def sin(a: Float) = Math.sin(a).toFloat 34 | final def cos(a: Float) = Math.cos(a).toFloat 35 | final def tan(a: Float) = Math.tan(a).toFloat 36 | final def asin(a: Float) = Math.asin(a).toFloat 37 | final def acos(a: Float) = Math.acos(a).toFloat 38 | final def atan(a: Float) = Math.atan(a).toFloat 39 | final def atan2(y: Float, x: Float) = Math.atan2(y, x).toFloat 40 | 41 | final def pow(a: Float, b: Float) = Math.pow(a, b).toFloat 42 | } 43 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/joints/PrismaticJointDef.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics.joints 2 | 3 | import vecmath.Vector2 4 | 5 | class PrismaticJointDef extends JointDef { 6 | /** The local anchor point relative to body1's origin. */ 7 | var localAnchor1 = Vector2.Zero 8 | 9 | /** The local anchor point relative to body2's origin. */ 10 | var localAnchor2 = Vector2.Zero 11 | 12 | /** The local translation axis in body1. */ 13 | var localAxis1 = Vector2.Zero 14 | 15 | /** The constrained angle between the bodies: body2_angle - body1_angle. */ 16 | var referenceAngle = 0f 17 | 18 | /** A flag to enable joint limits. */ 19 | var enableLimit = false 20 | 21 | /** The lower translation limit, usually in meters. */ 22 | var lowerTranslation = 0f 23 | 24 | /** The upper translation limit, usually in meters. */ 25 | var upperTranslation = 0f 26 | 27 | /** A flag to enable the joint motor. */ 28 | var enableMotor = false 29 | 30 | /** The desired motor speed. Usually in radians per second. */ 31 | var motorSpeed = 0f 32 | 33 | /** 34 | * The maximum motor torque used to achieve the desired motor speed. 35 | * Usually in N-m. 36 | */ 37 | var maxMotorForce = 0f 38 | 39 | /** 40 | * Initialize the bodies, anchors, and reference angle using the world anchor. 41 | */ 42 | def this(b1: Body, b2: Body, anchor: Vector2, axis: Vector2) { 43 | this() 44 | body1 = b1; 45 | body2 = b2; 46 | localAnchor1 = body1.toLocalPoint(anchor) 47 | localAnchor2 = body2.toLocalPoint(anchor) 48 | referenceAngle = body2.angle - body1.angle 49 | localAxis1 = body1.toLocalVector(axis) 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /scalabox2d-svg/src/main/scala/org/villane/box2d/svg/PureScalaSVGParser.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.svg 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | import shapes._ 6 | import dynamics._ 7 | import scala.xml.XML 8 | import dsl.DSL._ 9 | 10 | class PureScalaSVGParser(fileName: String, scale: Float, gravity: Vector2) { 11 | implicit def stringExt(s: String) = new { 12 | def toScalar = s.toFloat / scale 13 | } 14 | 15 | def parse = { 16 | val svg = XML.load("C:/drawing.svg") 17 | val aabb = { 18 | val width = (svg \ "@width").text.toScalar 19 | val height = (svg \ "@width").text.toScalar 20 | AABB(Vector2.Zero, (width, height)) 21 | } 22 | implicit val world = new World(aabb, gravity, true) 23 | for(g <- svg \\ "g") { 24 | for (r <- g \ "rect") body { 25 | val width = (r \ "@width").text.toScalar 26 | val height = (r \ "@height").text.toScalar 27 | pos((r \ "@x").text.toScalar + width / 2, 28 | (r \ "@y").text.toScalar + height / 2) 29 | box(width / 2, height / 2) 30 | } 31 | for (path <- g \ "path") { 32 | val sp = "@{http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd}" + _ 33 | 34 | if ("arc" == (path \ sp("type")).text) { 35 | val rx = (path \ sp("rx")).text.toScalar 36 | val ry = (path \ sp("ry")).text.toScalar 37 | if (rx == ry) body { 38 | val cx = (path \ sp("cx")).text.toScalar 39 | val cy = (path \ sp("cy")).text.toScalar 40 | pos(cx, cy) 41 | circle(rx) density 1 42 | massFromShapes 43 | } 44 | } 45 | } 46 | } 47 | world 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/shapes/Segment.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.shapes 2 | 3 | import vecmath._ 4 | 5 | object SegmentCollide { 6 | val Miss = SegmentCollide(SegmentCollideResult.Miss, 0, Vector2.Zero) 7 | def hit(lambda: Float, normal: Vector2) = 8 | SegmentCollide(SegmentCollideResult.Hit, lambda, normal) 9 | def startsInside(lambda: Float, normal: Vector2) = 10 | SegmentCollide(SegmentCollideResult.StartsInside, lambda, normal) 11 | } 12 | 13 | case class SegmentCollide( 14 | result: SegmentCollideResult, 15 | lambda: Float, 16 | normal: Vector2 17 | ) 18 | 19 | sealed trait SegmentCollideResult 20 | 21 | object SegmentCollideResult { 22 | object StartsInside extends SegmentCollideResult 23 | object Miss extends SegmentCollideResult 24 | object Hit extends SegmentCollideResult 25 | } 26 | 27 | /** A line segment */ 28 | class Segment(val p1: Vector2, val p2: Vector2) { 29 | /** Ray cast against this segment with another segment. */ 30 | def testSegment(segment: Segment, maxLambda: Float): SegmentCollide = { 31 | val s = segment.p1 32 | val r = segment.p2 - s 33 | val d = p2 - p1 34 | val n = d cross 1f 35 | 36 | val slop = 100f * Settings.Epsilon 37 | val denom = -(r dot n) 38 | 39 | // Cull back facing collision and ignore parallel segments. 40 | if (denom > slop) { 41 | // Does the segment intersect the infinite line associated with this segment? 42 | val b = s - p1 43 | val a = b dot n 44 | 45 | if (0f <= a && a <= maxLambda * denom) { 46 | val mu2 = -r.x * b.y + r.y * b.x 47 | 48 | // Does the segment intersect this segment? 49 | if (-slop * denom <= mu2 && mu2 <= denom * (1f + slop)) 50 | return SegmentCollide.hit(a / denom, n.normalize) 51 | } 52 | } 53 | SegmentCollide.Miss 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /scalabox2d-testbed/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.villane.box2d 6 | scalabox2d-parent 7 | 2.0.1-SNAPSHOT 8 | 9 | org.villane.box2d 10 | scalabox2d-testbed 11 | ScalaBox2D - 2D Physics Engine for Games (Test Bed) 12 | 2.0.1-SNAPSHOT 13 | 14 | 15 | slick-repo 16 | http://slick.cokeandcode.com/mavenrepo/ 17 | 18 | 19 | villane.org 20 | http://villane.org/mavenrepo/ 21 | 22 | 23 | 24 | 25 | org.villane.box2d 26 | scalabox2d 27 | 2.0.1-SNAPSHOT 28 | 29 | 30 | org.villane.box2d 31 | scalabox2d-svg 32 | 2.0.1-SNAPSHOT 33 | 34 | 35 | slick 36 | slick 37 | 264 38 | 39 | 40 | org.lwjgl 41 | lwjgl 42 | 2.1.0 43 | 44 | 45 | org.lwjgl 46 | lwjgl-util 47 | 2.1.0 48 | 49 | 50 | org.fenggui 51 | fenggui 52 | 648 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/scenes/Circles.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.testbed.scenes 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | import dsl.DSL._ 6 | 7 | object Circles extends TestbedScene { 8 | 9 | def createScene(implicit world: dynamics.World) { 10 | val ground = world.groundBody 11 | 12 | body { 13 | // Ground 14 | pos(0.0f, -10.0f) 15 | box(50.0f, 10.0f) friction 1.0f 16 | } 17 | 18 | val leftWall = body { 19 | pos(53.0f, 25.0f) 20 | box(3.0f,50.0f) friction 1.0f 21 | } 22 | val rightWall = body { 23 | pos(-53.0f, 25.0f) 24 | box(3.0f,50.0f) friction 1.0f 25 | } 26 | // Corners 27 | body { 28 | pos(-40f, 0.0f) 29 | angle(-π/4) 30 | box(20.0f, 3.0f) friction 1.0f 31 | } 32 | body { 33 | pos(40f, 0.0f) 34 | angle(π/4) 35 | box(20.0f, 3.0f) friction 1.0f 36 | } 37 | 38 | val bd = body { 39 | pos(0.0f, 10.0f) 40 | val numPieces = 5 41 | val radius = 6f 42 | for (i <- 0 until numPieces) { 43 | val x = radius * cos(2 * π * (i.toFloat / (numPieces))) 44 | val y = radius * sin(2 * π * (i.toFloat / (numPieces))) 45 | circle((x, y), 1.2f) density 25.0f friction 0.1f restitution 0.9f 46 | } 47 | massFromShapes 48 | } 49 | 50 | joint ( 51 | revolute(bd -> ground) 52 | anchor bd.pos 53 | motorSpeed π 54 | maxMotorTorque 1000000.0f 55 | enableMotor true 56 | ) 57 | 58 | val loadSize = 45 59 | for (j <- 0 until 10) { 60 | for (i <- 0 until loadSize) body { 61 | pos(-45f + 2*i, 50f + j) 62 | (circle(1.0f+(if (i%2==0) 1.0f else -1.0f)*.5f*(i.toFloat/loadSize)) 63 | density 5.0f 64 | friction 0.1f 65 | restitution 0.5f 66 | ) 67 | massFromShapes 68 | } 69 | } 70 | } 71 | 72 | } -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/collision/Collider.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.collision 2 | 3 | import vecmath._ 4 | import shapes._ 5 | 6 | /** 7 | * Companion object for performing collision detection without knowing the 8 | * concrete shape or collider types. 9 | */ 10 | object Collider { 11 | 12 | /** 13 | * Collide two shapes for which a collider exists, given their world transforms. 14 | */ 15 | def collide(shape1: Shape, t1: Transform2, shape2: Shape, t2: Transform2): 16 | Option[Manifold] = (shape1, shape2) match { 17 | case (s1: Circle, s2: Circle) => 18 | CircleCollider.collide(s1, t1, s2, t2) 19 | case (s1: Polygon, s2: Circle) => 20 | PolygonCircleCollider.collide(s1, t1, s2, t2) 21 | case (s1: Circle, s2: Polygon) => 22 | PolygonCircleCollider.collide(s2, t2, s1, t1) 23 | case (s1: Polygon, s2: Polygon) => 24 | PolygonCollider.collide(s1, t1, s2, t2) 25 | case (s1: Edge, s2: Circle) => 26 | EdgeCircleCollider.collide(s1, t1, s2, t2) 27 | case (s1: Circle, s2: Edge) => 28 | EdgeCircleCollider.collide(s2, t2, s1, t1) 29 | case (s1: Polygon, s2: Edge) => 30 | PolygonEdgeCollider.collide(s1, t1, s2, t2) 31 | case (s1: Edge, s2: Polygon) => 32 | PolygonEdgeCollider.collide(s2, t2, s1, t1) 33 | case (s1, s2) => 34 | throw new IllegalArgumentException( 35 | "No collider exists for shapes (" + 36 | s1.getClass.getSimpleName + ", " + 37 | s2.getClass.getSimpleName + ")") 38 | } 39 | 40 | } 41 | 42 | /** 43 | * Base trait for specific shape colliders. 44 | * 45 | * A collider returns Some(Manifold(...)) on contact or None on no contact. 46 | */ 47 | trait Collider[S1 <: Shape, S2 <: Shape] { 48 | 49 | /** 50 | * Collide two shapes, given their world transforms. 51 | */ 52 | def collide(shape1: S1, t1: Transform2, 53 | shape2: S2, t2: Transform2): Option[Manifold] 54 | 55 | } 56 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/collision/EdgeCircleCollider.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.collision 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | import shapes._ 6 | import collision._ 7 | import Settings.Epsilon 8 | 9 | object EdgeCircleCollider extends Collider[Edge, Circle] { 10 | 11 | def collide(edge: Edge, xf1: Transform2, 12 | circle: Circle, xf2: Transform2): Option[Manifold] = { 13 | val c = xf2 * circle.pos 14 | val cLocal = xf1 ** c 15 | 16 | val n = edge.normal 17 | val v1 = edge.v1 18 | val v2 = edge.v2 19 | val radius = circle.radius 20 | 21 | var separation = 0f 22 | 23 | var d = cLocal - v1 24 | 25 | var dirDist = d dot edge.direction 26 | if (dirDist <= 0) { 27 | if ((d dot edge.corner1Dir) < 0) return None 28 | d = c - xf1 * v1 29 | } else if (dirDist >= edge.length) { 30 | d = c - v2 31 | if ((d dot edge.corner2Dir) > 0) return None 32 | d = c - xf1 * v2 33 | } else { 34 | separation = d dot n 35 | if (separation > radius || separation < -radius) return None 36 | separation -= radius 37 | 38 | val id = ContactID.Zero 39 | val normal = xf1.rot * n 40 | val pos = c - normal * radius 41 | val points = new Array[ManifoldPoint](1) 42 | points(0) = ManifoldPoint(xf1 ** pos, xf2 ** pos, separation, id) 43 | return Some(Manifold(points, normal)) 44 | } 45 | 46 | val distSqr = d dot d 47 | if (distSqr > radius * radius) return None 48 | 49 | val normal = if (distSqr < Epsilon) { 50 | separation = -radius 51 | xf1.rot * n 52 | } else { 53 | val len = d.length 54 | separation = len - radius 55 | d / len 56 | } 57 | 58 | val id = ContactID.Zero 59 | val pos = c - normal * radius 60 | val points = new Array[ManifoldPoint](1) 61 | points(0) = ManifoldPoint(xf1 ** pos, xf2 ** pos, separation, id) 62 | Some(Manifold(points, normal)) 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/joints/Joint.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics.joints 2 | 3 | import vecmath._ 4 | import dynamics._ 5 | 6 | object Joint { 7 | def create(defn: JointDef): Joint = defn match { 8 | case d: DistanceJointDef => new DistanceJoint(d) 9 | case d: RevoluteJointDef => new RevoluteJoint(d) 10 | case d: PrismaticJointDef => new PrismaticJoint(d) 11 | case d: GearJointDef => new GearJoint(d) 12 | case d: PointingDeviceJointDef => new PointingDeviceJoint(d) 13 | } 14 | } 15 | 16 | abstract class Joint(defn: JointDef) { 17 | val body1 = defn.body1 18 | val body2 = defn.body2 19 | val collideConnected = defn.collideConnected 20 | val userData = defn.userData 21 | 22 | /** Node for connecting bodies. */ 23 | val node1 = if (body2 != null) JointEdge(body2, this) else null 24 | 25 | /** Node for connecting bodies. */ 26 | val node2 = if (body1 != null) JointEdge(body1, this) else null 27 | 28 | var islandFlag = false 29 | var invDt = 0f 30 | 31 | init 32 | def init { 33 | if (body1 != null) body1.jointList = node1 :: body1.jointList 34 | if (body2 != null) body2.jointList = node2 :: body2.jointList 35 | } 36 | 37 | /** Get the anchor point on body1 in world coordinates. */ 38 | def anchor1: Vector2 39 | /** Get the anchor point on body2 in world coordinates. */ 40 | def anchor2: Vector2 41 | /** Get the anchor point on body1 in local coordinates. */ 42 | def localAnchor1: Vector2 43 | /** Get the anchor point on body2 in local coordinates. */ 44 | def localAnchor2: Vector2 45 | /** Get the reaction force on body2 at the joint anchor. */ 46 | def reactionForce: Vector2 47 | /** Get the reaction torque on body2. */ 48 | def reactionTorque: Float 49 | 50 | def initVelocityConstraints(step: TimeStep) 51 | def solveVelocityConstraints(step: TimeStep) 52 | 53 | def initPositionConstraints() {} 54 | /** This returns true if the position errors are within tolerance. */ 55 | def solvePositionConstraints(): Boolean 56 | } 57 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/util/Timing.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.util 2 | 3 | object Timing { 4 | def currentTime = System.currentTimeMillis 5 | def nanoTime = System.nanoTime 6 | 7 | lazy val collectedTimes = new collection.mutable.HashMap[String, (Long, Int)] 8 | 9 | def repeat(times: Long)(block: => Unit) = { 10 | assert(times >= 0) 11 | var t = times; 12 | while(t > 0) { 13 | block 14 | t -= 1 15 | } 16 | } 17 | 18 | def repeatWithIndex(times: Long)(block: Long => Unit) = { 19 | assert(times >= 0) 20 | var t = 0; 21 | while(t < times) { 22 | block(t) 23 | t += 1 24 | } 25 | } 26 | 27 | def time[T](name: String)(block: => T) = { 28 | val start = currentTime 29 | try { 30 | block 31 | } finally { 32 | val diff = currentTime - start 33 | println("# Block \"" + name +"\" completed, time taken: " + diff + " ms (" + diff / 1000.0 + " s)") 34 | } 35 | } 36 | 37 | def timeNano[T](name: String)(block: => T) = { 38 | val start = nanoTime 39 | try { 40 | block 41 | } finally { 42 | val diff = nanoTime - start 43 | println("# Block \"" + name +"\" completed, time taken: " + diff + " ns (" + diff / 1000000.0 + " ms)") 44 | } 45 | } 46 | 47 | def collectNanoTime[T](name: String)(block: => T) = { 48 | val start = nanoTime 49 | try { 50 | block 51 | } finally { 52 | val diff = nanoTime - start 53 | val old = collectedTimes.getOrElseUpdate(name, (0,0)) 54 | collectedTimes(name) = (old._1 + diff, old._2 + 1) 55 | } 56 | } 57 | 58 | def printCollectedTimes() { 59 | for((name, time) <- collectedTimes) { 60 | println("# Blocks \"" + name +"\" completed, time taken: " + time._1 + " ns (" + time._1 / 1000000.0 + " ms)") 61 | println("# Blocks \"" + name +"\" executions: " + time._2) 62 | val avg = time._1 / time._2 63 | println("# Blocks \"" + name +"\" avg: " + avg + " ns (" + avg / 1000000.0 + " ms)") 64 | } 65 | collectedTimes.clear 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/controllers/Controller.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics.controllers 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | 6 | import collection.Set 7 | 8 | /** 9 | * Base trait for controllers. 10 | * Controllers are a convience for encapsulating common per-step functionality. 11 | */ 12 | trait Controller { 13 | 14 | /** The list of bodies that this controller applies to. */ 15 | def bodies: Set[Body] 16 | 17 | /** This is called on each world step after updating contacts. */ 18 | def step(step: TimeStep) 19 | 20 | /** Iterate over awake bodies. */ 21 | protected def forAwakeBodies(f: Body => Unit) = 22 | for (body <- bodies) if (!body.isSleeping) f(body) 23 | 24 | } 25 | 26 | /** 27 | * Controllers should mix this in if they don't have their own body list but 28 | * want to rely on contacts generated by 29 | */ 30 | trait SensorManagedBodies extends Controller { 31 | 32 | // TODO make this immutable when Scala supports trait params 33 | private[this] var _sensor: Body = null 34 | /** 35 | * The sensor body that owns this controller. If the sensor is destoryed, 36 | * the controller is destroyed too. 37 | */ 38 | def sensor = _sensor 39 | def sensor_=(body: Body) = { 40 | if (_sensor != null) _sensor.dependentControllers -= this 41 | _sensor = body 42 | if (_sensor != null) _sensor.dependentControllers += this 43 | } 44 | 45 | def bodies: Set[Body] = 46 | if (_sensor == null) 47 | Set() 48 | else 49 | Set() ++ _sensor.contactList filter (!_.contact.manifolds.isEmpty) map (_.other) 50 | 51 | } 52 | 53 | /** 54 | * Controllers should mix this in if they manage their own list of bodies. 55 | */ 56 | trait SelfManagedBodies extends Controller { 57 | 58 | private[this] val _bodies = collection.mutable.Set[Body]() 59 | 60 | def bodies: Set[Body] = _bodies 61 | 62 | def addBody(body: Body) = { 63 | _bodies += body 64 | body.managingControllers += this 65 | } 66 | 67 | def removeBody(body: Body) = { 68 | _bodies -= body 69 | body.managingControllers -= this 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/scenes/Pistons.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.testbed.scenes 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | import dsl.DSL._ 6 | 7 | object Pistons extends TestbedScene { 8 | val Bullets = false 9 | 10 | def createScene(implicit world: dynamics.World) { 11 | var prevBody = world.groundBody 12 | 13 | // Define crank. 14 | var bd = body { 15 | pos(0.0f, 7.0f) 16 | box(0.5f, 2.0f) density 1.0f 17 | massFromShapes 18 | } 19 | 20 | val joint1 = joint( 21 | revolute(prevBody -> bd) 22 | anchor(0.0f, 5.0f) 23 | motorSpeed 1.0f * 3.1415f 24 | maxMotorTorque Float.MaxValue 25 | enableMotor true 26 | ) 27 | 28 | prevBody = bd 29 | 30 | // Define follower. 31 | bd = body { 32 | pos(0.0f, 13.0f) 33 | box(0.5f, 4.0f) density 1.0f 34 | massFromShapes 35 | } 36 | 37 | joint( 38 | revolute(prevBody -> bd) 39 | anchor(0.0f, 9.0f) 40 | enableMotor false 41 | ) 42 | 43 | prevBody = bd 44 | 45 | // Define piston 46 | bd = body { 47 | pos(0.0f, 17.0f) 48 | box(5.0f, 1.5f) density 1.0f 49 | massFromShapes 50 | } 51 | 52 | joint(revolute(prevBody -> bd) anchor(0.0f, 17.0f)) 53 | 54 | val joint2 = joint( 55 | prismatic(world.groundBody -> bd) 56 | anchor(0.0f, 17.0f) 57 | axis(0.0f, 1.0f) 58 | //maxMotorForce Float.MaxValue 59 | enableMotor false 60 | ) 61 | 62 | // Create a payload 63 | for (i <- 0 until 100) body { 64 | pos(-1.0f, 23.0f + i) 65 | box(0.4f,0.3f) density 0.1f 66 | bullet(Bullets) 67 | massFromShapes 68 | } 69 | 70 | for (i <- 0 until 100) body { 71 | pos(1.0f, 23.0f + i) 72 | circle(0.36f) density 2.0f 73 | bullet(Bullets) 74 | massFromShapes 75 | } 76 | 77 | body { 78 | pos(6.1f, 50.0f) 79 | box(1.0f, 100.0f) density 0.0f friction 0.0f 80 | } 81 | body { 82 | pos(-6.1f, 50.0f) 83 | box(1.0f, 100.0f) density 0.0f friction 0.0f 84 | } 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/joints/RevoluteJointDef.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics.joints 2 | 3 | import vecmath.Vector2 4 | 5 | /** 6 | * Revolute joint definition. This requires defining an 7 | * anchor point where the bodies are joined. The definition 8 | * uses local anchor points so that the initial configuration 9 | * can violate the constraint slightly. You also need to 10 | * specify the initial relative angle for joint limits. This 11 | * helps when saving and loading a game. 12 | * The local anchor points are measured from the body's origin 13 | * rather than the center of mass because: 14 | * 1. you might not know where the center of mass will be. 15 | * 2. if you add/remove shapes from a body and recompute the mass, 16 | * the joints will be broken. 17 | */ 18 | class RevoluteJointDef extends JointDef { 19 | /** The local anchor point relative to body1's origin. */ 20 | var localAnchor1 = Vector2.Zero 21 | 22 | /** The local anchor point relative to body2's origin. */ 23 | var localAnchor2 = Vector2.Zero 24 | 25 | /** The body2 angle minus body1 angle in the reference state (radians). */ 26 | var referenceAngle = 0f 27 | 28 | /** A flag to enable joint limits. */ 29 | var enableLimit = false 30 | 31 | /** The lower angle for the joint limit (radians). */ 32 | var lowerAngle = 0f 33 | 34 | /** The upper angle for the joint limit (radians). */ 35 | var upperAngle = 0f 36 | 37 | /** A flag to enable the joint motor. */ 38 | var enableMotor = false 39 | 40 | /** The desired motor speed. Usually in radians per second. */ 41 | var motorSpeed = 0f 42 | 43 | /** 44 | * The maximum motor torque used to achieve the desired motor speed. 45 | * Usually in N-m. 46 | */ 47 | var maxMotorTorque = 0f 48 | 49 | /** 50 | * Initialize the bodies, anchors, and reference angle using the world anchor. 51 | */ 52 | def this(b1: Body, b2: Body, anchor: Vector2) { 53 | this() 54 | body1 = b1; 55 | body2 = b2; 56 | localAnchor1 = body1.toLocalPoint(anchor) 57 | localAnchor2 = body2.toLocalPoint(anchor) 58 | referenceAngle = body2.angle - body1.angle 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/Sweep.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | import Settings.Epsilon 6 | 7 | /** 8 | * Primarily for internal use. 9 | *

10 | * Describes the motion of a body/shape for TOI computation. 11 | * Shapes are defined with respect to the body origin, which may 12 | * not coincide with the center of mass. However, to support dynamics 13 | * we must interpolate the center of mass position. 14 | * 15 | * TODO move out of the vecmath package -- this is pretty much tied to the physics engine. 16 | */ 17 | class Sweep { 18 | /** Local center of mass position */ 19 | var localCenter = Vector2.Zero 20 | /** Center world positions */ 21 | var c0, c = Vector2.Zero 22 | /** World angles */ 23 | var a0, a = 0f 24 | /** Time interval = [t0,1], where t0 is in [0,1] */ 25 | var t0 = 0f 26 | 27 | override def toString() = { 28 | var s = "Sweep:\nlocalCenter: "+localCenter+"\n"; 29 | s += "c0: "+c0+", c: "+c+"\n"; 30 | s += "a0: "+a0+", a: "+a+"\n"; 31 | s += "t0: "+t0+"\n"; 32 | s 33 | } 34 | 35 | /** 36 | * Get the interpolated transform at a specific time. 37 | * @param t the normalized time in [0,1]. 38 | */ 39 | def getTransform(t: Float) = { 40 | // center = p + R * localCenter 41 | val xf = if (1f - t0 > Settings.Epsilon) { 42 | val alpha = (t - t0) / (1f - t0) 43 | val oneLessAlpha = 1f - alpha 44 | //val pos = c0 * (1f - alpha) + c * alpha 45 | val posx = c0.x * oneLessAlpha + c.x * alpha 46 | val posy = c0.y * oneLessAlpha + c.y * alpha 47 | val angle = oneLessAlpha * a0 + alpha * a 48 | Transform2(Vector2(posx, posy), angle) 49 | } else { 50 | Transform2(c, a) 51 | } 52 | 53 | // Shift to origin 54 | Transform2(xf.pos - (xf.rot * localCenter), xf.rot) 55 | } 56 | 57 | /** 58 | * Advance the sweep forward, yielding a new initial state. 59 | * @param t the new initial time. 60 | */ 61 | def advance(t: Float) { 62 | if (t0 < t && 1.0f - t0 > Epsilon) { 63 | val alpha = (t - t0) / (1.0f - t0) 64 | c0 = c0 * (1.0f - alpha) + c * alpha 65 | a0 = (1.0f - alpha) * a0 + alpha * a 66 | t0 = t 67 | } 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/shapes/Shape.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.shapes 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | import collision._ 6 | import dynamics._ 7 | 8 | object Shape { 9 | def create(defn: ShapeDef) = defn match { 10 | case d: CircleDef => new Circle(d.pos, d.radius) 11 | case d: PolygonDef => new Polygon(d) 12 | case d: EdgeDef => new Edge(d.v1, d.v2) 13 | case d: EdgeChainDef => 14 | throw new IllegalArgumentException("Turn edge chain into separate edges when creating shapes!") 15 | case d: PointDef => new Point(d.pos, d.mass) 16 | } 17 | } 18 | 19 | /** 20 | * A shape is used for collision detection. 21 | */ 22 | abstract class Shape { 23 | /** Sweep radius relative to the parent body's center of mass. */ 24 | def radius: Float 25 | 26 | /** 27 | * Test a point for containment in this shape. This only works for convex shapes. 28 | * @param t the shape world transform. 29 | * @param p a point in world coordinates. 30 | * @return true if the point is within the shape 31 | */ 32 | def testPoint(t: Transform2, p: Vector2): Boolean 33 | /** 34 | * Perform a ray cast against this shape. 35 | * @param t world transform 36 | /// @param segment defines the begin and end point of the ray cast. 37 | /// @param maxLambda a number typically in the range [0,1]. 38 | * @param lambda returns the hit fraction. You can use this to compute the contact point 39 | /// p = (1 - lambda) * segment.p1 + lambda * segment.p2. 40 | /// @param normal returns the normal at the contact point. If there is no intersection, the normal 41 | /// is not set. 42 | */ 43 | def testSegment(t: Transform2, segment: Segment, maxLambda: Float): SegmentCollide 44 | 45 | def computeAABB(t: Transform2): AABB 46 | def computeMass(density: Float): Mass 47 | /** 48 | * Compute the volume and centroid of this shape intersected with a half plane 49 | * @param normal the surface normal 50 | * @param offset the surface offset along normal 51 | * @param xf the shape transform 52 | * @return the total volume less than offset along normal and the centroid 53 | */ 54 | def computeSubmergedArea(normal: Vector2, offset: Float, t: Transform2): (Float, Vector2) 55 | 56 | def computeSweepRadius(pivot: Vector2): Float 57 | 58 | } 59 | -------------------------------------------------------------------------------- /scalabox2d-testbed/src/test/scala/org/villane/box2d/testbed/HeadlessPerformanceTest.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.testbed 2 | 3 | import vecmath._ 4 | import dynamics._ 5 | import dsl._ 6 | 7 | class HeadlessPerformanceTest(sceneFactory: SceneFactory) { 8 | 9 | val settings = new TestSettings 10 | 11 | def run: Unit = run(false, 500, 0, false) 12 | 13 | def run(warmUp: Boolean, steps: Int, split: Int, waitForKeypress: Boolean) { 14 | //Settings.threadedIslandSolving = true 15 | //Settings.numThreads = 3 16 | if (warmUp) { 17 | for (j <- 1 to 1) { 18 | val world = sceneFactory.create 19 | var i = 0 20 | while (i < steps) { step(world); i += 1 } 21 | } 22 | } 23 | 24 | val world = sceneFactory.create 25 | Vector2.creationCount = 0 26 | //var x = 0 27 | //m_world.bodyList foreach { b => if (!b.isStatic) x += 1 } 28 | //println("bodycount: " + x) 29 | if (waitForKeypress && split == 0) { 30 | println("Attach profiler and press Enter to start test: ") 31 | Console.readLine 32 | } 33 | var start = System.currentTimeMillis 34 | var i = 0 35 | var splitTime = 0L 36 | while (i < steps) { 37 | if (i == split && split > 0) { 38 | if (waitForKeypress) { 39 | println("Attach profiler and press Enter to start test: ") 40 | Console.readLine 41 | } 42 | splitTime = System.currentTimeMillis 43 | } 44 | step(world) 45 | i += 1 46 | } 47 | /*while (m_world.bodyList exists { b => 48 | !b.isSleeping && !b.isStatic 49 | }) { 50 | step 51 | }*/ 52 | val end = System.currentTimeMillis 53 | println((end - start) + " ms") 54 | if (split > 0) { 55 | println(" first " + split + ": " + (splitTime - start) + " ms") 56 | println(" last " + (steps - split) + ": " + (end - splitTime) + " ms") 57 | } 58 | println("vectors created:" + Vector2.creationCount) 59 | IslandSolverWorker.stopWorkers 60 | util.Timing.printCollectedTimes 61 | } 62 | 63 | def step(world: World) { 64 | var timeStep = if (settings.hz > 0.0f) 1.0f / settings.hz else 0.0f 65 | 66 | world.warmStarting = settings.enableWarmStarting 67 | world.positionCorrection = settings.enablePositionCorrection 68 | world.continuousPhysics = settings.enableTOI 69 | 70 | world.step(timeStep, settings.iterationCount) 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/contacts/MultiPointSingleManifoldContact.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics.contacts 2 | 3 | import collision._ 4 | import shapes._ 5 | 6 | class MultiPointSingleManifoldContact[S1 <: Shape, S2 <: Shape]( 7 | f1: Fixture, 8 | f2: Fixture, 9 | collider: Collider[S1, S2] 10 | ) extends SingleManifoldContact(f1, f2, collider) { 11 | 12 | override protected def evaluate(listener: ContactListener, manifold: Option[Manifold]) { 13 | val persisted = Array(false,false) 14 | val oldMH = manifoldHolder 15 | manifoldHolder = manifold 16 | if (manifoldHolder.isDefined) { 17 | val manifold = manifoldHolder.get 18 | // Match old contact ids to new contact ids and copy the 19 | // stored impulses to warm start the solver. 20 | val mpIter = manifold.points.elements 21 | while (mpIter.hasNext) { 22 | val mp = mpIter.next 23 | mp.normalImpulse = 0.0f 24 | mp.tangentImpulse = 0.0f 25 | var found = false 26 | val id = mp.id 27 | 28 | if (oldMH.isDefined) { 29 | val m0 = oldMH.get 30 | var j = 0 31 | val mp0Iter = m0.points.elements 32 | while (mp0Iter.hasNext) { 33 | val mp0 = mp0Iter.next 34 | if (!persisted(j) && !found) { 35 | //val mp0 = m0.points(j) 36 | 37 | if (mp0.id == id) { 38 | persisted(j) = true 39 | mp.normalImpulse = mp0.normalImpulse 40 | mp.tangentImpulse = mp0.tangentImpulse 41 | 42 | // A persistent point. 43 | found = true 44 | 45 | // Report persistent point. 46 | notifyListener(listener, mp, manifold.normal, EventType.Persist) 47 | } 48 | } 49 | j += 1 50 | } 51 | } 52 | 53 | // Report added point. 54 | if (!found) { 55 | notifyListener(listener, mp, manifold.normal, EventType.Add) 56 | } 57 | } 58 | } 59 | if (oldMH.isDefined) { 60 | // Report removed points. 61 | val m0 = oldMH.get 62 | val mp0Iter = m0.points.elements 63 | var i = 0 64 | while (mp0Iter.hasNext) { 65 | val mp = mp0Iter.next 66 | if (!persisted(i)) 67 | notifyListener(listener, mp, m0.normal, EventType.Remove) 68 | i += 1 69 | } 70 | } 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/draw/DebugDrawHandler.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.draw 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | 6 | /** 7 | * Implement this abstract class to allow JBox2d to automatically draw your 8 | * physics for debugging purposes. Not intended to replace your own custom 9 | * rendering routines! 10 | */ 11 | trait DebugDrawHandler { 12 | /// Draw a closed polygon provided in CCW order. 13 | def drawPolygon(vertices: Array[Vector2], color: Color3f) 14 | 15 | /// Draw a solid closed polygon provided in CCW order. 16 | def drawSolidPolygon(vertices: Array[Vector2], color: Color3f) 17 | 18 | /// Draw a circle. 19 | def drawCircle(center: Vector2, radius: Float, color: Color3f) 20 | 21 | /// Draw a solid circle. 22 | def drawSolidCircle(center: Vector2, radius: Float, axis: Vector2, color: Color3f) 23 | 24 | /// Draw a point. 25 | def drawPoint(position: Vector2, f: Float, color: Color3f) 26 | 27 | /// Draw a line segment. 28 | def drawSegment(p1: Vector2, p2: Vector2, color: Color3f) 29 | 30 | /// Draw a transform. Choose your own length scale. 31 | /// @param xf a transform. 32 | def drawTransform(xf: Transform2) 33 | 34 | def drawString(x: Float, y: Float, s: String, color: Color3f) 35 | 36 | //All the following should be overridden if the concrete drawing 37 | //class does any sort of camera movement 38 | 39 | /** 40 | * Stub method to overload for camera movement/zoom. 41 | * @param x - x coordinate of camera 42 | * @param y - y coordinate of camera 43 | * @param scale - zoom factor 44 | */ 45 | def setCamera(x: Float, y: Float, scale: Float) {} 46 | 47 | /** 48 | * @param screenV Screen position 49 | * @return World position 50 | */ 51 | def screenToWorld(screenV: Vector2) = screenV 52 | 53 | /** 54 | * @param screenx Screen x position 55 | * @param screeny Screey y position 56 | * @return World position 57 | */ 58 | final def screenToWorld(screenX: Float, screenY: Float): Vector2 = screenToWorld(Vector2(screenX, screenY)) 59 | 60 | /** 61 | * @param worldV World position 62 | * @return Screen position 63 | */ 64 | def worldToScreen(worldV: Vector2) = worldV 65 | 66 | /** 67 | * @param worldx World x position 68 | * @param worldy World y position 69 | * @return Screen position 70 | */ 71 | final def worldToScreen(worldX: Float, worldY: Float): Vector2 = worldToScreen(Vector2(worldX, worldY)) 72 | 73 | } 74 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/controllers/BuoyancyController.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics.controllers 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | 6 | /** Calculates buoyancy forces for fluids in the form of a half plane. */ 7 | abstract class BuoyancyController extends Controller { 8 | /** The outer surface normal */ 9 | var normal = Vector2.Zero 10 | /** The height of the fluid surface along the normal */ 11 | var offset = 0f 12 | /** The fluid density */ 13 | var density = 0f 14 | /** Fluid velocity, for drag calculations */ 15 | var velocity = Vector2.Zero 16 | /** Linear drag co-efficient */ 17 | var linearDrag = 0f 18 | /** Angular drag co-efficient */ 19 | var angularDrag = 0f 20 | /** 21 | * If false, bodies are assumed to be uniformly dense, otherwise use the shapes densities. 22 | * False by default to prevent a gotcha. 23 | */ 24 | var useDensity = false 25 | /** If true, gravity is taken from the world instead of the gravity parameter. */ 26 | var useWorldGravity = false 27 | /** Gravity vector, if the world's gravity is not used */ 28 | var gravity = Vector2.Zero 29 | 30 | def step(step: TimeStep) { 31 | //Buoyancy force is just a function of position, 32 | //so unlike most forces, it is safe to ignore sleeping bodes 33 | forAwakeBodies { body => 34 | var areac = Vector2.Zero 35 | var massc = Vector2.Zero 36 | var area = 0f 37 | var mass = 0f 38 | for (f <- body.fixtures) { 39 | val (sarea, sc) = f.computeSubmergedArea(normal, offset) 40 | area += sarea 41 | areac += sarea * sc 42 | val shapeDensity = if (useDensity) f.density else 1f 43 | mass += sarea * shapeDensity 44 | massc += sarea * sc * shapeDensity 45 | } 46 | areac /= area 47 | 48 | val localCentroid = body.transform ** areac 49 | massc /= mass 50 | if (area >= Settings.Epsilon) { 51 | //Buoyancy 52 | val buoyancyForce = -density * area * gravity 53 | body.applyForce(buoyancyForce, massc) 54 | //Linear drag 55 | var dragForce = body.getLinearVelocityFromWorldPoint(areac) - velocity 56 | dragForce *= -linearDrag * area 57 | body.applyForce(dragForce, areac) 58 | //Angular drag 59 | //TODO: Something that makes more physical sense? 60 | body.applyTorque(-body.I / body.mass * area * body.angularVelocity * angularDrag) 61 | } 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/shapes/Edge.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.shapes 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | import Settings.ε 6 | 7 | /** 8 | * A single edge from v1 to v2 9 | */ 10 | class Edge(val v1: Vector2, val v2: Vector2) extends Shape { 11 | // Erkki: is this correct? 12 | val radius = Settings.polygonRadius 13 | 14 | val direction = (v2 - v1).normalize 15 | val length = (v2 - v1).length 16 | val normal = direction.normal 17 | 18 | val corner1Dir = normal 19 | val corner2Dir = normal * -1.0f 20 | val corner1Convex = true // TODO!!! 21 | val corner2Convex = true // TODO!!! 22 | 23 | def testPoint(t: Transform2, p: Vector2) = false 24 | 25 | def testSegment(t: Transform2, segment: Segment, maxLambda: Float) = 26 | new Segment(t * v1, t * v2).testSegment(segment, maxLambda) 27 | 28 | def computeAABB(t: Transform2) = { 29 | val p1 = t * v1 30 | val p2 = t * v2 31 | val r = Vector2(radius, radius) 32 | AABB(min(p1, p2), max(p1, p2)) 33 | } 34 | 35 | // ERKKI Shouldn't the center be v1 + (v2 - v1) / 2 ? Or is it not important? 36 | def computeMass(density: Float) = Mass(0, v1, 0) 37 | 38 | def computeSubmergedArea(normal: Vector2, offset: Float, t: Transform2): 39 | (Float, Vector2) = { 40 | //Note that v0 is independent of any details of the specific edge 41 | //We are relying on v0 being consistent between multiple edges of the same body 42 | val v0 = offset * normal 43 | //b2Vec2 v0 = xf.position + (offset - b2Dot(normal, xf.position)) * normal; 44 | 45 | var lv1 = t * v1 46 | var lv2 = t * v2 47 | 48 | val d1 = (normal dot lv1) - offset 49 | val d2 = (normal dot lv2) - offset 50 | 51 | if (d1 > 0.0f) { 52 | if (d2 > 0.0f) { 53 | return (0, Vector2.Zero) 54 | } else { 55 | lv1 = -d2 / (d1 - d2) * lv1 + d1 / (d1 - d2) * lv2 56 | } 57 | } else { 58 | if (d2 > 0.0f) { 59 | lv2 = -d2 / (d1 - d2) * lv1 + d1 / (d1 - d2) * lv2 60 | } else { 61 | // Nothing 62 | } 63 | } 64 | 65 | // v0,v1,v2 represents a fully submerged triangle 66 | val tri = Polygon.Triangle(v0, v1, v2) 67 | // ERKKI in Box2D this centroid is not area weighted 68 | // even as the comment said so 69 | import Polygon.Triangle.inv3 70 | (tri.area, inv3 * (v0 + v1 + v2)) 71 | } 72 | 73 | def computeSweepRadius(pivot: Vector2) = { 74 | val ds1 = distanceSquared(v1, pivot) 75 | val ds2 = distanceSquared(v2, pivot) 76 | sqrt(max(ds1, ds2)) 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/scenes/DominoTower.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.testbed.scenes 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | import dsl.DSL._ 6 | import dynamics.World 7 | 8 | // ERKKI: this was 120 Hz in JBox2D 9 | object DominoTower extends TestbedScene { 10 | val dwidth = 0.2f 11 | val dheight = 1f 12 | var ddensity = 10f 13 | val dfriction = 0.1f 14 | val baseCount = 25 15 | 16 | def makeDomino(x: Float, y: Float, horizontal: Boolean)(implicit world: World) = 17 | body { 18 | pos(x, y) 19 | angle(if (horizontal) Pi / 2 else 0f) 20 | (box(dwidth / 2, dheight / 2) 21 | density ddensity 22 | friction dfriction 23 | restitution 0.65f) 24 | massFromShapes 25 | } 26 | 27 | def createScene(implicit world: dynamics.World) { 28 | body { // Floor 29 | pos(0, -10) 30 | box(50, 10) 31 | } 32 | 33 | //Make bullets 34 | val bs = box(0.7f, 0.7f) density 35f friction 0f restitution 0.85f 35 | var b = body { 36 | bullet(true) 37 | pos(30, 50) 38 | fixture(bs) 39 | massFromShapes 40 | } 41 | b.linearVelocity = (-25, -25) 42 | b.angularVelocity = 6.7f 43 | b = body { 44 | pos(-30, 25) 45 | fixture(bs) density 25f 46 | massFromShapes 47 | } 48 | b.linearVelocity = (35, 10) 49 | b.angularVelocity = -8.3f 50 | 51 | //Make base 52 | for (i <- 0 until baseCount) { 53 | val currX = i * 1.5f * dheight - (1.5f * dheight * baseCount / 2f) 54 | makeDomino(currX, dheight / 2.0f, false) 55 | makeDomino(currX, dheight + dwidth / 2.0f, true) 56 | } 57 | 58 | //Make 'I's 59 | for (j <- 1 until baseCount) { 60 | if (j > 3) ddensity *= .8f 61 | //y at center of 'I' structure 62 | val currY = dheight * 0.5f + (dheight + 2f * dwidth) * 0.99f * j 63 | 64 | for (i <- 0 until baseCount - j) { 65 | // + random(-.05f, .05f); 66 | val currX = i * 1.5f * dheight - (1.5f * dheight * (baseCount - j) /2f) 67 | ddensity *= 2.5f 68 | if (i == 0) 69 | makeDomino(currX - (1.25f * dheight) + 0.5f * dwidth, currY - dwidth, false) 70 | if ((i == baseCount - j - 1) && (j != 1)) 71 | makeDomino(currX + (1.25f * dheight) - 0.5f * dwidth, currY - dwidth, false) 72 | ddensity /= 2.5f 73 | makeDomino(currX, currY, false) 74 | makeDomino(currX, currY + 0.5f * (dwidth + dheight), true) 75 | makeDomino(currX, currY - 0.5f * (dwidth + dheight), true) 76 | } 77 | } 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/contacts/SingleManifoldContact.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics.contacts 2 | 3 | import vecmath._ 4 | import collision._ 5 | import shapes._ 6 | 7 | /** 8 | * Reusable implementation for contacts between convex shapes i.e. contacts with single manifolds. 9 | */ 10 | class SingleManifoldContact[S1 <: Shape, S2 <: Shape]( 11 | f1: Fixture, 12 | f2: Fixture, 13 | collider: Collider[S1, S2] 14 | ) extends Contact(f1, f2) { 15 | protected var manifoldHolder: Option[Manifold] = None 16 | def manifolds = if (manifoldHolder.isEmpty) Nil else manifoldHolder.get :: Nil 17 | 18 | def evaluate(listener: ContactListener) = evaluate( 19 | listener, 20 | collider.collide(f1.shape.asInstanceOf[S1], f1.body.transform, 21 | f2.shape.asInstanceOf[S2], f2.body.transform) 22 | ) 23 | 24 | protected def evaluate(listener: ContactListener, manifold: Option[Manifold]) { 25 | val oldMH = manifoldHolder 26 | manifoldHolder = manifold 27 | (oldMH, manifoldHolder) match { 28 | case (None, None) => 29 | case (None, Some(manifold)) => 30 | val mp = manifold.points(0) 31 | mp.normalImpulse = 0f 32 | mp.tangentImpulse = 0f 33 | notifyListener(listener, mp, manifold.normal, EventType.Add) 34 | case (Some(m0), Some(manifold)) => 35 | val mp = manifold.points(0) 36 | val mp0 = m0.points(0) 37 | mp.normalImpulse = mp0.normalImpulse 38 | mp.tangentImpulse = mp0.tangentImpulse 39 | notifyListener(listener, mp, manifold.normal, EventType.Persist) 40 | case (Some(m0), None) => 41 | notifyListener(listener, m0.points(0), m0.normal, EventType.Remove) 42 | } 43 | } 44 | 45 | protected final def notifyListener(listener: ContactListener, mp: ManifoldPoint, normal: Vector2, event: EventType) { 46 | if (listener != null) { 47 | val b1 = fixture1.body 48 | val b2 = fixture2.body 49 | val v1 = b1.getLinearVelocityFromLocalPoint(mp.localPoint1) 50 | val v2 = b2.getLinearVelocityFromLocalPoint(mp.localPoint2) 51 | val cp = ContactPoint( 52 | fixture1, 53 | fixture2, 54 | b1.toWorldPoint(mp.localPoint1), 55 | v2 - v1, 56 | normal, 57 | mp.separation, 58 | friction, 59 | restitution, 60 | mp.id 61 | ) 62 | event match { 63 | case EventType.Add => listener.add(cp) 64 | case EventType.Persist => listener.persist(cp) 65 | case EventType.Remove => listener.remove(cp) 66 | } 67 | } 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/collision/PolygonCircleCollider.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.collision 2 | 3 | import vecmath._ 4 | import shapes._ 5 | import collision._ 6 | import Settings.ε 7 | 8 | object PolygonCircleCollider extends Collider[Polygon, Circle] { 9 | 10 | def collide(polygon: Polygon, xf1: Transform2, 11 | circle: Circle, xf2: Transform2): Option[Manifold] = { 12 | // Compute circle position in the frame of the polygon. 13 | val c = xf2 * circle.pos 14 | val cLocal = xf1 ** c 15 | 16 | // Find edge with maximum separation. 17 | var normalIndex = 0 18 | var separation = -Float.MaxValue 19 | 20 | val vertexCount = polygon.vertexCount 21 | val vertices = polygon.vertices 22 | val normals = polygon.normals 23 | for (i <- 0 until vertexCount) { 24 | val s = normals(i) ∙ (cLocal - vertices(i)) 25 | if (s > circle.radius) { 26 | // Early out. 27 | return None 28 | } 29 | 30 | if (s > separation) { 31 | normalIndex = i; 32 | separation = s; 33 | } 34 | } 35 | 36 | // If the center is inside the polygon ... 37 | if (separation < ε) { 38 | val id = ContactID(0, normalIndex, ContactID.NullFeature, false) 39 | val normal = xf1.rot * normals(normalIndex) 40 | val p = c - (normal * circle.radius) 41 | val points = new Array[ManifoldPoint](1) 42 | points(0) = ManifoldPoint(xf1 ** p, xf2 ** p, separation - circle.radius, id) 43 | return Some(Manifold(points, normal)) 44 | } 45 | 46 | // Project the circle center onto the edge segment. 47 | val vertIndex1 = normalIndex; 48 | val vertIndex2 = if (vertIndex1 + 1 < vertexCount) vertIndex1 + 1 else 0 49 | var e = vertices(vertIndex2) - vertices(vertIndex1) 50 | val length = e.length 51 | e /= length // normalize 52 | assert(length > ε) 53 | 54 | // Project the center onto the edge. 55 | val u = (cLocal - vertices(vertIndex1)) ∙ e 56 | 57 | val (p, incidentEdge, incidentVertex) = u match { 58 | case _ if u <= 0f => (vertices(vertIndex1), ContactID.NullFeature, vertIndex1) 59 | case _ if u >= length => (vertices(vertIndex2), ContactID.NullFeature, vertIndex2) 60 | case _ => (vertices(vertIndex1) + (e * u), normalIndex, 0) 61 | } 62 | 63 | var d = cLocal - p 64 | val dist = d.length 65 | d /= dist // normalize 66 | 67 | if (dist > circle.radius) { 68 | return None 69 | } 70 | 71 | val id = ContactID(0, incidentEdge, incidentVertex, false) 72 | val normal = xf1.rot * d 73 | val pos = c - (normal * circle.radius) 74 | val points = new Array[ManifoldPoint](1) 75 | points(0) = ManifoldPoint(xf1 ** pos, xf2 ** pos, dist - circle.radius, id) 76 | Some(Manifold(points, normal)) 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/shapes/Circle.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.shapes 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | import Settings.ε 6 | 7 | /** 8 | * Circle 9 | * 10 | * pos - position 11 | * radius - radius of the circle 12 | */ 13 | class Circle(val pos: Vector2, val radius: Float) extends Shape { 14 | 15 | def testPoint(t: Transform2, p: Vector2) = { 16 | val center = t * pos 17 | val d = p - center 18 | (d ∙ d) <= (radius * radius) 19 | } 20 | 21 | /** 22 | * Collision Detection in Interactive 3D Environments by Gino van den Bergen 23 | * From Section 3.1.2 24 | * x = s + a * r 25 | * norm(x) = radius 26 | */ 27 | def testSegment(t: Transform2, segment: Segment, maxLambda: Float): SegmentCollide = { 28 | val p = t * pos 29 | val s = segment.p1 - p 30 | val b = (s dot s) - radius * radius 31 | 32 | // Does the segment start inside the circle? 33 | if (b < 0.0f) 34 | return SegmentCollide.startsInside(0, Vector2.Zero) 35 | 36 | // Solve quadratic equation. 37 | val r = segment.p2 - segment.p1 38 | val c = s dot r 39 | val rr = r dot r 40 | val sigma = c * c - rr * b 41 | 42 | // Check for negative discriminant and short segment. 43 | if (sigma < 0.0f || rr < ε) 44 | return SegmentCollide.Miss 45 | 46 | // Find the point of intersection of the line with the circle. 47 | var a = -(c + sqrt(sigma)) 48 | 49 | // Is the intersection point on the segment? 50 | if (0.0f <= a && a <= maxLambda * rr) { 51 | a /= rr 52 | return SegmentCollide.hit(a, (s + a * r).normalize) 53 | } 54 | 55 | SegmentCollide.Miss 56 | } 57 | 58 | def computeAABB(t: Transform2) = { 59 | val p = t * pos 60 | AABB(p - radius, p + radius) 61 | } 62 | 63 | def computeMass(density: Float) = { 64 | val mass = density * π * radius * radius 65 | // inertia about the local origin 66 | val i = mass * (0.5f * radius * radius + (pos ∙ pos)) 67 | Mass(mass, pos, i) 68 | } 69 | 70 | def computeSubmergedArea(normal: Vector2, offset: Float, t: Transform2): 71 | (Float, Vector2) = { 72 | val p = t * pos 73 | val l = -((normal dot p) - offset) 74 | if (l < -radius + ε) { 75 | // Completely dry 76 | return (0, Vector2.Zero) 77 | } 78 | if (l > radius) { 79 | // Completely wet 80 | return (π * radius * radius, p) 81 | } 82 | 83 | //Magic 84 | val r2 = radius * radius 85 | val l2 = l * l 86 | val area = r2 * (asin(l / radius) + π / 2) + l * sqrt(r2 - l2) 87 | val com = -2.0f / 3.0f * pow(r2-l2, 1.5f) / area 88 | 89 | (area, p + normal * com) 90 | } 91 | 92 | def computeSweepRadius(pivot: Vector2) = { 93 | // ERKKI : in Box2D now it is: = distance(pos, pivot) 94 | val d = pos - pivot 95 | d.length + radius - Settings.toiSlop 96 | } 97 | 98 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | org.villane.box2d 5 | scalabox2d-parent 6 | ScalaBox2D - 2D Physics Engine for Games (Parent POM) 7 | 2.0.1-SNAPSHOT 8 | pom 9 | 10 | scalabox2d 11 | scalabox2d-svg 12 | scalabox2d-testbed 13 | 14 | 15 | 2.7.4 16 | 17 | 18 | 19 | scala-tools.org 20 | Scala-Tools Maven2 Repository 21 | http://scala-tools.org/repo-releases 22 | 23 | 24 | 25 | 26 | scala-tools.org 27 | Scala-Tools Maven2 Repository 28 | http://scala-tools.org/repo-releases 29 | 30 | 31 | 32 | 33 | org.scala-lang 34 | scala-library 35 | ${scala.version} 36 | compile 37 | 38 | 39 | 40 | 41 | 42 | org.apache.maven.plugins 43 | maven-source-plugin 44 | 45 | 46 | attach-sources 47 | 48 | jar 49 | 50 | 51 | 52 | 53 | 54 | org.scala-tools 55 | maven-scala-plugin 56 | 57 | 58 | 59 | compile 60 | testCompile 61 | 62 | 63 | 64 | 65 | ${scala.version} 66 | 67 | -optimise 68 | -Yinline 69 | 70 | 71 | 72 | 73 | maven-compiler-plugin 74 | 75 | 1.6 76 | 1.6 77 | UTF-8 78 | 79 | 80 | 81 | maven-resources-plugin 82 | 83 | UTF-8 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/shapes/ShapeDef.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.shapes 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | 6 | /** 7 | * Superclass for shape definitions. 8 | * 9 | * Shape definitions are mutable to make them easier to use. 10 | */ 11 | sealed abstract class ShapeDef 12 | 13 | /** A circle definition. */ 14 | final case class CircleDef(var pos: Vector2, var radius: Float) extends ShapeDef 15 | 16 | /** Point definition. Like a 0-radius circle, but has mass */ 17 | final case class PointDef(var pos: Vector2, var mass: Float) extends ShapeDef 18 | 19 | object EdgeChainDef { 20 | def apply(vertices: Vector2*): EdgeChainDef = apply(false, vertices.toArray) 21 | def loop(vertices: Vector2*): EdgeChainDef = apply(true, vertices.toArray) 22 | } 23 | 24 | /** A chain of connected edges */ 25 | final case class EdgeChainDef( 26 | /** Whether to create an extra edge between the first and last vertices. */ 27 | var loop: Boolean, 28 | /** The vertices in local coordinates. */ 29 | var vertices: Array[Vector2] 30 | ) extends ShapeDef { 31 | def edges: Array[EdgeDef] = { 32 | val len = if (loop) vertices.length else vertices.length - 1 33 | val edges = new Array[EdgeDef](len) 34 | for (i <- 0 until len) { 35 | edges(i) = EdgeDef(vertices(i), vertices((i + 1) % vertices.length)) 36 | } 37 | edges 38 | } 39 | } 40 | 41 | final case class EdgeDef( 42 | var v1: Vector2, 43 | var v2: Vector2 44 | ) extends ShapeDef 45 | 46 | object PolygonDef { 47 | /** 48 | * Build vertices to represent an axis-aligned box. 49 | * @param hx the half-width. 50 | * @param hy the half-height. 51 | */ 52 | def box(hx: Float, hy: Float): PolygonDef = apply(Array((-hx,-hy),(hx,-hy),(hx,hy),(-hx,hy))) 53 | 54 | /** 55 | * Build vertices to represent an oriented box. 56 | * @param hx the half-width. 57 | * @param hy the half-height. 58 | * @param center the center of the box in local coordinates. 59 | */ 60 | def box(hx: Float, hy: Float, center: Vector2): PolygonDef = { 61 | val pd = box(hx, hy) 62 | val xf = Transform2(center, Matrix22.Identity) 63 | pd.vertices = pd.vertices.map(v => xf * v) 64 | pd 65 | } 66 | 67 | /** 68 | * Build vertices to represent an oriented box. 69 | * @param hx the half-width. 70 | * @param hy the half-height. 71 | * @param center the center of the box in local coordinates. 72 | * @param angle the rotation of the box in local coordinates. 73 | */ 74 | def box(hx: Float, hy: Float, center: Vector2, angle: Float): PolygonDef = { 75 | val pd = box(hx, hy) 76 | val xf = Transform2(center, angle) 77 | pd.vertices = pd.vertices.map(v => xf * v) 78 | pd 79 | } 80 | 81 | } 82 | 83 | /** 84 | * Convex polygon definition. 85 | * 86 | * The vertices must be in CCW order for a right-handed coordinate system 87 | * with the z-axis coming out of the screen. 88 | */ 89 | final case class PolygonDef( 90 | /** The CCW vertices in local coordinates. */ 91 | var vertices: Array[Vector2] 92 | ) extends ShapeDef 93 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dsl/JointBuilders.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dsl 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | import shapes._ 6 | import collision._ 7 | import dynamics._ 8 | import dynamics.joints._ 9 | 10 | class JointBuilder(j: JointDef) { 11 | def collideConnected(v: Boolean) = { j.collideConnected = v; this } 12 | def userData(v: AnyRef) = { j.userData = v; this } 13 | def define = j 14 | } 15 | 16 | class BodyJointBuilder(j: JointDef, bodies: (Body, Body)) 17 | extends JointBuilder(j) { 18 | j.body1 = bodies._1 19 | j.body2 = bodies._2 20 | } 21 | 22 | class GearJointBuilder(j: GearJointDef, joints: (Joint, Joint)) 23 | extends JointBuilder(j) { 24 | j.joint1 = joints._1 25 | j.joint2 = joints._2 26 | def ratio(v: Float) = { j.ratio = v; this } 27 | } 28 | 29 | class DistanceJointBuilder(j: DistanceJointDef, bodies: (Body, Body)) 30 | extends BodyJointBuilder(j, bodies) { 31 | def localAnchor1(v: Vector2) = { j.localAnchor1 = v; this } 32 | def localAnchor2(v: Vector2) = { j.localAnchor2 = v; this } 33 | def length(v: Float) = { j.length = v; this } 34 | def frequencyHz(v: Float) = { j.frequencyHz = v; this } 35 | def dampingRatio(v: Float) = { j.dampingRatio = v; this } 36 | } 37 | 38 | class RevoluteJointBuilder(j: RevoluteJointDef, bodies: (Body, Body)) 39 | extends BodyJointBuilder(j, bodies) { 40 | def localAnchor1(v: Vector2) = { j.localAnchor1 = v; this } 41 | def localAnchor2(v: Vector2) = { j.localAnchor2 = v; this } 42 | def referenceAngle(v: Float) = { j.referenceAngle = v; this } 43 | def anchor(v: Vector2) = { 44 | j.localAnchor1 = j.body1.toLocalPoint(v) 45 | j.localAnchor2 = j.body2.toLocalPoint(v) 46 | j.referenceAngle = j.body2.angle - j.body1.angle 47 | this 48 | } 49 | def enableLimit(v: Boolean) = { j.enableLimit = v; this } 50 | def lowerAngle(v: Float) = { j.lowerAngle = v; this } 51 | def upperAngle(v: Float) = { j.upperAngle = v; this } 52 | def enableMotor(v: Boolean) = { j.enableMotor = v; this } 53 | def motorSpeed(v: Float) = { j.motorSpeed = v; this } 54 | def maxMotorTorque(v: Float) = { j.maxMotorTorque = v; this } 55 | } 56 | 57 | class PrismaticJointBuilder(j: PrismaticJointDef, bodies: (Body, Body)) 58 | extends BodyJointBuilder(j, bodies) { 59 | def localAnchor1(v: Vector2) = { j.localAnchor1 = v; this } 60 | def localAnchor2(v: Vector2) = { j.localAnchor2 = v; this } 61 | def referenceAngle(v: Float) = { j.referenceAngle = v; this } 62 | def anchor(v: Vector2) = { 63 | j.localAnchor1 = j.body1.toLocalPoint(v) 64 | j.localAnchor2 = j.body2.toLocalPoint(v) 65 | j.referenceAngle = j.body2.angle - j.body1.angle 66 | this 67 | } 68 | def localAxis1(v: Vector2) = { j.localAxis1 = v; this } 69 | def axis(v: Vector2) = { 70 | j.localAxis1 = j.body1.toLocalVector(v); 71 | this 72 | } 73 | def enableLimit(v: Boolean) = { j.enableLimit = v; this } 74 | def lowerTranslation(v: Float) = { j.lowerTranslation = v; this } 75 | def upperTranslation(v: Float) = { j.upperTranslation = v; this } 76 | def enableMotor(v: Boolean) = { j.enableMotor = v; this } 77 | def motorSpeed(v: Float) = { j.motorSpeed = v; this } 78 | def maxMotorForce(v: Float) = { j.maxMotorForce = v; this } 79 | } 80 | -------------------------------------------------------------------------------- /scalabox2d-svg/src/main/scala/org/villane/box2d/svg/SlickSVGSceneLoader.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.svg 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | import dsl._ 6 | import dsl.DSL._ 7 | 8 | import shapes._ 9 | import dynamics._ 10 | 11 | import ConvexHull._ 12 | 13 | import java.io.FileInputStream 14 | import org.newdawn.slick.Color 15 | import org.newdawn.slick.svg._ 16 | import org.newdawn.slick.geom 17 | 18 | /** 19 | * TODO offset? 20 | */ 21 | class SlickSVGSceneLoader(fileName: String, scale: Float) 22 | extends SceneFactory { 23 | 24 | implicit def slickVector2fToVector2(v: geom.Vector2f) = Vector2(v.x, v.y) 25 | 26 | var gravity = Vector2(0, 9.81f) 27 | 28 | def create = { 29 | val svg = InkscapeLoader.load(new FileInputStream(fileName), false) 30 | val aabb = AABB(Vector2.Zero, (svg.getWidth, svg.getHeight) / scale) 31 | val world = new World(aabb, gravity, true) 32 | createScene(svg)(world) 33 | world 34 | } 35 | 36 | def createScene(svg: Diagram)(implicit world: World) { 37 | for (i <- 0 until svg.getFigureCount) { 38 | createFigure(svg.getFigure(i)) 39 | } 40 | } 41 | 42 | def createFigure(fig: Figure)(implicit world: World) = fig.getType match { 43 | case Figure.ELLIPSE => 44 | val c = fig.getShape.asInstanceOf[geom.Shape] 45 | body { 46 | pos((c.getCenterX, c.getCenterY) / scale) 47 | circle((c.getMinX - c.getCenterX).abs / scale) density 1 48 | if (isDynamic(fig)) massFromShapes 49 | } 50 | case Figure.LINE => 51 | val line = fig.getShape.asInstanceOf[geom.Line] 52 | body { 53 | edge(line.getStart / scale, line.getEnd / scale) 54 | } 55 | case Figure.RECTANGLE => 56 | val poly = fig.getShape.asInstanceOf[geom.Polygon] 57 | body { 58 | pos((poly.getCenterX, poly.getCenterY) / scale) 59 | box( 60 | (poly.getMinX - poly.getCenterX).abs / scale, 61 | (poly.getMinY - poly.getCenterY).abs / scale 62 | ) density 1 63 | if (isDynamic(fig)) massFromShapes 64 | } 65 | case Figure.PATH => 66 | val path = fig.getShape.asInstanceOf[geom.Path] 67 | body { 68 | val ps = new Array[Vector2](path.getPointCount) 69 | for (i <- 0 until path.getPointCount) { 70 | val p = path.getPoint(i) 71 | ps(i) = (p(0), p(1)) / scale 72 | } 73 | edge(ps:_*) 74 | } 75 | case Figure.POLYGON => 76 | val poly = fig.getShape.asInstanceOf[geom.Polygon] 77 | body { 78 | pos(poly.getCenterX / scale, poly.getCenterY / scale) 79 | val ps = new Array[Vector2](poly.getPointCount) 80 | for (i <- 0 until poly.getPointCount) { 81 | val p = poly.getPoint(i) 82 | ps(i) = (p(0) - poly.getCenterX, p(1) - poly.getCenterY) / scale 83 | } 84 | if (ps.length <= 8 && poly.closed) { 85 | polygon(convexHull(ps):_*) density 1 86 | if (isDynamic(fig)) massFromShapes 87 | } else { 88 | edge(ps:_*) 89 | } 90 | } 91 | } 92 | 93 | def isDynamic(fig: Figure) = 94 | fig.getData.getAttribute("fill") != null && 95 | fig.getData.isColor("fill") && 96 | Color.red == fig.getData.getAsColor("fill") 97 | 98 | } 99 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/vecmath/Matrix22.scala: -------------------------------------------------------------------------------- 1 | package org.villane.vecmath 2 | 3 | import Preamble._ 4 | 5 | object Matrix22 { 6 | val Zero = Matrix22(0, 0, 0, 0) 7 | val One = Matrix22(1, 1, 1, 1) 8 | val Identity = Matrix22(1, 0, 0, 1) 9 | 10 | def apply(col1: Vector2, col2: Vector2): Matrix22 = 11 | Matrix22(col1.x, col2.x, col1.y, col2.y) 12 | 13 | /** 14 | * Creates a rotation matrix from an angle. 15 | * 16 | * @param angle rotation in radians 17 | */ 18 | def rotation(angle: Float) = { 19 | val c = cos(angle) 20 | val s = sin(angle) 21 | Matrix22(c, -s, s, c) 22 | } 23 | } 24 | 25 | /** 26 | * A 2-by-2 matrix. Stored in row-major order. 27 | * 28 | * Design note: Matrix22 could conceptually extend Tuple4, but does not for efficiency. 29 | * See the same note about Vector2 for details. 30 | * 31 | * TODO compute eigenvalues / max eigenvalue 32 | */ 33 | case class Matrix22(a11: Float, a12: Float, a21: Float, a22: Float) { 34 | def col1 = Vector2(a11, a21) 35 | def col2 = Vector2(a12, a22) 36 | 37 | // TODO +,-,*,/ Float 38 | def +(a: Float) = Matrix22(a11 + a, a12 + a, a21 + a, a22 + a) 39 | 40 | def +(m: Matrix22) = Matrix22(a11 + m.a11, a12 + m.a12, 41 | a21 + m.a21, a22 + m.a22) 42 | 43 | /** 44 | * Multiply a vector by this matrix. 45 | * @param v Vector to multiply by matrix. 46 | * @return Resulting vector 47 | */ 48 | def *(v: Vector2) = Vector2(a11 * v.x + a12 * v.y, 49 | a21 * v.x + a22 * v.y) 50 | 51 | /** 52 | * Multiply a vector by the transpose of this matrix. (mulT) 53 | * @param v 54 | * @return 55 | */ 56 | def **(v: Vector2) = Vector2(a11 * v.x + a21 * v.y, 57 | a12 * v.x + a22 * v.y) 58 | 59 | def determinant = a11 * a22 - a12 * a21 60 | 61 | /** 62 | * Solve A * x = b where A = this matrix. 63 | * @return The vector x that solves the above equation. 64 | */ 65 | def solve(b: Vector2) = { 66 | var det = determinant 67 | assert (det != 0.0f) 68 | det = 1 / det 69 | Vector2(det * (a22 * b.x - a12 * b.y), 70 | det * (a11 * b.y - a21 * b.x)) 71 | } 72 | 73 | /** 74 | * Multiply another matrix by this one. 75 | */ 76 | // Matrix22(this * m.col1, this * m.col2) 77 | def *(m: Matrix22): Matrix22 = Matrix22( 78 | a11 * m.a11 + a12 * m.a21, 79 | a11 * m.a12 + a12 * m.a22, 80 | a21 * m.a11 + a22 * m.a21, 81 | a21 * m.a12 + a22 * m.a22 82 | ) 83 | 84 | /** 85 | * Multiply another matrix by the transpose of this one. 86 | */ 87 | def **(m: Matrix22): Matrix22 = Matrix22( 88 | a11 * m.a11 + a21 * m.a21, 89 | a11 * m.a12 + a21 * m.a22, 90 | a12 * m.a11 + a22 * m.a21, 91 | a12 * m.a12 + a22 * m.a22 92 | ) 93 | 94 | def transpose = Matrix22(a11, a21, a12, a22) 95 | 96 | def abs = Matrix22(Math.abs(a11), Math.abs(a12), 97 | Math.abs(a21), Math.abs(a22)) 98 | 99 | def invert = { 100 | var det = determinant 101 | assert (det != 0.0f) 102 | det = 1 / det 103 | Matrix22(det * a22, -det * a12, 104 | -det * a21, det * a11) 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/vecmath/Vector2.scala: -------------------------------------------------------------------------------- 1 | package org.villane.vecmath 2 | 3 | import Preamble._ 4 | 5 | /** 6 | * Vector2 creation methods and often used constant values. 7 | */ 8 | object Vector2 { 9 | val Zero = new Vector2(0, 0) 10 | val One = Vector2(1, 1) 11 | val XUnit = Vector2(1, 0) 12 | val YUnit = Vector2(0, 1) 13 | def polar(r: Float, theta: Float) = Vector2(cos(theta) * r, sin(theta) * r) 14 | 15 | def lerp(begin: Vector2, end: Vector2, scalar: Float) = Vector2( 16 | begin.x + scalar * (end.x - begin.x), 17 | begin.y + scalar * (end.y - begin.y) 18 | ) 19 | 20 | // for debugging only 21 | var creationCount = 0L 22 | } 23 | 24 | /** 25 | * An immutable 2D Vector represented as x and y coordinates in single precision 26 | * floating point numbers. 27 | * 28 | * TODO Design ideas: 29 | * 30 | * Vector2 could extend Tuple2(x, y), but this causes inefficiency: 31 | * tuple2 will store x,y as objects, causing boxing. Otherwise we could add: 32 | * extends Tuple2(x, y) { override def swap = Vector2(y, x) } 33 | * 34 | * Reconsider with Scala 2.8 @specialized 35 | * 36 | * Idea: provide mutable version for big computations 37 | */ 38 | case class Vector2(x: Float, y: Float) { 39 | def +(a: Float) = Vector2(x + a, y + a) 40 | def -(a: Float) = Vector2(x - a, y - a) 41 | def *(a: Float) = Vector2(x * a, y * a) 42 | def /(a: Float) = Vector2(x / a, y / a) 43 | 44 | def cross(a: Float) = Vector2(a * y, -a * x) 45 | def ×(a: Float) = Vector2(a * y, -a * x) 46 | 47 | def +(v: Vector2) = Vector2(x + v.x, y + v.y) 48 | def -(v: Vector2) = Vector2(x - v.x, y - v.y) 49 | 50 | def dot(v: Vector2) = x * v.x + y * v.y 51 | def ∙(v: Vector2) = x * v.x + y * v.y 52 | 53 | def cross(v: Vector2) = x * v.y - y * v.x 54 | def ×(v: Vector2) = x * v.y - y * v.x 55 | 56 | def normal = Vector2(y, -x) // = ×(1) 57 | def unary_- = Vector2(-x, -y) 58 | def swap = Vector2(y, x) 59 | def abs = Vector2(x.abs, y.abs) 60 | 61 | /** Polar coordinates */ 62 | def theta = atan2(y, x) 63 | def θ = atan2(y, x) 64 | 65 | def to(v: Vector2) = new Vector2Range(this, v, Vector2.One) 66 | def times(n: Int) = new Vector2Times(this, n, Vector2.One) 67 | 68 | /** 69 | * Since normalization is a simple operation, in cases where speed is desired, 70 | * but the length before normalization is also needed, use this instead: 71 | * 72 | * val len = v.length 73 | * v /= len 74 | * 75 | */ 76 | def normalize = this / length 77 | /** @see normalize */ 78 | def unit = this / length 79 | 80 | def length = sqrt(x * x + y * y) 81 | def lengthSquared = x * x + y * y 82 | 83 | // Unlike with float extensions, calling clamp on an existing vector doesn't effect performance, so this shouldn't be static. 84 | def clamp(low: Vector2, high: Vector2) = Preamble.max(low, Preamble.min(this, high)) 85 | 86 | // TODO remove this if Scala 2.8 allows Vector to extend Tuple2 without performance hit 87 | def tuple = (x, y) 88 | 89 | // Inlined for performance 90 | def isValid = x != Float.NaN && x != Float.NegativeInfinity && x != Float.PositiveInfinity && 91 | y != Float.NaN && y != Float.NegativeInfinity && y != Float.PositiveInfinity 92 | 93 | override def productPrefix = "" 94 | 95 | // for debugging only 96 | Vector2.creationCount += 1 97 | } 98 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/Fixture.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics 2 | 3 | import vecmath._ 4 | import vecmath.Preamble 5 | import shapes._ 6 | import collision._ 7 | import broadphase.BroadPhase 8 | import broadphase.PairManager 9 | 10 | object Fixture { 11 | def apply(body: Body, defn: FixtureDef) = new Fixture(defn, body) 12 | } 13 | 14 | class Fixture(defn: FixtureDef, val body: Body) { 15 | val shape = Shape.create(defn.shapeDef) 16 | 17 | /** The shape's friction coefficient, usually in the range [0,1]. */ 18 | var friction = defn.friction 19 | 20 | /** The shape's restitution (elasticity) usually in the range [0,1]. */ 21 | var restitution = defn.restitution 22 | 23 | /** Sweep radius relative to the parent body's center of mass. */ 24 | var sweepRadius = 0f 25 | 26 | /** The shape's density, usually in kg/m^2. */ 27 | var density = defn.density 28 | 29 | var proxyId = PairManager.NullProxy 30 | 31 | /** 32 | * A sensor shape collects contact information but never generates a collision 33 | * response. 34 | */ 35 | var isSensor = defn.isSensor 36 | 37 | /** Contact filtering data. */ 38 | var filter = defn.filter 39 | 40 | /** Use this to store application specify shape data. */ 41 | var userData = defn.userData 42 | 43 | def computeMass() = shape.computeMass(density) 44 | 45 | def computeSubmergedArea(normal: Vector2, offset: Float): (Float, Vector2) = 46 | shape.computeSubmergedArea(normal, offset, body.transform) 47 | 48 | def computeSweepRadius(pivot: Vector2) = shape.computeSweepRadius(pivot) 49 | 50 | /** Internal */ 51 | def createProxy(broadPhase: BroadPhase, transform: Transform2) { 52 | assert(proxyId == PairManager.NullProxy) 53 | 54 | val aabb = shape.computeAABB(transform) 55 | 56 | val inRange = broadPhase.inRange(aabb) 57 | 58 | assert(inRange, "You are creating a shape outside the world box.") 59 | 60 | proxyId = if (inRange) 61 | broadPhase.createProxy(aabb, this) 62 | else 63 | PairManager.NullProxy 64 | } 65 | 66 | /** Internal */ 67 | def destroyProxy(broadPhase: BroadPhase) { 68 | if (proxyId != PairManager.NullProxy) { 69 | broadPhase.destroyProxy(proxyId) 70 | proxyId = PairManager.NullProxy 71 | } 72 | // ERKKI: box2d de-allocates the shape here 73 | } 74 | 75 | /** Internal */ 76 | def synchronize(broadPhase: BroadPhase, t1: Transform2, t2: Transform2): Boolean = { 77 | if (proxyId == PairManager.NullProxy) return false 78 | 79 | // Compute an AABB that covers the swept shape (may miss some rotation effect). 80 | val aabb = shape.computeAABB(t1) combineWith shape.computeAABB(t2) 81 | if (broadPhase.inRange(aabb)) { 82 | broadPhase.moveProxy(proxyId, aabb) 83 | true 84 | } else { 85 | false 86 | } 87 | } 88 | 89 | /** Internal */ 90 | def refilterProxy(broadPhase: BroadPhase, transform: Transform2) { 91 | if (proxyId == PairManager.NullProxy) return 92 | 93 | broadPhase.destroyProxy(proxyId) 94 | 95 | val aabb = shape.computeAABB(transform) 96 | 97 | if (broadPhase.inRange(aabb)) { 98 | proxyId = broadPhase.createProxy(aabb, this) 99 | } else { 100 | proxyId = PairManager.NullProxy 101 | } 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/ContactManager.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics 2 | 3 | import contacts._ 4 | import shapes._ 5 | import collision._ 6 | import broadphase.PairListener 7 | 8 | /** Delegate of World - for internal use. */ 9 | class ContactManager extends PairListener { 10 | var world: World = null 11 | 12 | val destroyImmediate = false 13 | 14 | def pairAdded(proxyUserData1: AnyRef, proxyUserData2: AnyRef): AnyRef = { 15 | val fixture1 = proxyUserData1.asInstanceOf[Fixture] 16 | val fixture2 = proxyUserData2.asInstanceOf[Fixture] 17 | 18 | val body1 = fixture1.body 19 | val body2 = fixture2.body 20 | 21 | if (body1.isStatic && body2.isStatic) { 22 | return NullContact 23 | } 24 | 25 | if (body1 eq body2) { 26 | return NullContact 27 | } 28 | 29 | if (body2.isConnected(body1)) { 30 | return NullContact 31 | } 32 | 33 | if (world.contactFilter != null && !world.contactFilter.shouldCollide(fixture1, fixture2)) { 34 | return NullContact 35 | } 36 | 37 | // Call the factory. 38 | val c = Contact(fixture1, fixture2) 39 | 40 | if (c == null) { 41 | return NullContact 42 | } 43 | 44 | // Insert into the world. 45 | world.contactList += c 46 | // world.contactCount += 1 47 | 48 | c 49 | } 50 | 51 | // This is a callback from the broadphase when two AABB proxies cease 52 | // to overlap. We retire the b2Contact. 53 | def pairRemoved(proxyUserData1: AnyRef, proxyUserData2: AnyRef, pairUserData: AnyRef) { 54 | //B2_NOT_USED(proxyUserData1); 55 | //B2_NOT_USED(proxyUserData2); 56 | 57 | if (pairUserData == null) { 58 | return 59 | } 60 | 61 | val c = pairUserData.asInstanceOf[Contact] 62 | if (c == NullContact) { 63 | return 64 | } 65 | 66 | // An attached body is being destroyed, we must destroy this contact 67 | // immediately to avoid orphaned shape pointers. 68 | destroy(c) 69 | } 70 | 71 | def destroy(c: Contact) { 72 | val f1 = c.fixture1 73 | val f2 = c.fixture2 74 | 75 | // Inform the user that this contact is ending. 76 | val manifoldCount = c.manifolds.length 77 | if (manifoldCount > 0 && (world.contactListener != null)) { 78 | val b1 = f1.body 79 | val b2 = f2.body 80 | for (manifold <- c.manifolds) { 81 | val normal = manifold.normal 82 | for (mp <- manifold.points) { 83 | val pos = b1.toWorldPoint(mp.localPoint1) 84 | val v1 = b1.getLinearVelocityFromLocalPoint(mp.localPoint1) 85 | val v2 = b2.getLinearVelocityFromLocalPoint(mp.localPoint2) 86 | val cp = ContactPoint(f1, f2, pos, v2 - v1, manifold.normal, mp.separation, c.friction, c.restitution, mp.id) 87 | world.contactListener.remove(cp) 88 | } 89 | } 90 | } 91 | 92 | // TODO XXX HACK This probably has horrible performance! 93 | // Contact lists should be optimized for random removal as well 94 | // Remove from the world. 95 | world.contactList -= c 96 | val body1 = f1.body 97 | val body2 = f2.body 98 | 99 | // Remove from body 1 100 | body1.contactList = body1.contactList.remove(_.contact == c) 101 | 102 | // Remove from body 2 103 | body2.contactList = body2.contactList.remove(_.contact == c) 104 | 105 | Contact.destroy(c) 106 | // world.contactCount -= 1 107 | } 108 | 109 | def collide() { 110 | // Update awake contacts. 111 | var iC = 0 112 | while (iC < world.contactList.length) { 113 | val c = world.contactList(iC) 114 | iC += 1 115 | 116 | val body1 = c.fixture1.body 117 | val body2 = c.fixture2.body 118 | if (!body1.isSleeping || !body2.isSleeping) { 119 | c.update(world.contactListener) 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/joints/PointingDeviceJoint.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics.joints 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | import dynamics._ 6 | 7 | //p = attached point, m = mouse point 8 | //C = p - m 9 | //Cdot = v 10 | // = v + cross(w, r) 11 | //J = [I r_skew] 12 | //Identity used: 13 | //w k % (rx i + ry j) = w * (-ry i + rx j) 14 | class PointingDeviceJoint(defn: PointingDeviceJointDef) extends Joint(defn) { 15 | private[this] var _target = defn.target 16 | var localAnchor = body2.transform ** _target 17 | var force = Vector2.Zero 18 | var mass = Matrix22.Zero // effective mass for point-to-point constraint. 19 | var C = Vector2.Zero // position error 20 | 21 | var maxForce = defn.maxForce 22 | var beta = 0f // bias factor 23 | var gamma = 0f // softness 24 | 25 | initPDJ 26 | def initPDJ { 27 | val m = body2.mass 28 | // Frequency 29 | val ω = 2.0f * π * defn.frequencyHz 30 | // Damping coefficient 31 | val d = 2.0f * m * defn.dampingRatio * ω 32 | // Spring stiffness 33 | val k = m * ω * ω 34 | // magic formulas 35 | gamma = 1.0f / (d + defn.timeStep * k) 36 | beta = defn.timeStep * k / (d + defn.timeStep * k) 37 | } 38 | 39 | def target = _target 40 | /** Use this to update the target point. */ 41 | def target_=(target: Vector2) { 42 | if (body2.isSleeping) body2.wakeUp() 43 | this._target = target 44 | } 45 | 46 | def anchor1 = target 47 | def anchor2 = body2.toWorldPoint(localAnchor) 48 | def localAnchor1 = Vector2.Zero 49 | def localAnchor2 = localAnchor 50 | def reactionForce = force 51 | def reactionTorque = 0f 52 | 53 | def initVelocityConstraints(step: TimeStep) { 54 | val b = body2 55 | 56 | // Compute the effective mass matrix. 57 | val r = b.transform.rot * (localAnchor - b.localCenter) 58 | 59 | // K = [(1/m1 + 1/m2) * eye(2) - skew(r1) * invI1 * skew(r1) - skew(r2) 60 | // * invI2 * skew(r2)] 61 | // = [1/m1+1/m2 0 ] + invI1 * [r1.y*r1.y -r1.x*r1.y] + invI2 * 62 | // [r1.y*r1.y -r1.x*r1.y] 63 | // [ 0 1/m1+1/m2] [-r1.x*r1.y r1.x*r1.x] [-r1.x*r1.y r1.x*r1.x] 64 | val invMass = b.invMass 65 | val invI = b.invI 66 | 67 | val K1 = Matrix22(invMass, 0.0f, 0.0f, invMass) 68 | val K2 = Matrix22(invI * r.y * r.y, -invI * r.x * r.y, 69 | -invI * r.x * r.y, invI * r.x * r.x) 70 | 71 | var K = K1 + K2 72 | K = Matrix22(K.a11 + gamma, K.a12, 73 | K.a21, K.a22 + gamma) 74 | 75 | mass = K.invert 76 | 77 | C = (b.sweep.c.x + r.x - target.x, b.sweep.c.y + r.y - target.y) 78 | 79 | // Cheat with some damping 80 | b.angularVelocity *= 0.98f 81 | 82 | // Warm starting. 83 | val P = force * step.dt 84 | b.linearVelocity += P * invMass 85 | b.angularVelocity += invI * (r.x * P.y - r.y * P.x) 86 | } 87 | 88 | def solveVelocityConstraints(step: TimeStep) { 89 | val b = body2 90 | 91 | val r = b.transform.rot * (localAnchor - b.localCenter) 92 | 93 | // Cdot = v + cross(w, r) 94 | val Cdot = b.linearVelocity + b.angularVelocity × r 95 | 96 | //Vec2 force = -step.inv_dt * Mat22.mul(m_mass, Cdot + (m_beta * step.inv_dt) * m_C + m_gamma * step.dt * m_force); 97 | var f = Vector2(Cdot.x + (beta*step.invDt)*C.x + gamma * step.dt * force.x, 98 | Cdot.y + (beta*step.invDt)*C.y + gamma * step.dt * force.y) 99 | f = mass * f * (-step.invDt) 100 | 101 | val oldForce = force 102 | force += f 103 | val forceMagnitude = force.length 104 | if (forceMagnitude > maxForce) { 105 | force *= (maxForce / forceMagnitude) 106 | } 107 | f = (force.x - oldForce.x, force.y - oldForce.y) 108 | 109 | val P = f * step.dt 110 | b.linearVelocity += P * b.invMass 111 | b.angularVelocity += b.invI * r × P 112 | } 113 | 114 | def solvePositionConstraints() = true 115 | } 116 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/collision/TOI.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.collision 2 | 3 | import dynamics.Sweep 4 | import vecmath._ 5 | import vecmath.Preamble._ 6 | import shapes._ 7 | 8 | import Settings.ε 9 | 10 | case class TOIInput( 11 | sweep1: Sweep, 12 | sweep2: Sweep, 13 | sweepRadius1: Float, 14 | sweepRadius2: Float, 15 | tolerance: Float 16 | ) 17 | 18 | /** Handles conservative advancement to compute time of impact between shapes. */ 19 | object TOI { 20 | // This algorithm uses conservative advancement to compute the time of 21 | // impact (TOI) of two shapes. 22 | // Refs: Bullet, Young Kim 23 | 24 | /** 25 | * Compute the time when two shapes begin to touch or touch at a closer distance. 26 | *

Warning: the sweeps must have the same time interval. 27 | * @return the fraction between [0,1] in which the shapes first touch. 28 | * fraction=0 means the shapes begin touching/overlapped, and fraction=1 means the shapes don't touch. 29 | */ 30 | def timeOfImpact(input: TOIInput, shape1: Shape, shape2: Shape) = { 31 | val r1 = input.sweepRadius1 32 | val r2 = input.sweepRadius2 33 | val sweep1 = input.sweep1 34 | val sweep2 = input.sweep2 35 | val tolerance = input.tolerance 36 | 37 | assert(sweep1.t0 == sweep2.t0) 38 | assert(1.0f - sweep1.t0 > ε) 39 | 40 | val t0 = sweep1.t0 41 | val v1 = sweep1.c - sweep1.c0 42 | val v2 = sweep2.c - sweep2.c0 43 | val ω1 = sweep1.a - sweep1.a0 44 | val ω2 = sweep2.a - sweep2.a0 45 | 46 | var α = 0f 47 | 48 | val k_maxIterations = 20 // TODO_ERIN b2Settings 49 | var iter = 0 50 | var targetDistance = 0f 51 | // use loop = false instead of breaks as 'break' isn't available in Scala 52 | var loop = true 53 | while(loop) { 54 | val t = (1f - α) * t0 + α 55 | val xf1 = sweep1.getTransform(t) 56 | val xf2 = sweep2.getTransform(t) 57 | 58 | //val distInput = DistanceInput(xf1, xf2, false) 59 | 60 | // Get the distance between shapes. 61 | val (distance,p1,p2) = Distance.distance(shape1, xf1, shape2, xf2) 62 | //System.out.println(distance); 63 | 64 | if (iter == 0) { 65 | // Compute a reasonable target distance to give some breathing room 66 | // for conservative advancement. 67 | if (distance > 2.0f * Settings.toiSlop) { 68 | targetDistance = 1.5f * Settings.toiSlop; 69 | } else { 70 | targetDistance = max(0.05f * Settings.toiSlop, distance - 0.5f * Settings.toiSlop); 71 | } 72 | } 73 | 74 | if (distance - targetDistance < 0.05f * Settings.toiSlop || iter == k_maxIterations) { 75 | //if (distance-targetDistance < 0) System.out.println("dist error: "+ (distance-targetDistance) + " toiSlop: "+Settings.toiSlop + " iter: "+iter); 76 | loop = false 77 | } else { 78 | val normal = (p2 - p1).normalize 79 | 80 | // Compute upper bound on remaining movement. 81 | val approachVelocityBound = (normal ∙ (v1 - v2)) + ω1.abs * r1 + ω2.abs * r2 82 | if (approachVelocityBound.abs < ε) { 83 | α = 1.0f 84 | loop = false 85 | } else { 86 | // Get the conservative time increment. Don't advance all the way. 87 | val dAlpha = (distance - targetDistance) / approachVelocityBound 88 | //float32 dt = (distance - 0.5f * b2_linearSlop) / approachVelocityBound; 89 | val newAlpha = α + dAlpha 90 | 91 | // The shapes may be moving apart or a safe distance apart. 92 | if (newAlpha < 0.0f || 1.0f < newAlpha) { 93 | α = 1.0f 94 | loop = false 95 | } else { 96 | 97 | // Ensure significant advancement. 98 | if (newAlpha < (1.0f + 100.0f * ε) * α) { 99 | loop = false 100 | } else { 101 | α = newAlpha 102 | iter += 1 103 | } 104 | } 105 | } 106 | } 107 | } 108 | 109 | α 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/slick/SlickDisplayWorld.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.testbed.slick 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | import box2d.draw._ 6 | import box2d.shapes._ 7 | import box2d.collision._ 8 | import box2d.dynamics._ 9 | import box2d.dynamics.joints._ 10 | import box2d.dynamics.contacts.ContactListener 11 | import org.newdawn.slick.AppGameContainer; 12 | import org.newdawn.slick.BasicGame; 13 | import org.newdawn.slick.GameContainer; 14 | import org.newdawn.slick.Graphics; 15 | import org.newdawn.slick.SlickException; 16 | import org.newdawn.slick.tests.AntiAliasTest; 17 | import box2d.testbed._ 18 | 19 | object SlickDisplayWorld { 20 | import DrawFlags._ 21 | def runWithSimulation(world: World, flipY: Boolean, flags: Int): Unit = 22 | run(world, flipY, true, flags) 23 | 24 | def run(world: World): Unit = run(world, true, false, Shapes) 25 | 26 | def run(world: World, flipY: Boolean, runSimulation: Boolean, flags: Int) { 27 | val app = new SlickDisplayWorld(world) 28 | app.simulate = runSimulation 29 | app.dd.flags = flags 30 | val container = new AppGameContainer(app) 31 | app.m_debugDraw.yFlip = if (flipY) -1 else 1 32 | container.setTargetFrameRate(60) 33 | container.setDisplayMode(800,800,false) 34 | container.start() 35 | } 36 | } 37 | 38 | class SlickDisplayWorld(world: World) extends BasicGame("Slick ScalaBox2D: World") { 39 | var simulate = false 40 | val m_debugDraw = new SlickDebugDraw(null,null) 41 | val dd = new DebugDraw(m_debugDraw) 42 | def width = gameContainer.getWidth 43 | def height = gameContainer.getHeight 44 | def mouseX = mousePos.x 45 | def mouseY = mousePos.y 46 | def pmouseX = mousePosOld.x 47 | def pmouseY = mousePosOld.y 48 | var gameContainer: GameContainer = null 49 | 50 | def init(container: GameContainer) { 51 | m_debugDraw.container = container; 52 | gameContainer = container 53 | m_debugDraw.transX = world.aabb.center.x 54 | m_debugDraw.transY = world.aabb.center.y 55 | } 56 | 57 | def update(container: GameContainer, delta: Int) { 58 | if (simulate) { 59 | var timeStep = 1.0f / 60 60 | world.step(timeStep, 10) 61 | } 62 | } 63 | 64 | def render(container: GameContainer, g: Graphics) { 65 | m_debugDraw.g = g 66 | handleCanvasDrag 67 | dd.drawDebugData(world) 68 | } 69 | 70 | var mouseButton = 0 71 | var mousePressed = false 72 | var mousePos = Vector2.Zero 73 | var mousePosOld = Vector2.Zero 74 | /** 75 | * Handle mouseDown events. 76 | * @param p The screen location that the mouse is down at. 77 | */ 78 | override def mousePressed(b: Int, x: Int, y: Int) { 79 | mouseButton = b 80 | mousePressed = true 81 | mousePosOld = mousePos 82 | mousePos = (x,y) 83 | } 84 | 85 | /** 86 | * Handle mouseUp events. 87 | */ 88 | override def mouseReleased(b: Int, x: Int, y: Int) { 89 | mousePosOld = mousePos 90 | mousePos = (x,y) 91 | mousePressed = false 92 | } 93 | 94 | /** 95 | * Handle mouseMove events (TestbedMain also sends mouseDragged events here) 96 | * @param p The new mouse location (screen coordinates) 97 | */ 98 | override def mouseMoved(oldX: Int, oldY: Int, x: Int, y: Int) { 99 | mousePosOld = mousePos 100 | mousePos = (x,y) 101 | } 102 | 103 | def handleCanvasDrag() { 104 | val d = m_debugDraw 105 | //Vec2 mouseWorld = d.screenToWorld(mouseX, mouseY); 106 | if (mouseButton == 1) { 107 | if (mousePressed) { 108 | d.transX += mouseX - pmouseX; 109 | d.transY -= mouseY - pmouseY; 110 | val v = d.screenToWorld(width*.5f,height*.5f); 111 | } 112 | } 113 | 114 | } 115 | override def mouseWheelMoved(amount: Int) { 116 | val d = m_debugDraw 117 | val notches = amount 118 | val oldCenter = d.screenToWorld(width / 2.0f, height / 2.0f) 119 | //Change the zoom and clamp it to reasonable values 120 | if (notches < 0) { 121 | d.scaleFactor = Math.min(300f, d.scaleFactor * 1.05f); 122 | } 123 | else if (notches > 0) { 124 | d.scaleFactor = Math.max(.02f, d.scaleFactor / 1.05f); 125 | } 126 | val newCenter = d.screenToWorld(width / 2.0f, height / 2.0f); 127 | d.transX -= (oldCenter.x - newCenter.x) * d.scaleFactor; 128 | d.transY -= (oldCenter.y - newCenter.y) * d.scaleFactor; 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/Settings.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d 2 | 3 | import vecmath.Preamble._ 4 | 5 | /** Global tuning constants based on MKS units and various integer maximums (vertices per shape, pairs, etc.). */ 6 | object Settings { 7 | 8 | /** A "close to zero" float epsilon value for use */ 9 | val ε = 1.1920928955078125E-7f 10 | val Epsilon = ε 11 | 12 | // Define your unit system here. The default system is 13 | // meters-kilograms-seconds. For the tuning to work well, 14 | // your dynamic objects should be bigger than a pebble and smaller 15 | // than a house. 16 | // 17 | // Use of these settings has been deprecated - they do not even 18 | // exist anymore in the C++ version of the engine, and future support 19 | // is unlikely. 20 | val lengthUnitsPerMeter = 1.0f 21 | val massUnitsPerKilogram = 1.0f 22 | val timeUnitsPerSecond = 1.0f 23 | 24 | // Collision 25 | 26 | val maxManifoldPoints = 2 27 | val maxShapesPerBody = 64 28 | val maxPolygonVertices = 8 29 | 30 | // ERKKI XXX do these still have to be power of two? 31 | /** Must be a power of two. */ 32 | val maxProxies = 2048 33 | /** Must be a power of two. */ 34 | val maxPairs = 8 * maxProxies 35 | 36 | // Dynamics 37 | 38 | /** 39 | * A small length used as a collision and constraint tolerance. Usually it is 40 | * chosen to be numerically significant, but visually insignificant. 41 | */ 42 | val linearSlop = 0.005f * lengthUnitsPerMeter; // 0.5 cm 43 | 44 | /** 45 | * A small angle used as a collision and constraint tolerance. Usually it is 46 | * chosen to be numerically significant, but visually insignificant. 47 | */ 48 | val angularSlop = 2.0f / 180.0f * π // 2 degrees 49 | 50 | /** 51 | * The radius of the polygon/edge shape skin. This should not be modified. 52 | * Making this smaller means polygons will have and insufficient for 53 | * continuous collision. Making it larger may create artifacts for vertex 54 | * collision. 55 | */ 56 | val polygonRadius = 2.0f * linearSlop 57 | 58 | /** 59 | * A velocity threshold for elastic collisions. Any collision with a relative linear 60 | * velocity below this threshold will be treated as inelastic. 61 | */ 62 | val velocityThreshold = 1.0f * lengthUnitsPerMeter / timeUnitsPerSecond // 1 m/s 63 | 64 | /** 65 | * The maximum linear position correction used when solving constraints. This helps to 66 | * prevent overshoot. 67 | */ 68 | val maxLinearCorrection = 0.2f * lengthUnitsPerMeter // 20 cm 69 | 70 | /** 71 | * The maximum angular position correction used when solving constraints. This helps to 72 | * prevent overshoot. 73 | */ 74 | val maxAngularCorrection = 8.0f / 180.0f * π // 8 degrees 75 | 76 | /** 77 | * This scale factor controls how fast overlap is resolved. Ideally this would be 1 so 78 | * that overlap is removed in one time step. However using values close to 1 often lead 79 | * to overshoot. 80 | */ 81 | val contactBaumgarte = 0.2f 82 | 83 | /** The time that a body must be still before it will go to sleep. */ 84 | val timeToSleep = 0.5f * timeUnitsPerSecond // half a second 85 | 86 | /** A body cannot sleep if its linear velocity is above this tolerance. */ 87 | val linearSleepTolerance = 0.01f * lengthUnitsPerMeter / timeUnitsPerSecond // 1 cm/s 88 | 89 | /** A body cannot sleep if its angular velocity is above this tolerance. */ 90 | val angularSleepTolerance = 2.0f / 180.0f / timeUnitsPerSecond 91 | 92 | /** 93 | * Continuous collision detection (CCD) works with core, shrunken shapes. This is the 94 | * amount by which shapes are automatically shrunk to work with CCD. This must be 95 | * larger than b2_linearSlop. 96 | */ 97 | val toiSlop = 8.0f * linearSlop 98 | 99 | /** 100 | * The maximum linear velocity of a body. This limit is very large and is used 101 | * to prevent numerical problems. You shouldn't need to adjust this. 102 | */ 103 | val maxLinearVelocity = 200.0f 104 | val maxLinearVelocitySquared = maxLinearVelocity * maxLinearVelocity 105 | 106 | /** 107 | * The maximum angular velocity of a body. This limit is very large and is used 108 | * to prevent numerical problems. You shouldn't need to adjust this. 109 | */ 110 | val maxAngularVelocity = 250.0f 111 | val maxAngularVelocitySquared = maxAngularVelocity * maxAngularVelocity 112 | 113 | /** Maximum number of contacts to be handled to solve a TOI island. */ 114 | val maxTOIContactsPerIsland = 32 115 | 116 | var mixFriction = { (a: Float, b: Float) => sqrt(a * b) } 117 | var mixRestitution = { (a: Float, b: Float) => max(a, b) } 118 | 119 | var threadedIslandSolving = false 120 | var numThreads = Runtime.getRuntime.availableProcessors + 1 121 | } 122 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/contacts/Contact.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics.contacts 2 | 3 | import collection.mutable 4 | import vecmath._ 5 | import vecmath.Preamble._ 6 | import shapes._ 7 | import collision._ 8 | 9 | object ContactFlags { 10 | val nonSolid = 0x0001 11 | val slow = 0x0002 12 | val island = 0x0004 13 | val toi = 0x0008 14 | } 15 | 16 | object Contact { 17 | def apply(f1: Fixture, f2: Fixture) = (f1.shape, f2.shape) match { 18 | case (s1: Circle, s2: Circle) => 19 | new SingleManifoldContact(f1, f2, CircleCollider) 20 | case (s1: Polygon, s2: Circle) => 21 | new MultiPointSingleManifoldContact(f1, f2, PolygonCircleCollider) 22 | case (s1: Circle, s2: Polygon) => 23 | new MultiPointSingleManifoldContact(f2, f1, PolygonCircleCollider) 24 | case (s1: Polygon, s2: Polygon) => 25 | new MultiPointSingleManifoldContact(f1, f2, PolygonCollider) 26 | case (s1: Edge, s2: Circle) => 27 | new SingleManifoldContact(f1, f2, EdgeCircleCollider) 28 | case (s1: Circle, s2: Edge) => 29 | new SingleManifoldContact(f2, f1, EdgeCircleCollider) 30 | case (s1: Polygon, s2: Edge) => 31 | new MultiPointSingleManifoldContact(f1, f2, PolygonEdgeCollider) 32 | case (s1: Edge, s2: Polygon) => 33 | new MultiPointSingleManifoldContact(f2, f1, PolygonEdgeCollider) 34 | case (s1, s2) => 35 | // XXX ERKKI this was return null; and probably should be NullContact? 36 | throw new IllegalArgumentException("Contact(" + s1.getClass.getSimpleName + 37 | ", " + s2.getClass.getSimpleName + ") not supported!") 38 | } 39 | 40 | def destroy(contact: Contact) { 41 | if (contact.manifolds.length > 0) { 42 | contact.fixture1.body.wakeUp() 43 | contact.fixture2.body.wakeUp() 44 | } 45 | } 46 | 47 | } 48 | 49 | /** 50 | * Base class for contacts between shapes. 51 | * @author ewjordan 52 | */ 53 | abstract class Contact(val fixture1: Fixture, val fixture2: Fixture) { 54 | /** Node for connecting bodies. */ 55 | val node1 = if (fixture2 != null) ContactEdge(fixture2.body, this) else null 56 | 57 | /** Node for connecting bodies. */ 58 | val node2 = if (fixture1 != null) ContactEdge(fixture1.body, this) else null 59 | 60 | // TODO ERKKI: mixing friction/restitution has been moved to the solver 61 | /** Combined friction */ 62 | var friction = 63 | if (fixture1 == null || fixture2 == null) 0f 64 | else sqrt(fixture1.friction * fixture2.friction) 65 | // TODO Settings.mixFriction(fixture1.friction, fixture2.friction) 66 | 67 | /** Combined restitution */ 68 | var restitution = 69 | if (fixture1 == null || fixture2 == null) 0f 70 | else max(fixture1.restitution, fixture2.restitution) 71 | // TODO Settings.mixRestitution(fixture1.restitution, fixture2.restitution) 72 | 73 | var flags = 0 74 | var toi = 0f 75 | 76 | def evaluate(listener: ContactListener) 77 | 78 | /** 79 | * Get the number of manifolds. This is 0 or 1 between convex shapes. 80 | * This may be greater than 1 for convex-vs-concave shapes. Each 81 | * manifold holds up to two contact points with a shared contact normal. 82 | * 83 | * Get the manifold array. 84 | */ 85 | def manifolds: Seq[Manifold] 86 | 87 | def solid = (flags & ContactFlags.nonSolid) == 0 88 | 89 | init 90 | 91 | def init { 92 | // This is mainly so that NullContract could be created! 93 | if (fixture1 != null && fixture2 != null) { 94 | if (fixture1.isSensor || fixture2.isSensor) { 95 | flags |= ContactFlags.nonSolid 96 | } 97 | //world = s1.body.world 98 | fixture1.body.contactList = node1 :: fixture1.body.contactList 99 | fixture2.body.contactList = node2 :: fixture2.body.contactList 100 | } 101 | } 102 | 103 | def update(listener: ContactListener) { 104 | val oldCount = manifolds.length 105 | evaluate(listener) 106 | val newCount = manifolds.length 107 | 108 | val body1 = fixture1.body 109 | val body2 = fixture2.body 110 | 111 | if (newCount == 0 && oldCount > 0) { 112 | body1.wakeUp() 113 | body2.wakeUp() 114 | } 115 | 116 | // Slow contacts don't generate TOI events. 117 | if (body1.isStatic || body1.isBullet || body2.isStatic || body2.isBullet) { 118 | flags &= ~ContactFlags.slow 119 | } else { 120 | flags |= ContactFlags.slow 121 | } 122 | } 123 | 124 | // ERKKI impl from circlecontact 125 | def computeTOI(sweep1: Sweep, sweep2: Sweep) = { 126 | val toiInput = TOIInput( 127 | sweep1, 128 | sweep2, 129 | fixture1.computeSweepRadius(sweep1.localCenter), 130 | fixture2.computeSweepRadius(sweep2.localCenter), 131 | Settings.linearSlop 132 | ) 133 | TOI.timeOfImpact(toiInput, fixture1.shape, fixture2.shape) 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/joints/DistanceJoint.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics.joints 2 | 3 | import vecmath.Vector2 4 | import vecmath.Preamble._ 5 | import Settings.ε 6 | 7 | //C = norm(p2 - p1) - L 8 | //u = (p2 - p1) / norm(p2 - p1) 9 | //Cdot = dot(u, v2 + cross(w2, r2) - v1 - cross(w1, r1)) 10 | //J = [-u -cross(r1, u) u cross(r2, u)] 11 | //K = J * invM * JT 12 | //= invMass1 + invI1 * cross(r1, u)^2 + invMass2 + invI2 * cross(r2, u)^2 13 | 14 | /** 15 | * A distance joint constrains two points on two bodies 16 | * to remain at a fixed distance from each other. You can view 17 | * this as a massless, rigid rod. 18 | */ 19 | class DistanceJoint(defn: DistanceJointDef) extends Joint(defn) { 20 | val localAnchor1 = defn.localAnchor1 21 | val localAnchor2 = defn.localAnchor2 22 | val length = defn.length 23 | var impulse = 0.0f 24 | var u = Vector2.Zero 25 | // effective mass for the constraint. 26 | var mass = 0.0f 27 | val frequencyHz = defn.frequencyHz 28 | val dampingRatio = defn.dampingRatio 29 | var gamma = 0.0f 30 | var bias = 0.0f 31 | 32 | def anchor1 = body1.toWorldPoint(localAnchor1) 33 | def anchor2 = body2.toWorldPoint(localAnchor2) 34 | def reactionForce = (impulse * u.x, impulse * u.y) 35 | def reactionTorque = 0.0f 36 | 37 | def initVelocityConstraints(step: TimeStep) { 38 | invDt = step.invDt 39 | 40 | //TODO: fully inline temp Vec2 ops 41 | val b1 = body1 42 | val b2 = body2 43 | 44 | // Compute the effective mass matrix. 45 | val r1 = b1.transform.rot * (localAnchor1 - b1.localCenter) 46 | val r2 = b2.transform.rot * (localAnchor2 - b2.localCenter) 47 | u = (b2.sweep.c.x + r2.x - b1.sweep.c.x - r1.x, 48 | b2.sweep.c.y + r2.y - b1.sweep.c.y - r1.y) 49 | 50 | // Handle singularity. 51 | val len = u.length 52 | if (len > Settings.linearSlop) { 53 | u /= len 54 | } else { 55 | u = Vector2.Zero 56 | } 57 | 58 | val cr1u = r1 × u 59 | val cr2u = r2 × u 60 | 61 | val invMass = b1.invMass + b1.invI * cr1u * cr1u + b2.invMass + b2.invI * cr2u * cr2u 62 | assert(invMass > ε) 63 | mass = 1.0f / invMass 64 | 65 | if (frequencyHz > 0.0f) { 66 | val C = len - length 67 | 68 | // Frequency 69 | val ω = 2 * π * frequencyHz 70 | 71 | // Damping coefficient 72 | val d = 2.0f * mass * dampingRatio * ω 73 | 74 | // Spring stiffness 75 | val k = mass * ω * ω 76 | 77 | // magic formulas 78 | gamma = 1.0f / (step.dt * (d + step.dt * k)) 79 | bias = C * step.dt * k * gamma 80 | mass = 1.0f / (invMass + gamma) 81 | } 82 | 83 | if (step.warmStarting) { 84 | impulse *= step.dtRatio 85 | val P = u * impulse 86 | b1.linearVelocity -= P * b1.invMass 87 | b1.angularVelocity -= b1.invI * r1 × P 88 | b2.linearVelocity += P * b2.invMass 89 | b2.angularVelocity += b2.invI * r2 × P 90 | } else { 91 | impulse = 0.0f 92 | } 93 | } 94 | 95 | def solvePositionConstraints(): Boolean = { 96 | if (frequencyHz > 0.0f) { 97 | return true 98 | } 99 | 100 | val b1 = body1 101 | val b2 = body2 102 | 103 | val r1 = b1.transform.rot * (localAnchor1 - b1.localCenter) 104 | val r2 = b2.transform.rot * (localAnchor2 - b2.localCenter) 105 | 106 | var d = Vector2(b2.sweep.c.x + r2.x - b1.sweep.c.x - r1.x, 107 | b2.sweep.c.y + r2.y - b1.sweep.c.y - r1.y) 108 | 109 | val len = d.length 110 | d = d.normalize 111 | val C = (len - length).clamp(-Settings.maxLinearCorrection, Settings.maxLinearCorrection) 112 | 113 | val imp = -mass * C 114 | u = d 115 | val P = imp * u 116 | 117 | b1.sweep.c -= P * b1.invMass 118 | b1.sweep.a -= b1.invI * (r1.x*P.y-r1.y*P.x)//(r1 × P); 119 | b2.sweep.c += P * b2.invMass 120 | b2.sweep.a += b2.invI * (r2.x*P.y-r2.y*P.x)//(r2 × P); 121 | 122 | b1.synchronizeTransform() 123 | b2.synchronizeTransform() 124 | 125 | return C.abs < Settings.linearSlop 126 | } 127 | 128 | def solveVelocityConstraints(step: TimeStep) { 129 | val b1 = body1 130 | val b2 = body2 131 | 132 | val r1 = b1.transform.rot * (localAnchor1 - b1.localCenter) 133 | val r2 = b2.transform.rot * (localAnchor2 - b2.localCenter) 134 | 135 | // Cdot = dot(u, v + cross(w, r)) 136 | val v1 = b1.linearVelocity + b1.angularVelocity × r1 137 | val v2 = b2.linearVelocity + b2.angularVelocity × r2 138 | val Cdot = u ∙ (v2 - v1) 139 | 140 | val imp = -mass * (Cdot + bias + gamma * impulse) 141 | impulse += imp 142 | 143 | val P = u * imp 144 | b1.linearVelocity -= P * b1.invMass 145 | b1.angularVelocity -= b1.invI * (r1.x*P.y - r1.y*P.x)//(r1 × P); 146 | b2.linearVelocity += P * b2.invMass 147 | b2.angularVelocity += b2.invI * (r2.x*P.y - r2.y*P.x)//(r2 × P); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/slick/SlickDebugDraw.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.testbed.slick 2 | 3 | import vecmath._ 4 | import box2d.draw._ 5 | import org.newdawn.slick.Color; 6 | import org.newdawn.slick.GameContainer; 7 | import org.newdawn.slick.Graphics; 8 | import org.newdawn.slick.geom.Circle; 9 | import org.newdawn.slick.geom.Polygon; 10 | import org.newdawn.slick.opengl.TextureImpl; 11 | 12 | /** 13 | * Not fully implemented - just enough here to get the Pyramid 14 | * demo to draw. 15 | * 16 | */ 17 | class SlickDebugDraw(var g: Graphics, var container: GameContainer) extends DebugDrawHandler { 18 | 19 | def hw = container.getWidth / 2 20 | def hh = container.getHeight / 2 21 | // World 0,0 maps to transX, transY on screen 22 | var transX = 0f 23 | var transY = 0f 24 | var scaleFactor = 10.0f 25 | var yFlip = -1.0f 26 | 27 | implicit def box2dColorToSlickColor(c: Color3f) = 28 | new Color(c.r.toInt, c.g.toInt, c.b.toInt, c.a.toInt) 29 | 30 | override def setCamera(x: Float, y: Float, scale: Float) { 31 | transX = hw + x * scale - 50 // -50 for control panel 32 | transY = hh + yFlip * y * scale 33 | scaleFactor = scale 34 | } 35 | 36 | def map(mapMe: Float, fromLow: Float, fromHigh: Float, toLow: Float, toHigh: Float) = { 37 | val interp = (mapMe - fromLow) / (fromHigh - fromLow) 38 | (interp*toHigh + (1.0f-interp)*toLow) 39 | } 40 | 41 | override def worldToScreen(world: Vector2) = { 42 | val x = map(world.x, 0f, 1f, transX, transX+scaleFactor) 43 | var y = map(world.y, 0f, 1f, transY, transY+scaleFactor) 44 | if (yFlip == -1.0f) y = map(y, 0f, container.getHeight, container.getHeight, 0f) 45 | Vector2(x, y) 46 | } 47 | 48 | override def screenToWorld(screen: Vector2) = { 49 | val x = map(screen.x, transX, transX+scaleFactor, 0f, 1f) 50 | var y = screen.y 51 | if (yFlip == -1.0f) y = map(y, container.getHeight, 0f, 0f, container.getHeight) 52 | y = map(y, transY, transY + scaleFactor, 0f, 1f) 53 | Vector2(x, y) 54 | } 55 | 56 | def drawCircle(center: Vector2, radius: Float, color: Color3f) { 57 | val c = worldToScreen(center) 58 | val circle = new Circle(c.x, c.y, radius * scaleFactor) 59 | g.setColor(color) 60 | g.draw(circle) 61 | } 62 | 63 | def drawPoint(position: Vector2, f: Float, color: Color3f) { 64 | g.setColor(color) 65 | val c = worldToScreen(position) 66 | val radius = 3 67 | // x1, y1 are upper left corner 68 | val x1 = c.x - radius 69 | val y1 = c.y - radius 70 | g.fillOval(x1, y1, 2 * radius, 2 * radius) 71 | } 72 | 73 | def drawPolygon(vertices: Array[Vector2], color: Color3f) { 74 | val polygon = new Polygon() 75 | for (v <- vertices) { 76 | val screenPt = worldToScreen(v) 77 | polygon.addPoint(screenPt.x, screenPt.y) 78 | } 79 | g.setColor(color) 80 | g.draw(polygon) 81 | } 82 | 83 | def drawSolidPolygon(vertices: Array[Vector2], color: Color3f) { 84 | val polygon = new Polygon() 85 | for (v <- vertices) { 86 | val screenPt = worldToScreen(v) 87 | polygon.addPoint(screenPt.x, screenPt.y) 88 | } 89 | val slickColor = box2dColorToSlickColor(color) 90 | g.setColor(slickColor) 91 | g.draw(polygon) 92 | slickColor.a *= 0.5f 93 | g.setColor(slickColor) 94 | g.fill(polygon) 95 | } 96 | 97 | def drawSegment(p1: Vector2, p2: Vector2, color: Color3f) { 98 | g.setColor(color) 99 | TextureImpl.bindNone() 100 | g.setLineWidth(1) 101 | g.setAntiAlias(false) 102 | val screen1 = worldToScreen(p1) 103 | val screen2 = worldToScreen(p2) 104 | g.drawLine(screen1.x,screen1.y,screen2.x,screen2.y) 105 | } 106 | 107 | def drawSolidCircle(center: Vector2, radius: Float, axis: Vector2, color: Color3f) { 108 | val c = worldToScreen(center) 109 | val circle = new Circle(c.x, c.y, radius * scaleFactor) 110 | val slickColor = box2dColorToSlickColor(color) 111 | g.setColor(slickColor) 112 | g.draw(circle) 113 | val screenRadius = scaleFactor * radius 114 | g.drawLine(c.x, c.y, c.x + screenRadius * axis.x, c.y + yFlip * screenRadius * axis.y) 115 | slickColor.a *= 0.5f 116 | g.setColor(slickColor) 117 | g.fill(circle) 118 | } 119 | 120 | def drawString(x: Float, y: Float, s: String, color: Color3f) { 121 | g.setColor(color) 122 | g.drawString(s, x, y) 123 | } 124 | 125 | def drawTransform(xf: Transform2) { 126 | val r = 3 127 | val p1 = xf.pos 128 | val k_axisScale = 0.4f 129 | val p1world = worldToScreen(p1) 130 | val p2 = p1 + (xf.rot.col1 * k_axisScale) 131 | val p2world = worldToScreen(p2) 132 | val p3 = p1 + (xf.rot.col2 * k_axisScale) 133 | val p3world = worldToScreen(p3) 134 | g.setColor(new Color(1f, 0f, 0f)) 135 | g.drawLine(p1world.x, p1world.y, p2world.x, p2world.y) 136 | g.setColor(new Color(0f, 1f, 0f)) 137 | g.drawLine(p1world.x, p1world.y, p3world.x, p3world.y) 138 | } 139 | 140 | } -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dsl/DSL.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dsl 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | import shapes._ 6 | import collision._ 7 | import dynamics._ 8 | import dynamics.joints._ 9 | import collection.mutable.ListBuffer 10 | 11 | object DSL { 12 | 13 | implicit def fixtureBuilder2FixtureDef(b: FixtureBuilder) = b.define 14 | 15 | object BodyCtx extends ThreadLocal[BodyCtx] 16 | class BodyCtx { 17 | var massFromShapes = false 18 | val bodyBuilder = new BodyBuilder(new BodyDef) 19 | val fixBuilders = new ListBuffer[FixtureBuilder] 20 | } 21 | 22 | def body[T](block: => T)(implicit world: World) = { 23 | val ctx = new BodyCtx 24 | BodyCtx.set(ctx) 25 | block 26 | val body = world.createBody(ctx.bodyBuilder.define) 27 | for (builder <- ctx.fixBuilders) { 28 | builder.define match { 29 | case fixDef @ FixtureDef(ec: EdgeChainDef) => 30 | for (edge <- ec.edges) { 31 | fixDef.shapeDef = edge 32 | body.createFixture(fixDef) 33 | } 34 | fixDef.shapeDef = ec 35 | case fixDef => body.createFixture(fixDef) 36 | } 37 | } 38 | if (ctx.massFromShapes) body.computeMassFromShapes 39 | BodyCtx.remove 40 | body 41 | } 42 | 43 | def fixture(fd: FixtureDef) = { 44 | val builder = new FixtureBuilder(fd) 45 | if (BodyCtx.get != null) BodyCtx.get.fixBuilders += builder 46 | builder 47 | } 48 | 49 | def fixture(fb: FixtureBuilder) = { 50 | if (BodyCtx.get != null) BodyCtx.get.fixBuilders += fb 51 | fb 52 | } 53 | 54 | private def fbuilder(sd: ShapeDef) = { 55 | val builder = new FixtureBuilder(FixtureDef(sd)) 56 | if (BodyCtx.get != null) BodyCtx.get.fixBuilders += builder 57 | builder 58 | } 59 | 60 | def shape(shapeDef: ShapeDef) = fbuilder(shapeDef) 61 | def circle(radius: Float) = fbuilder(CircleDef(Vector2.Zero, radius)) 62 | def circle(pos: Vector2, radius: Float) = fbuilder(CircleDef(pos, radius)) 63 | def polygon(vertices: Vector2*) = fbuilder(PolygonDef(vertices.toArray)) 64 | def box(halfW: Float, halfH: Float) = fbuilder(PolygonDef.box(halfW, halfH)) 65 | def box(halfW: Float, halfH: Float, center: Vector2) = 66 | fbuilder(PolygonDef.box(halfW, halfH, center)) 67 | def box(halfW: Float, halfH: Float, center: Vector2, angle: Float) = 68 | fbuilder(PolygonDef.box(halfW, halfH, center, angle)) 69 | def edge(vertices: Vector2*) = fbuilder(EdgeChainDef(vertices:_*)) 70 | def loop(vertices: Vector2*) = fbuilder(EdgeChainDef.loop(vertices:_*)) 71 | 72 | private def bbuilder = BodyCtx.get.bodyBuilder 73 | 74 | def userData = bbuilder.userData _ 75 | def mass = bbuilder.mass _ 76 | def pos = bbuilder.pos _ 77 | def angle = bbuilder.angle _ 78 | def linearDamping = bbuilder.linearDamping _ 79 | def angularDamping = bbuilder.angularDamping _ 80 | def sleepingAllowed = bbuilder.sleepingAllowed _ 81 | def initiallySleeping = bbuilder.initiallySleeping _ 82 | def fixedRotation = bbuilder.fixedRotation _ 83 | def bullet = bbuilder.bullet _ 84 | 85 | def massFromShapes = BodyCtx.get.massFromShapes = true 86 | 87 | def joint[JB <: JointBuilder](jBuilder: => JB)(implicit world: World) = { 88 | val jDef = jBuilder.define 89 | world.createJoint(jDef) 90 | } 91 | 92 | def revolute(bodies: (Body, Body)) = new RevoluteJointBuilder( 93 | new RevoluteJointDef, bodies) 94 | def prismatic(bodies: (Body, Body)) = new PrismaticJointBuilder( 95 | new PrismaticJointDef, bodies) 96 | def distance(bodies: (Body, Body)) = new DistanceJointBuilder( 97 | new DistanceJointDef, bodies) 98 | def gear(joints: (Joint, Joint)) = new GearJointBuilder( 99 | new GearJointDef, joints) 100 | } 101 | 102 | class BodyBuilder(b: BodyDef) { 103 | def userData(userData: AnyRef) = { b.userData = userData; this } 104 | def mass(mass: Mass) = { b.mass = mass; this } 105 | def pos(pos: Vector2) = { b.pos = pos; this } 106 | def angle(angle: Float) = { b.angle = angle; this } 107 | def linearDamping(linearDamping: Float) = { b.linearDamping = linearDamping; this } 108 | def angularDamping(angularDamping: Float) = { b.angularDamping = angularDamping; this } 109 | def sleepingAllowed(allowSleep: Boolean) = { b.allowSleep = allowSleep; this } 110 | def initiallySleeping(isSleeping: Boolean) = { b.isSleeping = isSleeping; this } 111 | def fixedRotation(fixedRotation: Boolean) = { b.fixedRotation = fixedRotation; this } 112 | def bullet(isBullet: Boolean) = { b.isBullet = isBullet; this } 113 | def define = b 114 | } 115 | 116 | class FixtureBuilder(s: FixtureDef) { 117 | def userData(userData: AnyRef) = { s.userData = userData; this } 118 | def material(material: Material) = { s.apply(material); this } 119 | def friction(friction: Float) = { s.friction = friction; this } 120 | def restitution(restitution: Float) = { s.restitution = restitution; this } 121 | def density(density: Float) = { s.density = density; this } 122 | def filter(filter: FilterData) = { s.filter = filter; this } 123 | def sensor(isSensor: Boolean) = { s.isSensor = isSensor; this } 124 | def define = s 125 | } 126 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/joints/GearJoint.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics.joints 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | 6 | /** 7 | * A gear joint is used to connect two joints together. Either joint 8 | * can be a revolute or prismatic joint. You specify a gear ratio 9 | * to bind the motions together: 10 | * coordinate1 + ratio * coordinate2 = constant 11 | * The ratio can be negative or positive. If one joint is a revolute joint 12 | * and the other joint is a prismatic joint, then the ratio will have units 13 | * of length or units of 1/length. 14 | *
Warning: The revolute and prismatic joints must be attached to 15 | * fixed bodies (which must be body1 on those joints). 16 | */ 17 | class GearJoint(defn: GearJointDef) extends Joint(defn) { 18 | // Gear Joint: 19 | // C0 = (coordinate1 + ratio * coordinate2)_initial 20 | // C = C0 - (cordinate1 + ratio * coordinate2) = 0 21 | // Cdot = -(Cdot1 + ratio * Cdot2) 22 | // J = -[J1 ratio * J2] 23 | // K = J * invM * JT 24 | // = J1 * invM1 * J1T + ratio * ratio * J2 * invM2 * J2T 25 | // 26 | // Revolute: 27 | // coordinate = rotation 28 | // Cdot = angularVelocity 29 | // J = [0 0 1] 30 | // K = J * invM * JT = invI 31 | // 32 | // Prismatic: 33 | // coordinate = dot(p - pg, ug) 34 | // Cdot = dot(v + cross(w, r), ug) 35 | // J = [ug cross(r, ug)] 36 | // K = J * invM * JT = invMass + invI * cross(r, ug)^2 37 | 38 | val joint1 = defn.joint1 39 | val joint2 = defn.joint2 40 | assert(joint1.isInstanceOf[RevoluteJoint] || joint2.isInstanceOf[PrismaticJoint]) 41 | assert(joint2.isInstanceOf[RevoluteJoint] || joint2.isInstanceOf[PrismaticJoint]) 42 | assert(joint1.body1.isStatic) 43 | assert(joint2.body1.isStatic) 44 | 45 | val ground1 = joint1.body1 46 | val ground2 = joint2.body1 47 | val groundAnchor1 = joint1.localAnchor1 48 | val localAnchor1 = joint1.localAnchor2 49 | val groundAnchor2 = joint2.localAnchor1 50 | val localAnchor2 = joint2.localAnchor2 51 | 52 | def getCoordinate(joint: Joint) = joint match { 53 | case pj: PrismaticJoint => pj.jointTranslation 54 | case rj: RevoluteJoint => rj.jointAngle 55 | } 56 | val coordinate1 = getCoordinate(joint1) 57 | val coordinate2 = getCoordinate(joint2) 58 | 59 | val ratio = defn.ratio 60 | val constant = coordinate1 + ratio * coordinate2 61 | 62 | var J = Jacobian.Zero 63 | 64 | /** Effective mass */ 65 | var mass = 0f 66 | 67 | /** Force for accumulation/warm starting. */ 68 | var force = 0f 69 | 70 | def anchor1 = body1.toWorldPoint(localAnchor1) 71 | def anchor2 = body2.toWorldPoint(localAnchor2) 72 | // TODO_ERIN not tested 73 | def reactionForce = Vector2(force * J.linear2.x, force * J.linear2.y) 74 | def reactionTorque = { 75 | // TODO_ERIN not tested 76 | val r = body2.transform.rot * (localAnchor2 - body2.localCenter) 77 | val F = reactionForce 78 | force * J.angular2 - (r cross F) 79 | } 80 | 81 | def initVelocityConstraints(step: TimeStep) = { 82 | val g1 = ground1 83 | val g2 = ground2 84 | val b1 = body1 85 | val b2 = body2 86 | 87 | var K = 0.0f 88 | var ug = Vector2.Zero 89 | var r = Vector2.Zero 90 | var ang1 = 0f 91 | var lin1 = Vector2.Zero 92 | var ang2 = 0f 93 | var lin2 = Vector2.Zero 94 | 95 | joint1 match { 96 | case rj: RevoluteJoint => 97 | ang1 = -1 98 | K += b1.invI 99 | case pj: PrismaticJoint => 100 | ug = g1.transform.rot * pj.localXAxis1 101 | r = b1.transform.rot * (localAnchor1 - b1.localCenter) 102 | val crug = r cross ug 103 | lin1 = -ug 104 | ang1 = -crug 105 | K += b1.invMass + b1.invI * crug * crug 106 | } 107 | 108 | joint2 match { 109 | case rj: RevoluteJoint => 110 | ang2 = -ratio 111 | K += ratio * ratio * b2.invI 112 | case pj: PrismaticJoint => 113 | ug = g2.transform.rot * pj.localXAxis1 114 | r = b2.transform.rot * (localAnchor2 - b2.localCenter) 115 | val crug = r cross ug 116 | ug *= -ratio 117 | lin2 = ug 118 | ang2 = -ratio * crug 119 | K += ratio * ratio * (b2.invMass + b2.invI * crug * crug) 120 | } 121 | 122 | J = Jacobian(lin1, ang1, lin2, ang2) 123 | 124 | // Compute effective mass. 125 | assert (K > 0.0f) 126 | mass = 1.0f / K 127 | 128 | if (step.warmStarting) { 129 | // Warm starting. 130 | updateVelocities(step.dt * force) 131 | } else { 132 | force = 0.0f 133 | } 134 | } 135 | 136 | private def updateVelocities(P: Float) { 137 | body1.linearVelocity += body1.invMass * P * J.linear1 138 | body1.angularVelocity += body1.invI * P * J.angular1 139 | body2.linearVelocity += body2.invMass * P * J.linear2 140 | body2.angularVelocity += body2.invI * P * J.angular2 141 | } 142 | 143 | def solveVelocityConstraints(step: TimeStep) = { 144 | val b1 = body1 145 | val b2 = body2 146 | 147 | val Cdot = J.compute(b1.linearVelocity, b1.angularVelocity, 148 | b2.linearVelocity, b2.angularVelocity) 149 | 150 | var forceL = -step.invDt * mass * Cdot 151 | force += forceL 152 | updateVelocities(step.dt * forceL) 153 | } 154 | 155 | def solvePositionConstraints: Boolean = { 156 | var linearError = 0.0f 157 | 158 | val b1 = body1 159 | val b2 = body2 160 | 161 | val coord1 = getCoordinate(joint1) 162 | val coord2 = getCoordinate(joint2) 163 | 164 | val C = constant - (coord1 + ratio * coord2) 165 | val impulse = -mass * C 166 | 167 | b1.sweep.c += b1.invMass * impulse * J.linear1 168 | b1.sweep.a += b1.invI * impulse * J.angular1 169 | b2.sweep.c += b2.invMass * impulse * J.linear2 170 | b2.sweep.a += b2.invI * impulse * J.angular2 171 | 172 | b1.synchronizeTransform 173 | b2.synchronizeTransform 174 | 175 | linearError < Settings.linearSlop 176 | } 177 | 178 | } 179 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/draw/DebugDraw.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.draw 2 | 3 | import collision._ 4 | import vecmath._ 5 | import vecmath.Preamble._ 6 | import shapes._ 7 | import dynamics._ 8 | import dynamics.joints._ 9 | import broadphase._ 10 | 11 | object Colors { 12 | val Static = Color3f.ratio(0.5f, 0.9f, 0.5f) 13 | val Sensor = Color3f.ratio(1f, 1f, 0.4f, 0.3f) 14 | val Sleeping = Color3f.ratio(0.5f, 0.5f, 0.9f) 15 | val Dynamic = Color3f.ratio(0.9f, 0.9f, 0.9f) 16 | val Joint = Color3f.ratio(0.5f, 0.8f, 0.8f) 17 | val Core = Color3f.ratio(0.9f, 0.6f, 0.6f) 18 | val Pair = Color3f.ratio(0.9f, 0.9f, 0.3f) 19 | val AABB = Color3f(255, 255, 255) 20 | val WorldAABB = Color3f.ratio(0.3f, 0.9f, 0.9f) 21 | } 22 | 23 | object DrawFlags { 24 | /** draw shapes */ 25 | val Shapes = 0x0001 26 | /** draw joint connections */ 27 | val Joints = 0x0002 28 | /** @deprecated draw core (TOI) shapes */ 29 | val CoreShapes = 0x0004 30 | /** draw axis aligned bounding boxes */ 31 | val AABBs = 0x0008 32 | /** draw broad-phase pairs */ 33 | val Pairs = 0x0020 34 | /** draw center of mass frame */ 35 | val CenterOfMass = 0x0040 36 | /** draw sensor shapes */ 37 | val Sensors = 0x0080 38 | } 39 | 40 | /** 41 | * Draws the representation of the world, given a concrete DebugDrawHandler 42 | */ 43 | final class DebugDraw(val handler: DebugDrawHandler) { 44 | import DrawFlags._ 45 | var flags = 0 46 | 47 | def appendFlags(flags: Int) = this.flags |= flags 48 | def clearFlags(flags: Int) = this.flags &= ~flags 49 | def flag(flag: Int) = (this.flags & flag) == flag 50 | 51 | def drawDebugData(world: World) { 52 | drawDebugData(world.bodyList, world.jointList, world.broadPhase) 53 | } 54 | 55 | /** For internal use */ 56 | private def drawDebugData(bodies: Seq[Body], 57 | joints: Seq[Joint], 58 | bp: BroadPhase) { 59 | if (flag(Shapes)) bodies foreach drawBody 60 | if (flag(Joints)) joints foreach drawJoint 61 | 62 | if (flag(Pairs)) { 63 | val invQ = 1f / bp.quantizationFactor 64 | 65 | // ERKKI TODO this was done differently with the custom hash table 66 | for (((proxyId1,proxyId2),pair) <- bp.pairManager.hashTable) { 67 | val p1 = bp.proxyPool(proxyId1) 68 | val p2 = bp.proxyPool(proxyId2) 69 | 70 | val b1 = AABB( 71 | (bp.worldAABB.lowerBound.x + invQ.x * bp.m_bounds(0)(p1.lowerBounds(0)).value, 72 | bp.worldAABB.lowerBound.y + invQ.y * bp.m_bounds(1)(p1.lowerBounds(1)).value), 73 | (bp.worldAABB.lowerBound.x + invQ.x * bp.m_bounds(0)(p1.upperBounds(0)).value, 74 | bp.worldAABB.lowerBound.y + invQ.y * bp.m_bounds(1)(p1.upperBounds(1)).value)) 75 | 76 | val b2 = AABB( 77 | (bp.worldAABB.lowerBound.x + invQ.x * bp.m_bounds(0)(p2.lowerBounds(0)).value, 78 | bp.worldAABB.lowerBound.y + invQ.y * bp.m_bounds(1)(p2.lowerBounds(1)).value), 79 | (bp.worldAABB.lowerBound.x + invQ.x * bp.m_bounds(0)(p2.upperBounds(0)).value, 80 | bp.worldAABB.lowerBound.y + invQ.y * bp.m_bounds(1)(p2.upperBounds(1)).value)) 81 | 82 | val x1 = 0.5f * (b1.lowerBound + b1.upperBound) 83 | val x2 = 0.5f * (b2.lowerBound + b2.upperBound) 84 | 85 | handler.drawSegment(x1, x2, Colors.Pair) 86 | } 87 | } 88 | 89 | val worldLower = bp.worldAABB.lowerBound 90 | val worldUpper = bp.worldAABB.upperBound 91 | 92 | if (flag(AABBs)) { 93 | val invQ = 1f / bp.quantizationFactor 94 | for (i <- 0 until Settings.maxProxies) { 95 | val p = bp.proxyPool(i) 96 | if (p.isValid) { 97 | val b = AABB( 98 | (worldLower.x + invQ.x * bp.m_bounds(0)(p.lowerBounds(0)).value, 99 | worldLower.y + invQ.y * bp.m_bounds(1)(p.lowerBounds(1)).value), 100 | (worldLower.x + invQ.x * bp.m_bounds(0)(p.upperBounds(0)).value, 101 | worldLower.y + invQ.y * bp.m_bounds(1)(p.upperBounds(1)).value)) 102 | 103 | val vs: Array[Vector2] = Array( 104 | b.lowerBound, 105 | (b.upperBound.x, b.lowerBound.y), 106 | b.upperBound, 107 | (b.lowerBound.x, b.upperBound.y) 108 | ) 109 | handler.drawPolygon(vs, Colors.AABB) 110 | } 111 | } 112 | } 113 | 114 | val vsw: Array[Vector2] = Array( 115 | worldLower, 116 | (worldUpper.x, worldLower.y), 117 | worldUpper, 118 | (worldLower.x, worldUpper.y) 119 | ) 120 | handler.drawPolygon(vsw, Colors.WorldAABB) 121 | 122 | if (flag(CenterOfMass)) { 123 | for (b <- bodies) { 124 | val xf = Transform2(b.worldCenter, b.transform.rot) 125 | handler.drawTransform(xf) 126 | } 127 | } 128 | } 129 | 130 | def drawBody(body: Body) { 131 | val xf = body.transform 132 | for (f <- body.fixtures) { 133 | if (!f.isSensor || flag(Sensors)) { 134 | val color = if (f.isSensor) Colors.Sensor 135 | else if (body.isStatic) Colors.Static 136 | else if (body.isSleeping) Colors.Sleeping 137 | else Colors.Dynamic 138 | drawShape(f.shape, xf, color, flag(CoreShapes)) 139 | } 140 | } 141 | } 142 | 143 | def drawShape(shape: Shape, xf: Transform2, color: Color3f, core: Boolean) { 144 | shape match { 145 | case circle: Circle => 146 | val center = xf * circle.pos 147 | val radius = circle.radius 148 | val axis = xf.rot.col1 149 | 150 | handler.drawSolidCircle(center, radius, axis, color) 151 | 152 | if (core) { 153 | handler.drawCircle(center, radius - Settings.toiSlop, Colors.Core) 154 | } 155 | case poly: Polygon => 156 | val vertexCount = poly.vertexCount 157 | assert(vertexCount <= Settings.maxPolygonVertices) 158 | val localVertices = poly.vertices 159 | var vertices = localVertices.map(v => xf * v) 160 | 161 | handler.drawSolidPolygon(vertices, color) 162 | 163 | if (core) { 164 | val localCoreVertices = poly.coreVertices 165 | vertices = localCoreVertices.map(v => xf * v) 166 | handler.drawPolygon(vertices, Colors.Core) 167 | } 168 | case edge: Edge => 169 | val v1 = xf * edge.v1 170 | val v2 = xf * edge.v2 171 | handler.drawSegment(v1, v2, color) 172 | } 173 | } 174 | 175 | def drawJoint(joint: Joint) { 176 | val b1 = joint.body1 177 | val b2 = joint.body2 178 | val xf1 = b1.transform 179 | val xf2 = b2.transform 180 | val x1 = xf1.pos 181 | val x2 = xf2.pos 182 | val p1 = joint.anchor1 183 | val p2 = joint.anchor2 184 | 185 | joint match { 186 | case _: DistanceJoint => 187 | handler.drawSegment(p1, p2, Colors.Joint) 188 | /* if (type == JointType.PULLEY_JOINT) { 189 | PulleyJoint pulley = (PulleyJoint)joint; 190 | Vec2 s1 = pulley.getGroundAnchor1(); 191 | Vec2 s2 = pulley.getGroundAnchor2(); 192 | m_debughandler.drawSegment(s1, p1, color); 193 | m_debughandler.drawSegment(s2, p2, color); 194 | m_debughandler.drawSegment(s1, s2, color); 195 | }*/ 196 | case _: PointingDeviceJoint => 197 | handler.drawSegment(p1, p2, Colors.Joint) 198 | case _ => 199 | handler.drawSegment(x1, p1, Colors.Joint) 200 | handler.drawSegment(p1, p2, Colors.Joint) 201 | handler.drawSegment(x2, p2, Colors.Joint) 202 | } 203 | } 204 | 205 | } 206 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/collision/PolygonEdgeCollider.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.collision 2 | 3 | import vecmath._ 4 | import vecmath.Preamble._ 5 | import shapes._ 6 | 7 | object PolygonEdgeCollider extends Collider[Polygon, Edge] { 8 | 9 | def collide(polygon: Polygon, xf1: Transform2, 10 | edge: Edge, xf2: Transform2): Option[Manifold] = { 11 | val v1 = xf2 * edge.v1 12 | val v2 = xf2 * edge.v2 13 | val n = xf2.rot * edge.normal 14 | val v1Local = xf1 ** v1 15 | val v2Local = xf1 ** v2 16 | val nLocal = xf1.rot ** n 17 | 18 | var separation1 = 0f 19 | var separationIndex1 = -1 // which normal on the poly found the shallowest depth? 20 | var separationMax1 = -Float.MaxValue // the shallowest depth of edge in poly 21 | var separation2 = 0f 22 | var separationIndex2 = -1 // which normal on the poly found the shallowest depth? 23 | var separationMax2 = -Float.MaxValue // the shallowest depth of edge in poly 24 | 25 | var separationMax = -Float.MaxValue // the shallowest depth of edge in poly 26 | var separationV1 = false // is the shallowest depth from edge's v1 or v2 vertex? 27 | var separationIndex = -1 // which normal on the poly found the shallowest depth? 28 | 29 | val vertexCount = polygon.vertexCount 30 | val vertices = polygon.vertices 31 | val normals = polygon.normals 32 | 33 | var enterStartIndex = -1 // the last poly vertex above the edge 34 | var enterEndIndex = -1 // the first poly vertex below the edge 35 | var exitStartIndex = -1 // the last poly vertex below the edge 36 | var exitEndIndex = -1 // the first poly vertex above the edge 37 | //int deepestIndex; 38 | 39 | // the "N" in the following variables refers to the edge's normal. 40 | // these are projections of poly vertices along the edge's normal, 41 | // a.k.a. they are the separation of the poly from the edge. 42 | var prevSepN = 0.0f 43 | var nextSepN = 0.0f 44 | var enterSepN = 0.0f // the depth of enterEndIndex under the edge (stored as a separation, so it's negative) 45 | var exitSepN = 0.0f // the depth of exitStartIndex under the edge (stored as a separation, so it's negative) 46 | var deepestSepN = Float.MaxValue // the depth of the deepest poly vertex under the end (stored as a separation, so it's negative) 47 | 48 | // for each poly normal, get the edge's depth into the poly. 49 | // for each poly vertex, get the vertex's depth into the edge. 50 | // use these calculations to define the remaining variables declared above. 51 | prevSepN = (vertices(vertexCount - 1) - v1Local) dot nLocal 52 | 53 | for (i <- 0 until vertexCount) { 54 | separation1 = (v1Local - vertices(i)) dot normals(i) 55 | separation2 = (v2Local - vertices(i)) dot normals(i) 56 | if (separation2 < separation1) { 57 | if (separation2 > separationMax) { 58 | separationMax = separation2 59 | separationV1 = false 60 | separationIndex = i 61 | } 62 | } else { 63 | if (separation1 > separationMax) { 64 | separationMax = separation1 65 | separationV1 = true 66 | separationIndex = i 67 | } 68 | } 69 | if (separation1 > separationMax1) { 70 | separationMax1 = separation1 71 | separationIndex1 = i 72 | } 73 | if (separation2 > separationMax2) { 74 | separationMax2 = separation2 75 | separationIndex2 = i 76 | } 77 | 78 | nextSepN = (vertices(i) - v1Local) dot nLocal 79 | if (nextSepN >= 0.0f && prevSepN < 0.0f) { 80 | exitStartIndex = if (i == 0) vertexCount - 1 else i - 1 81 | exitEndIndex = i 82 | exitSepN = prevSepN 83 | } else if (nextSepN < 0.0f && prevSepN >= 0.0f) { 84 | enterStartIndex = if (i == 0) vertexCount - 1 else i - 1 85 | enterEndIndex = i 86 | enterSepN = nextSepN 87 | } 88 | if (nextSepN < deepestSepN) { 89 | deepestSepN = nextSepN; 90 | //deepestIndex = i; 91 | } 92 | prevSepN = nextSepN 93 | } 94 | 95 | if (enterStartIndex == -1) { 96 | // poly is entirely below or entirely above edge, return with no contact: 97 | return None 98 | } 99 | if (separationMax > 0.0f) { 100 | // poly is laterally disjoint with edge, return with no contact: 101 | return None 102 | } 103 | 104 | // if the poly is near a convex corner on the edge 105 | if ((separationV1 && edge.corner1Convex) || (!separationV1 && edge.corner2Convex)) { 106 | // if shallowest depth was from edge into poly, 107 | // use the edge's vertex as the contact point: 108 | if (separationMax > deepestSepN + Settings.linearSlop) { 109 | // if -normal angle is closer to adjacent edge than this edge, 110 | // let the adjacent edge handle it and return with no contact: 111 | if (separationV1) { 112 | val temp = xf1.rot ** (xf2.rot * edge.corner1Dir) 113 | if ((normals(separationIndex1) dot temp) >= 0.0f) { 114 | return None 115 | } 116 | } else { 117 | val temp = xf1.rot ** (xf2.rot * edge.corner2Dir) 118 | if ((normals(separationIndex2) dot temp) <= 0.0f) { 119 | return None 120 | } 121 | } 122 | 123 | val id = ContactID(0, separationIndex, ContactID.NullFeature, false) 124 | val normal = xf1.rot * normals(separationIndex) 125 | val (p1, p2) = if (separationV1) 126 | (v1Local, edge.v1) 127 | else 128 | (v2Local, edge.v2) 129 | val points = new Array[ManifoldPoint](1) 130 | points(0) = ManifoldPoint(p1, p2, separationMax, id) 131 | return Some(Manifold(points, normal)) 132 | } 133 | } 134 | 135 | // We're going to use the edge's normal now. 136 | val normal = -n 137 | 138 | // Check whether we only need one contact point. 139 | if (enterEndIndex == exitStartIndex) { 140 | val id = ContactID(0, enterEndIndex, ContactID.NullFeature, false) 141 | val p1 = vertices(enterEndIndex) 142 | val p2 = xf1 * p1 143 | val points = new Array[ManifoldPoint](1) 144 | points(0) = ManifoldPoint(p1, p2, enterSepN, id) 145 | return Some(Manifold(points, normal)) 146 | } 147 | 148 | // dirLocal should be the edge's direction vector, but in the frame of the polygon. 149 | val dirLocal = -nLocal.normal // TODO: figure out why this optimization didn't work 150 | //Vec2 dirLocal = XForm.mulT(xf1.R, XForm.mul(xf2.R, edge.GetDirectionVector())); 151 | 152 | val dirProj1 = dirLocal dot (vertices(enterEndIndex) - v1Local) 153 | var dirProj2 = 0.0f 154 | 155 | // The contact resolution is more robust if the two manifold points are 156 | // adjacent to each other on the polygon. So pick the first two poly 157 | // vertices that are under the edge: 158 | 159 | exitEndIndex = if (enterEndIndex == vertexCount - 1) 0 else enterEndIndex + 1 160 | if (exitEndIndex != exitStartIndex) { 161 | exitStartIndex = exitEndIndex 162 | exitSepN = nLocal dot (vertices(exitStartIndex) - v1Local) 163 | } 164 | dirProj2 = dirLocal dot (vertices(exitStartIndex) - v1Local) 165 | 166 | val points = new Array[ManifoldPoint](2) 167 | val id1 = ContactID(0, enterEndIndex, ContactID.NullFeature, false) 168 | 169 | val edgeLen = edge.length 170 | if (dirProj1 > edgeLen) { 171 | val ratio = (edgeLen - dirProj2) / (dirProj1 - dirProj2) 172 | val sep = if (ratio > 100.0f * Settings.Epsilon && ratio < 1.0f) 173 | exitSepN * (1.0f - ratio) + enterSepN * ratio 174 | else 175 | enterSepN 176 | points(0) = ManifoldPoint(v2Local, edge.v2, sep, id1) 177 | } else { 178 | val p1 = vertices(enterEndIndex) 179 | val p2 = xf2 ** (xf1 * vertices(enterEndIndex)) 180 | points(0) = ManifoldPoint(p1, p2, enterSepN, id1) 181 | } 182 | 183 | val id2 = ContactID(0, exitStartIndex, ContactID.NullFeature, false) 184 | 185 | if (dirProj2 < 0.0f) { 186 | val ratio = (-dirProj1) / (dirProj2 - dirProj1) 187 | val sep = if (ratio > 100.0f * Settings.Epsilon && ratio < 1.0f) 188 | enterSepN * (1.0f - ratio) + exitSepN * ratio 189 | else 190 | exitSepN 191 | points(1) = ManifoldPoint(v1Local, edge.v1, sep, id2) 192 | } else { 193 | val p1 = vertices(exitStartIndex) 194 | val p2 = xf2 ** (xf1 * vertices(exitStartIndex)) 195 | points(1) = ManifoldPoint(p1, p2, exitSepN, id2) 196 | } 197 | Some(Manifold(points, normal)) 198 | } 199 | 200 | } 201 | -------------------------------------------------------------------------------- /scalabox2d/src/main/scala/org/villane/box2d/dynamics/Island.scala: -------------------------------------------------------------------------------- 1 | package org.villane.box2d.dynamics 2 | 3 | import collision._ 4 | import joints._ 5 | import contacts._ 6 | import vecmath._ 7 | import vecmath.Preamble._ 8 | 9 | import collection.jcl.ArrayList 10 | 11 | /** 12 | * Handles much of the heavy lifting of physics solving - for internal use. 13 | */ 14 | class Island(val bodyCapacity: Int, 15 | val contactCapacity: Int, 16 | val jointCapacity: Int, 17 | val listener: ContactListener) { 18 | // XXX To reduce code size, replaced the three fixed-size arrays with listbuffers. Perhaps should return later? 19 | val bodies = new ArrayList[Body]//(new java.util.ArrayList[Body](bodyCapacity)) 20 | var contacts = new ArrayList[Contact]//(new java.util.ArrayList[Contact](contactCapacity)) 21 | var joints = new ArrayList[Joint]//(new java.util.ArrayList[Joint](jointCapacity)) 22 | 23 | // ERKKI: This was static, but that seemed like a bug 24 | var positionIterationCount = 0 25 | 26 | var positionError = 0f 27 | 28 | def clear() { 29 | bodies.clear() 30 | contacts.clear() 31 | joints.clear() 32 | } 33 | 34 | def add(body: Body) = bodies += body 35 | def add(contact: Contact) = contacts += contact 36 | def add(joint: Joint) = joints += joint 37 | 38 | def solve(step: TimeStep, gravity: Vector2, correctPositions: Boolean, allowSleep: Boolean) { 39 | // Integrate velocities and apply damping. 40 | for (b <- bodies) { 41 | if (!b.isStatic) { 42 | // Integrate velocities. 43 | b.linearVelocity += (gravity + b.force * b.invMass) * step.dt 44 | b.angularVelocity += step.dt * b.invI * b.torque 45 | 46 | // Reset forces. 47 | b.force = Vector2.Zero 48 | b.torque = 0f 49 | 50 | // Apply damping. 51 | // ODE: dv/dt + c * v = 0 52 | // Solution: v(t) = v0 * exp(-c * t) 53 | // Time step: v(t + dt) = v0 * exp(-c * (t + dt)) = v0 * exp(-c * t) * exp(-c * dt) = v * exp(-c * dt) 54 | // v2 = exp(-c * dt) * v1 55 | // Taylor expansion: 56 | // v2 = (1.0f - c * dt) * v1 57 | b.linearVelocity *= (1f - step.dt * b.linearDamping).clamp(0f, 1f) 58 | b.angularVelocity *= (1f - step.dt * b.angularDamping).clamp(0f, 1f) 59 | 60 | // Check for large velocities. 61 | if ((b.linearVelocity ∙ b.linearVelocity) > Settings.maxLinearVelocitySquared) { 62 | b.linearVelocity = b.linearVelocity.normalize * Settings.maxLinearVelocity 63 | } 64 | 65 | if (b.angularVelocity * b.angularVelocity > Settings.maxAngularVelocitySquared) { 66 | if (b.angularVelocity < 0f) { 67 | b.angularVelocity = -Settings.maxAngularVelocity 68 | } else { 69 | b.angularVelocity = Settings.maxAngularVelocity 70 | } 71 | } 72 | } 73 | } 74 | 75 | val contactSolver = new ContactSolver(contacts) 76 | 77 | // Initialize velocity constraints. 78 | contactSolver.initVelocityConstraints(step) 79 | 80 | for (joint <- joints) { 81 | joint.initVelocityConstraints(step) 82 | } 83 | 84 | // Solve velocity constraints. 85 | var i = 0 86 | while (i < step.maxIterations) { 87 | i += 1 88 | 89 | contactSolver.solveVelocityConstraints() 90 | 91 | var j = 0 92 | while (j < joints.length) { 93 | val joint = joints(j) 94 | j += 1 95 | 96 | joint.solveVelocityConstraints(step) 97 | } 98 | } 99 | 100 | // Post-solve (store impulses for warm starting). 101 | contactSolver.finalizeVelocityConstraints() 102 | 103 | // Integrate positions. 104 | for (b <- bodies) { 105 | if (!b.isStatic) { 106 | // Store positions for continuous collision. 107 | // TODO ERKKI : perhaps this should be capsulated as a method in body or sweep? 108 | b.sweep.c0 = b.sweep.c 109 | b.sweep.a0 = b.sweep.a 110 | 111 | // Integrate 112 | b.sweep.c += step.dt * b.linearVelocity 113 | b.sweep.a += step.dt * b.angularVelocity 114 | 115 | // Compute new transform 116 | b.synchronizeTransform() 117 | 118 | // Note: shapes are synchronized later. 119 | } 120 | } 121 | 122 | if (correctPositions) { 123 | // Initialize position constraints. 124 | // Contacts don't need initialization. 125 | for (joint <- joints) { 126 | joint.initPositionConstraints() 127 | } 128 | 129 | // Iterate over constraints. 130 | var loop = true 131 | positionIterationCount = 0 132 | while (loop && positionIterationCount < step.maxIterations) { 133 | val contactsOkay = contactSolver.solvePositionConstraints(Settings.contactBaumgarte) 134 | 135 | var jointsOkay = true 136 | // do we have to call this for all joints always? otherwise we could do 137 | // val jointsOkay = joints.forall(_.solvePositionConstraint()) 138 | for (joint <- joints) { 139 | val jointOkay = joint.solvePositionConstraints() 140 | jointsOkay &&= jointOkay 141 | } 142 | 143 | if (contactsOkay && jointsOkay) { 144 | loop = false 145 | } else { 146 | positionIterationCount += 1 147 | } 148 | } 149 | } 150 | 151 | report(contactSolver.constraints) 152 | 153 | if (allowSleep) { 154 | var minSleepTime = Float.MaxValue 155 | 156 | val linTolSqr = Settings.linearSleepTolerance * Settings.linearSleepTolerance 157 | val angTolSqr = Settings.angularSleepTolerance * Settings.angularSleepTolerance 158 | 159 | for (b <- bodies) { 160 | if (b.invMass != 0f) { 161 | if (! b.isAllowSleeping) { 162 | b.sleepTime = 0f 163 | minSleepTime = 0f 164 | } 165 | 166 | if (! b.isAllowSleeping || 167 | b.angularVelocity * b.angularVelocity > angTolSqr || 168 | (b.linearVelocity ∙ b.linearVelocity) > linTolSqr) { 169 | b.sleepTime = 0f 170 | minSleepTime = 0f 171 | } else { 172 | b.sleepTime += step.dt 173 | minSleepTime = min(minSleepTime, b.sleepTime) 174 | } 175 | } 176 | } 177 | 178 | if (minSleepTime >= Settings.timeToSleep) { 179 | for (b <- bodies) { 180 | b.flags |= BodyFlags.sleep 181 | b.linearVelocity = Vector2.Zero 182 | b.angularVelocity = 0f 183 | } 184 | } 185 | } 186 | } 187 | 188 | def solveTOI(subStep: TimeStep) { 189 | val contactSolver = new ContactSolver(contacts) 190 | 191 | // No warm starting needed for TOI events. 192 | 193 | // Solve velocity constraints. 194 | for (i <- 0 until subStep.maxIterations) { 195 | contactSolver.solveVelocityConstraints() 196 | } 197 | 198 | // Don't store the TOI contact forces for warm starting 199 | // because they can be quite large. 200 | 201 | // Integrate positions. 202 | for (b <- bodies) { 203 | if (!b.isStatic) { 204 | //System.out.println("(Island::SolveTOI 1) :"+b.m_sweep); 205 | // Store positions for continuous collision. 206 | b.sweep.c0 = b.sweep.c 207 | b.sweep.a0 = b.sweep.a 208 | 209 | // Integrate 210 | b.sweep.c += subStep.dt * b.linearVelocity 211 | b.sweep.a += subStep.dt * b.angularVelocity 212 | 213 | //System.out.println("(Island::SolveTOI 2) :"+b.m_sweep); 214 | // Compute new transform 215 | b.synchronizeTransform(); 216 | 217 | // System.out.println("(Island::SolveTOI 3) :"+b.m_sweep); 218 | // Note: shapes are synchronized later. 219 | } 220 | } 221 | 222 | // Solve position constraints. 223 | val k_toiBaumgarte = 0.75f 224 | var contactsOkay = false 225 | var i = 0 226 | while (!contactsOkay && i < subStep.maxIterations) { 227 | val contactsOkay = contactSolver.solvePositionConstraints(k_toiBaumgarte) 228 | i += 1 229 | } 230 | 231 | report(contactSolver.constraints) 232 | } 233 | 234 | def report(constraints: Array[ContactConstraint]): Unit = { 235 | if (listener == null) { 236 | return 237 | } 238 | 239 | var i = 0 240 | while (i < contacts.length) { 241 | val c = contacts(i) 242 | val cc = constraints(i) 243 | i += 1 244 | 245 | val f1 = c.fixture1 246 | val f2 = c.fixture2 247 | val b1 = f1.body 248 | for (manifold <- c.manifolds) { 249 | for (k <- 0 until manifold.points.length) { 250 | val point = manifold.points(k) 251 | val ccp = cc.points(k) 252 | val pos = b1.transform * point.localPoint1 253 | 254 | // TOI constraint results are not stored, so get 255 | // the result from the constraint. 256 | val cr = new ContactResult(f1, f2, 257 | pos, manifold.normal, 258 | ccp.normalImpulse, ccp.tangentImpulse, 259 | point.id) 260 | listener.result(cr) 261 | } 262 | } 263 | } 264 | } 265 | 266 | } 267 | --------------------------------------------------------------------------------