Streamline KBox2D api by removing unnecessary interfaces

This commit is contained in:
DBotThePony 2022-02-20 14:32:53 +07:00
parent 721d2a2029
commit 27a870fcf0
Signed by: DBot
GPG Key ID: DCC23B5715498507
32 changed files with 1187 additions and 1360 deletions

View File

@ -148,353 +148,3 @@ enum class BodyFlags(val bitmask: Int) {
return other and bitmask.inv() return other and bitmask.inv()
} }
} }
interface IBody {
/** Creates a fixture and attach it to this body. Use this function if you need
* to set some fixture parameters, like friction. Otherwise you can create the
* fixture directly from a shape.
* If the density is non-zero, this function automatically updates the mass of the body.
* Contacts are not created until the next time step.
* @param def the fixture definition.
* @warning This function is locked during callbacks.
*/
fun createFixture(def: FixtureDef): IFixture
/**
* Creates a fixture from a shape and attach it to this body.
* This is a convenience function. Use b2FixtureDef if you need to set parameters
* like friction, restitution, user data, or filtering.
* If the density is non-zero, this function automatically updates the mass of the body.
* @param shape the shape to be cloned.
* @param density the shape density (set to zero for static bodies).
* @warning This function is locked during callbacks.
*/
fun createFixture(shape: IShape<*>, density: Double): IFixture {
return createFixture(
FixtureDef(
shape = shape,
density = density
)
)
}
/**
* Destroy a fixture. This removes the fixture from the broad-phase and
* destroys all contacts associated with this fixture. This will
* automatically adjust the mass of the body if the body is dynamic and the
* fixture has positive density.
* All fixtures attached to a body are implicitly destroyed when the body is destroyed.
* @param fixture the fixture to be removed.
* @warning This function is locked during callbacks.
*/
fun destroyFixture(fixture: IFixture)
/**
* Set the position of the body's origin and rotation.
* Manipulating a body's transform may cause non-physical behavior.
* Note: contacts are updated on the next call to b2World::Step.
* @param position the world position of the body's local origin.
* @param angle the world rotation in radians.
*/
fun setTransform(position: Vector2d, angle: Double)
/**
* Get the body transform for the body's origin.
* @return the world transform of the body's origin.
*/
val transform: Transform
/**
* Get the world body origin position.
* @return the world position of the body's origin.
*/
val position: Vector2d
/**
* Get the angle in radians.
* @return the current world rotation angle in radians.
*/
val angle: Double
/**
* Get the world position of the center of mass.
*/
val worldCenter: Vector2d
/**
* Get the local position of the center of mass.
*/
val localCenter: Vector2d
/**
* The linear velocity of the body's origin in world co-ordinates.
*/
var linearVelocity: Vector2d
/**
* The angular velocity of the body.
*/
var angularVelocity: Double
/**
* Apply a force at a world point. If the force is not
* applied at the center of mass, it will generate a torque and
* affect the angular velocity. This wakes up the body.
* @param force the world force vector, usually in Newtons (N).
* @param point the world position of the point of application.
* @param wake also wake up the body
*/
fun applyForce(force: Vector2d, point: Vector2d, wake: Boolean = true)
/**
* Apply a force to the center of mass. This wakes up the body.
* @param force the world force vector, usually in Newtons (N).
* @param wake also wake up the body
*/
fun applyForceToCenter(force: Vector2d, wake: Boolean = true)
/**
* Apply a torque. This affects the angular velocity
* without affecting the linear velocity of the center of mass.
* @param torque about the z-axis (out of the screen), usually in N-m.
* @param wake also wake up the body
*/
fun applyTorque(torque: Double, wake: Boolean = true)
/**
* Apply an impulse at a point. This immediately modifies the velocity.
* It also modifies the angular velocity if the point of application
* is not at the center of mass. This wakes up the body.
* @param impulse the world impulse vector, usually in N-seconds or kg-m/s.
* @param point the world position of the point of application.
* @param wake also wake up the body
*/
fun applyLinearImpulse(impulse: Vector2d, point: Vector2d, wake: Boolean = true)
/**
* Apply an impulse to the center of mass. This immediately modifies the velocity.
* @param impulse the world impulse vector, usually in N-seconds or kg-m/s.
* @param wake also wake up the body
*/
fun applyLinearImpulseToCenter(impulse: Vector2d, wake: Boolean = true)
/**
* Apply an angular impulse.
* @param impulse the angular impulse in units of kg*m*m/s
* @param wake also wake up the body
*/
fun applyAngularImpulse(impulse: Double, wake: Boolean = true)
/**
* Get the total mass of the body.
* @return the mass, usually in kilograms (kg).
*/
val mass: Double
/**
* Get the rotational inertia of the body about the local origin.
* @return the rotational inertia, usually in kg-m^2.
*/
val inertia: Double
/**
* Get the mass data of the body.
* @return a struct containing the mass, inertia and center of the body.
* Set the mass properties to override the mass properties of the fixtures.
* Note that this changes the center of mass position.
* Note that creating or destroying fixtures can also alter the mass.
* This function has no effect if the body isn't dynamic.
* @param data the mass properties.
*/
var massData: MassData
/**
* This resets the mass properties to the sum of the mass properties of the fixtures.
* This normally does not need to be called unless you called SetMassData to override
* the mass and you later want to reset the mass.
*/
fun resetMassData()
/**
* Get the world coordinates of a point given the local coordinates.
* @param localPoint a point on the body measured relative the the body's origin.
* @return the same point expressed in world coordinates.
*/
fun getWorldPoint(localPoint: Vector2d): Vector2d
/**
* Get the world coordinates of a vector given the local coordinates.
* @param localVector a vector fixed in the body.
* @return the same vector expressed in world coordinates.
*/
fun getWorldVector(localPoint: Vector2d): Vector2d
/**
* Gets a local point relative to the body's origin given a world point.
* @param worldPoint a point in world coordinates.
* @return the corresponding local point relative to the body's origin.
*/
fun getLocalPoint(worldPoint: Vector2d): Vector2d
/**
* Gets a local vector given a world vector.
* @param worldVector a vector in world coordinates.
* @return the corresponding local vector.
*/
fun getLocalVector(worldVector: Vector2d): Vector2d
/**
* Get the world linear velocity of a world point attached to this body.
* @param worldPoint a point in world coordinates.
* @return the world velocity of a point.
*/
fun getLinearVelocityFromWorldPoint(worldPoint: Vector2d): Vector2d
/**
* Get the world velocity of a local point.
* @param localPoint a point in local coordinates.
* @return the world velocity of a point.
*/
fun getLinearVelocityFromLocalPoint(localPoint: Vector2d): Vector2d
var linearDamping: Double
var angularDamping: Double
var gravityScale: Double
/**
* Set the type of this body. This may alter the mass and velocity.
*/
var type: BodyType
/**
* Should this body be treated like a bullet for continuous collision detection?
*/
var isBullet: Boolean
/**
* You can disable sleeping on this body. If you disable sleeping, the
* body will be woken.
*/
var allowAutoSleep: Boolean
/**
* Set the sleep state of the body. A sleeping body has very
* low CPU cost.
* @param flag set to true to wake the body, false to put it to sleep.
*/
var isAwake: Boolean
/**
* Allow a body to be disabled. A disabled body is not simulated and cannot
* be collided with or woken up.
*
* If you pass a flag of true, all fixtures will be added to the broad-phase.
*
* If you pass a flag of false, all fixtures will be removed from the
* broad-phase and all contacts will be destroyed.
*
* Fixtures and joints are otherwise unaffected. You may continue
* to create/destroy fixtures and joints on disabled bodies.
*
* Fixtures on a disabled body are implicitly disabled and will
* not participate in collisions, ray-casts, or queries.
*
* Joints connected to a disabled body are implicitly disabled.
*
* An diabled body is still owned by a b2World object and remains
* in the body list.
*/
var isEnabled: Boolean
/**
* Set this body to have fixed rotation. This causes the mass to be reset.
*/
var isFixedRotation: Boolean
/**
* Get the list of all fixtures attached to this body.
*
* Kotlin: This is not a list, but, as in C++ impl, a custom
* linked list.
*/
val fixtureList: IFixture?
val fixtureIterator: Iterator<IFixture> get() {
return object : Iterator<IFixture> {
private var node = fixtureList
override fun hasNext(): Boolean {
return node != null
}
override fun next(): IFixture {
val old = node!!
node = old.next
return old
}
}
}
/**
* Get the list of all joints attached to this body.
*
* Kotlin: This is not a list, but, as in C++ impl, a custom
* linked list.
*/
val jointList: JointEdge?
val jointIterator: Iterator<JointEdge> get() {
return object : Iterator<JointEdge> {
private var node = jointList
override fun hasNext(): Boolean {
return node != null
}
override fun next(): JointEdge {
val old = node!!
node = old.next
return old
}
}
}
/**
* Get the list of all contacts attached to this body.
* @warning this list changes during the time step and you may
* miss some collisions if you don't use b2ContactListener.
*
* Kotlin: This is not a list, but, as in C++ impl, a custom
* linked list.
*/
val contactEdge: ContactEdge?
val contactEdgeIterator: Iterator<ContactEdge> get() {
return object : Iterator<ContactEdge> {
private var node = contactEdge
override fun hasNext(): Boolean {
return node != null
}
override fun next(): ContactEdge {
val old = node!!
node = old.next
return old
}
}
}
val next: IBody?
val prev: IBody?
val userData: Any?
/**
* Get the parent world of this body.
*/
val world: IB2World
/// Dump this body to a file
fun dump()
}

View File

@ -1,47 +0,0 @@
package ru.dbotthepony.kbox2d.api
typealias b2Pair = Pair<Int, Int>
const val e_nullProxy = -1
/**
* The broad-phase is used for computing pairs and performing volume queries and ray casts.
* This broad-phase does not persist pairs. Instead, this reports potentially new pairs.
* It is up to the client to consume the new pairs and to track subsequent overlap.
*/
interface IBroadPhase : IProxieable, IMovable {
/**
* Call to trigger a re-processing of it's pairs on the next call to UpdatePairs.
*/
fun touchProxy(proxyID: Int)
/**
* Test overlap of fat AABBs.
*/
fun testOverlap(proxyIDA: Int, proxyIDB: Int): Boolean
/**
* Get the number of proxies.
*/
val proxyCount: Int
/**
* Update the pairs. This results in pair callbacks. This can only add pairs.
*/
fun updatePairs(callback: (Any?, Any?) -> Unit)
/**
* Get the height of the embedded tree.
*/
val treeHeight: Int
/**
* Get the balance of the embedded tree.
*/
val treeBalance: Int
/**
* Get the quality metric of the embedded tree.
*/
val treeQuality: Double
}

View File

@ -1,5 +1,7 @@
package ru.dbotthepony.kbox2d.api package ru.dbotthepony.kbox2d.api
import ru.dbotthepony.kbox2d.dynamics.Body
import ru.dbotthepony.kbox2d.dynamics.contact.AbstractContact
import kotlin.math.sqrt import kotlin.math.sqrt
/// Friction mixing law. The idea is to allow either fixture to drive the friction to zero. /// Friction mixing law. The idea is to allow either fixture to drive the friction to zero.
@ -31,8 +33,8 @@ data class ContactRegister(
* nodes, one for each attached body. * nodes, one for each attached body.
*/ */
data class ContactEdge( data class ContactEdge(
val other: ru.dbotthepony.kbox2d.api.IBody, ///< provides quick access to the other body attached. val other: Body, ///< provides quick access to the other body attached.
val contact: IContact, ///< the contact val contact: AbstractContact, ///< the contact
var prev: ContactEdge? = null, ///< the previous contact edge in the body's contact list var prev: ContactEdge? = null, ///< the previous contact edge in the body's contact list
var next: ContactEdge? = null, ///< the next contact edge in the body's contact list var next: ContactEdge? = null, ///< the next contact edge in the body's contact list
) )
@ -68,79 +70,3 @@ enum class ContactFlags(val bitmask: Int) {
return other and bitmask.inv() return other and bitmask.inv()
} }
} }
/**
* The class manages contact between two shapes. A contact exists for each overlapping
* AABB in the broad-phase (except if filtered). Therefore a contact object may exist
* that has no contact points.
*/
interface IContact {
/// Get the contact manifold. Do not modify the manifold unless you understand the
/// internals of Box2D.
val manifold: Manifold
/// Get the world manifold.
val worldManifold: IWorldManifold
/// Is this contact touching?
val isTouching: Boolean
/// Enable/disable this contact. This can be used inside the pre-solve
/// contact listener. The contact is only disabled for the current
/// time step (or sub-step in continuous collisions).
var isEnabled: Boolean
/// Get the next contact in the world's contact list.
val next: IContact?
val prev: IContact?
/// Get fixture A in this contact.
val fixtureA: IFixture
/// Get the child primitive index for fixture A.
val childIndexA: Int
/// Get fixture B in this contact.
val fixtureB: IFixture
/// Get the child primitive index for fixture B.
val childIndexB: Int
/// Override the default friction mixture. You can call this in b2ContactListener::PreSolve.
/// This value persists until set or reset.
/// Get the friction.
var friction: Double
/// Reset the friction mixture to the default value.
fun resetFriction() {
friction = b2MixFriction(fixtureA.friction, fixtureB.friction)
}
fun flagForFiltering()
/// Override the default restitution mixture. You can call this in b2ContactListener::PreSolve.
/// The value persists until you set or reset.
var restitution: Double
/// Reset the restitution to the default value.
fun resetRestitution() {
restitution = b2MixRestitution(fixtureA.restitution, fixtureB.restitution)
}
/// Override the default restitution velocity threshold mixture. You can call this in b2ContactListener::PreSolve.
/// The value persists until you set or reset.
/// Get the restitution threshold.
var restitutionThreshold: Double
/// Reset the restitution threshold to the default value.
fun resetRestitutionThreshold() {
restitutionThreshold = b2MixRestitutionThreshold(fixtureA.restitutionThreshold, fixtureB.restitutionThreshold)
}
/// Set the desired tangent speed for a conveyor belt behavior. In meters per second.
/// Get the desired tangent speed. In meters per second.
var tangentSpeed: Double
/// Evaluate this contact with your own manifold and transforms.
fun evaluate(xfA: Transform, xfB: Transform): Manifold
}

View File

@ -1,37 +0,0 @@
package ru.dbotthepony.kbox2d.api
/**
* Delegate of b2World.
*/
interface IContactManager {
fun addPair(proxyUserDataA: Any?, proxyUserDataB: Any?)
fun findNewContacts()
fun destroy(contact: IContact)
fun collide()
val broadPhase: IBroadPhase
var contactList: IContact?
val contactListIterator: Iterator<IContact> get() {
return object : Iterator<IContact> {
private var node = contactList
override fun hasNext(): Boolean {
return node != null
}
override fun next(): IContact {
val old = node!!
node = old.next
return old
}
}
}
val contactCount: Int
var contactFilter: IContactFilter?
var contactListener: IContactListener?
}

View File

@ -1,26 +1,8 @@
package ru.dbotthepony.kbox2d.api package ru.dbotthepony.kbox2d.api
import ru.dbotthepony.kbox2d.collision.DistanceProxy
import ru.dbotthepony.kvector.vector.ndouble.Vector2d import ru.dbotthepony.kvector.vector.ndouble.Vector2d
/**
* A distance proxy is used by the GJK algorithm.
* It encapsulates any shape.
*/
interface IDistanceProxy {
val vertices: List<Vector2d>
val radius: Double
/**
* Get the supporting vertex index in the given direction.
*/
fun getSupport(d: Vector2d): Int
/**
* Get the supporting vertex in the given direction.
*/
fun getSupportVertex(d: Vector2d): Vector2d
}
/** /**
* Used to warm start b2Distance. * Used to warm start b2Distance.
* Set count to zero on first call. * Set count to zero on first call.
@ -38,8 +20,8 @@ data class SimplexCache(
* in the computation. Even * in the computation. Even
*/ */
data class DistanceInput( data class DistanceInput(
var proxyA: IDistanceProxy, var proxyA: DistanceProxy,
var proxyB: IDistanceProxy, var proxyB: DistanceProxy,
var transformA: Transform = Transform(), var transformA: Transform = Transform(),
var transformB: Transform = Transform(), var transformB: Transform = Transform(),
var useRadii: Boolean = false var useRadii: Boolean = false
@ -57,8 +39,8 @@ data class DistanceOutput(
) )
data class ShapeCastInput( data class ShapeCastInput(
var proxyA: IDistanceProxy, var proxyA: DistanceProxy,
var proxyB: IDistanceProxy, var proxyB: DistanceProxy,
var transformA: Transform, var transformA: Transform,
var transformB: Transform, var transformB: Transform,
var translationB: Vector2d, var translationB: Vector2d,

View File

@ -1,58 +0,0 @@
package ru.dbotthepony.kbox2d.api
import ru.dbotthepony.kbox2d.collision.DynamicTree
import ru.dbotthepony.kbox2d.collision.b2_nullNode
import ru.dbotthepony.kvector.util2d.AABB
private const val DEBUG_CYCLIC_REFERENCES = true
/// A node in the dynamic tree. The client does not interact with this directly.
data class TreeNode(
val tree: DynamicTree,
/// Enlarged AABB
var aabb: AABB? = null,
var child1: Int = 0,
var child2: Int = 0,
// leaf = 0, free node = -1
var height: Int = 0,
var moved: Boolean = false,
var userData: Any? = null,
) {
val isLeaf get() = child1 == b2_nullNode
// union
private var _union: Int = b2_nullNode
var parent by this::_union
var next by this::_union
}
interface IDynamicTree : IProxieable, IMovable {
fun wasMoved(proxyID: Int): Boolean
fun clearMoved(proxyID: Int)
/**
* Validate this tree. For testing.
*/
fun validate()
/**
* Compute the height of the binary tree in O(N) time. Should not be
* called often.
*/
val height: Int
/// Get the maximum balance of an node in the tree. The balance is the difference
/// in height of the two children of a node.
val maxBalance: Int
/// Get the ratio of the sum of the node areas to the root area.
val getAreaRatio: Double
/// Build an optimal tree. Very expensive. For testing.
fun rebuildBottomUp()
}

View File

@ -1,5 +1,7 @@
package ru.dbotthepony.kbox2d.api package ru.dbotthepony.kbox2d.api
import ru.dbotthepony.kbox2d.collision.e_nullProxy
import ru.dbotthepony.kbox2d.dynamics.Fixture
import ru.dbotthepony.kvector.util2d.AABB import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.vector.ndouble.Vector2d import ru.dbotthepony.kvector.vector.ndouble.Vector2d
@ -40,26 +42,38 @@ data class ImmutableFilter(
) : IFilter ) : IFilter
data class FixtureDef( data class FixtureDef(
/// The shape, this must be set. The shape will be cloned. /**
* The shape, this must be set. The shape will be cloned.
*/
var shape: IShape<*>? = null, var shape: IShape<*>? = null,
var friction: Double = 0.2, var friction: Double = 0.2,
/// The restitution (elasticity) usually in the range [0,1]. /**
* The restitution (elasticity) usually in the range [0,1].
*/
var restitution: Double = 0.0, var restitution: Double = 0.0,
/// Restitution velocity threshold, usually in m/s. Collisions above this /**
/// speed have restitution applied (will bounce). * Restitution velocity threshold, usually in m/s. Collisions above this
* speed have restitution applied (will bounce).
*/
var restitutionThreshold: Double = 1.0 * b2_lengthUnitsPerMeter, var restitutionThreshold: Double = 1.0 * b2_lengthUnitsPerMeter,
/// The density, usually in kg/m^2. /**
* The density, usually in kg/m^2.
*/
var density: Double = 0.0, var density: Double = 0.0,
/// A sensor shape collects contact information but never generates a collision /**
/// response. * A sensor shape collects contact information but never generates a collision
* response.
*/
var isSensor: Boolean = false, var isSensor: Boolean = false,
/// Use this to store application specific fixture data. /**
* Use this to store application specific fixture data.
*/
var userData: Any? = null, var userData: Any? = null,
val filter: Filter = Filter() val filter: Filter = Filter()
@ -67,7 +81,7 @@ data class FixtureDef(
data class FixtureProxy( data class FixtureProxy(
var aabb: AABB, var aabb: AABB,
val fixture: IFixture, val fixture: Fixture,
val childIndex: Int, val childIndex: Int,
) { ) {
private var setProxyID = false private var setProxyID = false
@ -83,99 +97,3 @@ data class FixtureProxy(
throw IllegalStateException("FixtureProxy should be immutable (tried to set value $value, already having $field)") throw IllegalStateException("FixtureProxy should be immutable (tried to set value $value, already having $field)")
} }
} }
/**
* A fixture is used to attach a shape to a body for collision detection. A fixture
* inherits its transform from its parent. Fixtures hold additional non-geometric data
* such as friction, collision filters, etc.
* Fixtures are created via b2Body::CreateFixture.
* @warning you cannot reuse fixtures.
*/
interface IFixture {
/// Get the type of the child shape. You can use this to down cast to the concrete shape.
/// @return the shape type.
val type: IShape.Type
/// Get the child shape. You can modify the child shape, however you should not change the
/// number of vertices because this will crash some collision caching mechanisms.
/// Manipulating the shape may lead to non-physical behavior.
val shape: IShape<*>
/// Set if this fixture is a sensor.
/// Is this fixture a sensor (non-solid)?
/// @return the true if the shape is a sensor.
var isSensor: Boolean
/// Set the contact filtering data. This will not update contacts until the next time
/// step when either parent body is active and awake.
/// This automatically calls Refilter.
var filter: IFilter
/// Call this if you want to establish collision that was previously disabled by b2ContactFilter::ShouldCollide.
fun refilter()
/// Get the parent body of this fixture. This is nullptr if the fixture is not attached.
/// @return the parent body.
val body: ru.dbotthepony.kbox2d.api.IBody?
/// Get the next fixture in the parent body's fixture list.
/// @return the next shape.
val next: IFixture?
/// Get the user data that was assigned in the fixture definition. Use this to
/// store your application specific data.
val userData: Any?
/// Test a point for containment in this fixture.
/// @param p a point in world coordinates.
fun testPoint(p: Vector2d): Boolean {
return shape.testPoint(checkNotNull(body) { "Tried to use detached fixture" }.transform, p)
}
/// Cast a ray against this shape.
/// @param output the ray-cast results.
/// @param input the ray-cast input parameters.
/// @param childIndex the child shape index (e.g. edge index)
fun rayCast(input: RayCastInput, childIndex: Int): RayCastOutput {
return shape.rayCast(input, checkNotNull(body) { "Tried to use detached fixture" }.transform, childIndex)
}
/// Get the mass data for this fixture. The mass data is based on the density and
/// the shape. The rotational inertia is about the shape's origin. This operation
/// may be expensive.
fun getMassData(): MassData {
return shape.computeMass(density)
}
/// Set the density of this fixture. This will _not_ automatically adjust the mass
/// of the body. You must call b2Body::ResetMassData to update the body's mass.
/// Get the density of this fixture.
var density: Double
/// Get the coefficient of friction.
/// Set the coefficient of friction. This will _not_ change the friction of
/// existing contacts.
var friction: Double
/// Get the coefficient of restitution.
/// Set the coefficient of restitution. This will _not_ change the restitution of
/// existing contacts.
var restitution: Double
/// Get the restitution velocity threshold.
/// Set the restitution threshold. This will _not_ change the restitution threshold of
/// existing contacts.
var restitutionThreshold: Double
/// Get the fixture's AABB. This AABB may be enlarge and/or stale.
/// If you need a more accurate AABB, compute it using the shape and
/// the body transform.
fun getAABB(childIndex: Int): AABB {
return proxies[childIndex].aabb
}
/// Dump this fixture to the log file. (Log4J)
fun dump(childIndex: Int) { }
val proxies: List<FixtureProxy>
}

View File

@ -2,6 +2,8 @@ package ru.dbotthepony.kbox2d.api
import ru.dbotthepony.kvector.util2d.AABB import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.vector.ndouble.Vector2d import ru.dbotthepony.kvector.vector.ndouble.Vector2d
import ru.dbotthepony.kbox2d.collision.DynamicTree
import ru.dbotthepony.kbox2d.collision.BroadPhase
fun interface ProxyQueryCallback { fun interface ProxyQueryCallback {
fun invoke(nodeId: Int, userData: Any?): Boolean fun invoke(nodeId: Int, userData: Any?): Boolean
@ -11,33 +13,33 @@ fun interface ProxyRayCastCallback {
fun invoke(subInput: RayCastInput, nodeId: Int, userData: Any?): Double fun invoke(subInput: RayCastInput, nodeId: Int, userData: Any?): Double
} }
sealed interface IProxieable { interface IProxieable {
/** /**
* For [IBroadPhase]: * For [BroadPhase]:
* Create a proxy with an initial AABB. Pairs are not reported until * Create a proxy with an initial AABB. Pairs are not reported until
* UpdatePairs is called. * UpdatePairs is called.
* *
* For [IDynamicTree]: * For [DynamicTree]:
* Create a proxy. Provide a tight fitting AABB and a userData pointer. * Create a proxy. Provide a tight fitting AABB and a userData pointer.
*/ */
fun createProxy(aabb: AABB, userData: Any?): Int fun createProxy(aabb: AABB, userData: Any?): Int
/** /**
* For [IBroadPhase]: * For [BroadPhase]:
* Destroy a proxy. It is up to the client to remove any pairs. * Destroy a proxy. It is up to the client to remove any pairs.
* *
* For [IDynamicTree]: * For [DynamicTree]:
* Destroy a proxy. This asserts if the id is invalid. * Destroy a proxy. This asserts if the id is invalid.
*/ */
fun destroyProxy(proxyID: Int) fun destroyProxy(proxyID: Int)
/** /**
* For [IBroadPhase]: * For [BroadPhase]:
* Call MoveProxy as many times as you like, then when you are done * Call MoveProxy as many times as you like, then when you are done
* call UpdatePairs to finalized the proxy pairs (for your time step). * call UpdatePairs to finalized the proxy pairs (for your time step).
* @return true * @return true
* *
* For [IDynamicTree]: * For [DynamicTree]:
* Move a proxy with a swepted AABB. If the proxy has moved outside of its fattened AABB, * Move a proxy with a swepted AABB. If the proxy has moved outside of its fattened AABB,
* then the proxy is removed from the tree and re-inserted. Otherwise * then the proxy is removed from the tree and re-inserted. Otherwise
* the function returns immediately. * the function returns immediately.

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.kbox2d.api package ru.dbotthepony.kbox2d.api
import ru.dbotthepony.kbox2d.dynamics.Body
import ru.dbotthepony.kbox2d.dynamics.joint.AbstractJoint import ru.dbotthepony.kbox2d.dynamics.joint.AbstractJoint
import ru.dbotthepony.kvector.vector.ndouble.Vector2d import ru.dbotthepony.kvector.vector.ndouble.Vector2d
import kotlin.math.PI import kotlin.math.PI
@ -12,8 +13,8 @@ data class StiffnessResult(val stiffness: Double, val damping: Double)
fun b2LinearStiffness( fun b2LinearStiffness(
frequencyHertz: Double, frequencyHertz: Double,
dampingRatio: Double, dampingRatio: Double,
bodyA: IBody?, bodyA: Body?,
bodyB: IBody?, bodyB: Body?,
): StiffnessResult { ): StiffnessResult {
val massA = bodyA?.mass ?: 0.0 val massA = bodyA?.mass ?: 0.0
val massB = bodyB?.mass ?: 0.0 val massB = bodyB?.mass ?: 0.0
@ -41,8 +42,8 @@ fun b2LinearStiffness(
fun b2AngularStiffness( fun b2AngularStiffness(
frequencyHertz: Double, frequencyHertz: Double,
dampingRatio: Double, dampingRatio: Double,
bodyA: IBody?, bodyA: Body?,
bodyB: IBody?, bodyB: Body?,
): StiffnessResult { ): StiffnessResult {
val inertiaA = bodyA?.inertia ?: 0.0 val inertiaA = bodyA?.inertia ?: 0.0
val inertiaB = bodyB?.inertia ?: 0.0 val inertiaB = bodyB?.inertia ?: 0.0
@ -91,13 +92,13 @@ data class Jacobian(
* nodes, one for each attached body. * nodes, one for each attached body.
*/ */
class JointEdge( class JointEdge(
other: IBody?, ///< provides quick access to the other body attached. other: Body?, ///< provides quick access to the other body attached.
val joint: IJoint, ///< the joint val joint: AbstractJoint, ///< the joint
var prev: JointEdge? = null, ///< the previous joint edge in the body's joint list var prev: JointEdge? = null, ///< the previous joint edge in the body's joint list
var next: JointEdge? = null ///< the next joint edge in the body's joint list var next: JointEdge? = null ///< the next joint edge in the body's joint list
) { ) {
val otherNullable: IBody? = other val otherNullable: Body? = other
val other: IBody get() = checkNotNull(otherNullable) { "Other body is not present" } val other: Body get() = checkNotNull(otherNullable) { "Other body is not present" }
} }
sealed interface IJointDef { sealed interface IJointDef {
@ -109,12 +110,12 @@ sealed interface IJointDef {
/** /**
* The first attached body. * The first attached body.
*/ */
val bodyA: IBody? val bodyA: Body?
/** /**
* The second attached body. * The second attached body.
*/ */
val bodyB: IBody? val bodyB: Body?
/** /**
* Set this flag to true if the attached bodies should collide. * Set this flag to true if the attached bodies should collide.
@ -128,8 +129,8 @@ sealed interface IJointDef {
} }
class DistanceJointDef( class DistanceJointDef(
b1: IBody, b1: Body,
b2: IBody, b2: Body,
anchor1: Vector2d, anchor1: Vector2d,
anchor2: Vector2d anchor2: Vector2d
) : IJointDef { ) : IJointDef {
@ -160,8 +161,8 @@ class DistanceJointDef(
*/ */
val damping: Double = 0.0 val damping: Double = 0.0
override var bodyA: IBody = b1 override var bodyA: Body = b1
override var bodyB: IBody = b2 override var bodyB: Body = b2
override var collideConnected: Boolean = false override var collideConnected: Boolean = false
override var userData: Any? = null override var userData: Any? = null
@ -185,13 +186,13 @@ class DistanceJointDef(
} }
class RevoluteJointDef( class RevoluteJointDef(
b1: IBody, b1: Body,
b2: IBody, b2: Body,
anchor: Vector2d, anchor: Vector2d,
) : IJointDef { ) : IJointDef {
override val type: JointType = JointType.REVOLUTE override val type: JointType = JointType.REVOLUTE
override var bodyA: IBody = b1 override var bodyA: Body = b1
override var bodyB: IBody = b2 override var bodyB: Body = b2
override var collideConnected: Boolean = false override var collideConnected: Boolean = false
override var userData: Any? = null override var userData: Any? = null
@ -244,14 +245,14 @@ class RevoluteJointDef(
} }
class PrismaticJointDef( class PrismaticJointDef(
b1: IBody, b1: Body,
b2: IBody, b2: Body,
anchor: Vector2d, anchor: Vector2d,
axis: Vector2d, axis: Vector2d,
) : IJointDef { ) : IJointDef {
override val type: JointType = JointType.PRISMATIC override val type: JointType = JointType.PRISMATIC
override var bodyA: IBody = b1 override var bodyA: Body = b1
override var bodyB: IBody = b2 override var bodyB: Body = b2
override var collideConnected: Boolean = false override var collideConnected: Boolean = false
override var userData: Any? = null override var userData: Any? = null
@ -311,8 +312,8 @@ class PrismaticJointDef(
* two dynamic body anchor points, and a pulley ratio. * two dynamic body anchor points, and a pulley ratio.
*/ */
class PulleyJointDef( class PulleyJointDef(
b1: IBody, b1: Body,
b2: IBody, b2: Body,
/** /**
* The first ground anchor in world coordinates. This point never moves. * The first ground anchor in world coordinates. This point never moves.
@ -329,8 +330,8 @@ class PulleyJointDef(
ratio: Double, ratio: Double,
) : IJointDef { ) : IJointDef {
override val type: JointType = JointType.PULLEY override val type: JointType = JointType.PULLEY
override var bodyA: IBody = b1 override var bodyA: Body = b1
override var bodyB: IBody = b2 override var bodyB: Body = b2
override var collideConnected: Boolean = false override var collideConnected: Boolean = false
override var userData: Any? = null override var userData: Any? = null
@ -375,8 +376,8 @@ class PulleyJointDef(
* @warning bodyB on the input joints must both be dynamic * @warning bodyB on the input joints must both be dynamic
*/ */
class GearJointDef( class GearJointDef(
override var bodyA: IBody, override var bodyA: Body,
override var bodyB: IBody, override var bodyB: Body,
/** /**
* The first revolute/prismatic joint attached to the gear joint. * The first revolute/prismatic joint attached to the gear joint.
@ -416,8 +417,8 @@ class MouseJointDef(
*/ */
var target: Vector2d, var target: Vector2d,
override var bodyB: IBody, override var bodyB: Body,
override val bodyA: IBody? = null, override val bodyA: Body? = null,
/** /**
* The maximum constraint force that can be exerted * The maximum constraint force that can be exerted
@ -436,8 +437,8 @@ class MouseJointDef(
fun linearStiffness( fun linearStiffness(
frequencyHertz: Double, frequencyHertz: Double,
dampingRatio: Double, dampingRatio: Double,
bodyA: IBody?, bodyA: Body?,
bodyB: IBody? bodyB: Body?
): MouseJointDef { ): MouseJointDef {
val (stiffness, damping) = b2LinearStiffness(frequencyHertz, dampingRatio, bodyA, bodyB) val (stiffness, damping) = b2LinearStiffness(frequencyHertz, dampingRatio, bodyA, bodyB)
this.stiffness = stiffness this.stiffness = stiffness
@ -448,8 +449,8 @@ class MouseJointDef(
fun angularStiffness( fun angularStiffness(
frequencyHertz: Double, frequencyHertz: Double,
dampingRatio: Double, dampingRatio: Double,
bodyA: IBody?, bodyA: Body?,
bodyB: IBody? bodyB: Body?
): MouseJointDef { ): MouseJointDef {
val (stiffness, damping) = b2AngularStiffness(frequencyHertz, dampingRatio, bodyA, bodyB) val (stiffness, damping) = b2AngularStiffness(frequencyHertz, dampingRatio, bodyA, bodyB)
this.stiffness = stiffness this.stiffness = stiffness
@ -467,8 +468,8 @@ class MouseJointDef(
* anchors and a local axis helps when saving and loading a game. * anchors and a local axis helps when saving and loading a game.
*/ */
class WheelJointDef( class WheelJointDef(
override var bodyA: IBody, override var bodyA: Body,
override var bodyB: IBody, override var bodyB: Body,
anchor: Vector2d, anchor: Vector2d,
axis: Vector2d, axis: Vector2d,
@ -534,8 +535,8 @@ class WheelJointDef(
fun linearStiffness( fun linearStiffness(
frequencyHertz: Double, frequencyHertz: Double,
dampingRatio: Double, dampingRatio: Double,
bodyA: IBody = this.bodyA, bodyA: Body = this.bodyA,
bodyB: IBody = this.bodyB bodyB: Body = this.bodyB
): WheelJointDef { ): WheelJointDef {
val (stiffness, damping) = b2LinearStiffness(frequencyHertz, dampingRatio, bodyA, bodyB) val (stiffness, damping) = b2LinearStiffness(frequencyHertz, dampingRatio, bodyA, bodyB)
this.stiffness = stiffness this.stiffness = stiffness
@ -546,8 +547,8 @@ class WheelJointDef(
fun angularStiffness( fun angularStiffness(
frequencyHertz: Double, frequencyHertz: Double,
dampingRatio: Double, dampingRatio: Double,
bodyA: IBody = this.bodyA, bodyA: Body = this.bodyA,
bodyB: IBody = this.bodyB bodyB: Body = this.bodyB
): WheelJointDef { ): WheelJointDef {
val (stiffness, damping) = b2AngularStiffness(frequencyHertz, dampingRatio, bodyA, bodyB) val (stiffness, damping) = b2AngularStiffness(frequencyHertz, dampingRatio, bodyA, bodyB)
this.stiffness = stiffness this.stiffness = stiffness
@ -562,8 +563,8 @@ class WheelJointDef(
* of the anchor points is important for computing the reaction torque. * of the anchor points is important for computing the reaction torque.
*/ */
class WeldJointDef( class WeldJointDef(
override var bodyA: IBody, override var bodyA: Body,
override var bodyB: IBody, override var bodyB: Body,
anchor: Vector2d, anchor: Vector2d,
/** /**
@ -598,8 +599,8 @@ class WeldJointDef(
fun linearStiffness( fun linearStiffness(
frequencyHertz: Double, frequencyHertz: Double,
dampingRatio: Double, dampingRatio: Double,
bodyA: IBody = this.bodyA, bodyA: Body = this.bodyA,
bodyB: IBody = this.bodyB bodyB: Body = this.bodyB
): WeldJointDef { ): WeldJointDef {
val (stiffness, damping) = b2LinearStiffness(frequencyHertz, dampingRatio, bodyA, bodyB) val (stiffness, damping) = b2LinearStiffness(frequencyHertz, dampingRatio, bodyA, bodyB)
this.stiffness = stiffness this.stiffness = stiffness
@ -610,8 +611,8 @@ class WeldJointDef(
fun angularStiffness( fun angularStiffness(
frequencyHertz: Double, frequencyHertz: Double,
dampingRatio: Double, dampingRatio: Double,
bodyA: IBody = this.bodyA, bodyA: Body = this.bodyA,
bodyB: IBody = this.bodyB bodyB: Body = this.bodyB
): WeldJointDef { ): WeldJointDef {
val (stiffness, damping) = b2AngularStiffness(frequencyHertz, dampingRatio, bodyA, bodyB) val (stiffness, damping) = b2AngularStiffness(frequencyHertz, dampingRatio, bodyA, bodyB)
this.stiffness = stiffness this.stiffness = stiffness
@ -624,8 +625,8 @@ class WeldJointDef(
* Friction joint definition. * Friction joint definition.
*/ */
class FrictionJointDef( class FrictionJointDef(
override var bodyA: IBody, override var bodyA: Body,
override var bodyB: IBody, override var bodyB: Body,
anchor: Vector2d, anchor: Vector2d,
/** /**
@ -655,8 +656,8 @@ class FrictionJointDef(
} }
class MotorJointDef( class MotorJointDef(
override var bodyA: IBody, override var bodyA: Body,
override var bodyB: IBody, override var bodyB: Body,
/** /**
* The maximum motor force in N. * The maximum motor force in N.
@ -692,77 +693,3 @@ class MotorJointDef(
field = value field = value
} }
} }
interface IJoint : IMovable {
/**
* Get the type of the concrete joint.
*/
val type: JointType
/**
* Get the first body attached to this joint.
*/
val bodyA: IBody
/**
* Get the second body attached to this joint.
*/
val bodyB: IBody
/**
* Get the anchor point on bodyA in world coordinates.
*/
val anchorA: Vector2d
/**
* Get the anchor point on bodyB in world coordinates.
*/
val anchorB: Vector2d
/**
* Get the reaction force on bodyB at the joint anchor in Newtons.
*/
fun getReactionForce(inv_dt: Double): Vector2d
/**
* Get the reaction torque on bodyB in N*m.
*/
fun getReactionTorque(inv_dt: Double): Double
/**
* Get the next joint the world joint list.
*
* Kotlin: This is not a list, but, as in C++ impl, a custom
* linked list.
*/
val next: IJoint?
val prev: IJoint?
fun destroy() {
bodyA.world.destroyJoint(this)
}
/**
* Short-cut function to determine if either body is enabled.
*/
val isEnabled: Boolean get() = bodyA.isEnabled || bodyB.isEnabled
/**
* Get collide connected.
* Note: modifying the collide connect flag won't work correctly because
* the flag is only checked when fixture AABBs begin to overlap.
*/
val collideConnected: Boolean
/**
* Dump this joint to the log file.
*/
fun dump() { }
/**
* Debug draw this joint
*/
fun draw(draw: IDebugDraw) { }
var userData: Any?
}

View File

@ -9,9 +9,12 @@ data class MassData(
val inertia: Double, val inertia: Double,
) )
/// A shape is used for collision detection. You can create a shape however you like.
/// Shapes used for simulation in b2World are created automatically when a b2Fixture /**
/// is created. Shapes may encapsulate a one or more child shapes. * A shape is used for collision detection. You can create a shape however you like.
* Shapes used for simulation in b2World are created automatically when a b2Fixture
* is created. Shapes may encapsulate a one or more child shapes.
*/
interface IShape<S : IShape<S>> { interface IShape<S : IShape<S>> {
enum class Type { enum class Type {
CIRCLE, CIRCLE,
@ -20,41 +23,58 @@ interface IShape<S : IShape<S>> {
CHAIN, CHAIN,
} }
/// Clone the concrete shape. /**
* Clone the concrete shape.
*/
fun copy(): S fun copy(): S
/// Get the type of this shape. You can use this to down cast to the concrete shape. /**
/// @return the shape type. * Get the type of this shape. You can use this to down cast to the concrete shape.
* @return the shape type.
*/
val type: Type val type: Type
/// Get the number of child primitives. /**
* Get the number of child primitives.
*/
val childCount: Int val childCount: Int
/// Test a point for containment in this shape. This only works for convex shapes.
/// @param xf the shape world transform. /**
/// @param p a point in world coordinates. * Test a point for containment in this shape. This only works for convex shapes.
* @param xf the shape world transform.
* @param p a point in world coordinates.
*/
fun testPoint(transform: Transform, p: Vector2d): Boolean { return false } fun testPoint(transform: Transform, p: Vector2d): Boolean { return false }
/// Cast a ray against a child shape. /**
/// @param output the ray-cast results. * Cast a ray against a child shape.
/// @param input the ray-cast input parameters. * @param output the ray-cast results.
/// @param transform the transform to be applied to the shape. * @param input the ray-cast input parameters.
/// @param childIndex the child shape index * @param transform the transform to be applied to the shape.
* @param childIndex the child shape index
*/
fun rayCast(input: RayCastInput, transform: Transform, childIndex: Int): RayCastOutput fun rayCast(input: RayCastInput, transform: Transform, childIndex: Int): RayCastOutput
/// Given a transform, compute the associated axis aligned bounding box for a child shape. /**
/// @param aabb returns the axis aligned box. * Given a transform, compute the associated axis aligned bounding box for a child shape.
/// @param xf the world transform of the shape. * @param aabb returns the axis aligned box.
/// @param childIndex the child shape * @param xf the world transform of the shape.
* @param childIndex the child shape
*/
fun computeAABB(transform: Transform, childIndex: Int): AABB fun computeAABB(transform: Transform, childIndex: Int): AABB
/// Compute the mass properties of this shape using its dimensions and density. /**
/// The inertia tensor is computed about the local origin. * Compute the mass properties of this shape using its dimensions and density.
/// @param massData returns the mass data for this shape. * The inertia tensor is computed about the local origin.
/// @param density the density in kilograms per meter squared. * @param massData returns the mass data for this shape.
* @param density the density in kilograms per meter squared.
*/
fun computeMass(density: Double): MassData fun computeMass(density: Double): MassData
/// Radius of a shape. For polygonal shapes this must be b2_polygonRadius. There is no support for /**
/// making rounded polygons. * Radius of a shape. For polygonal shapes this must be b2_polygonRadius. There is no support for
* making rounded polygons.
*/
var radius: Double var radius: Double
} }

View File

@ -1,2 +0,0 @@
package ru.dbotthepony.kbox2d.api

View File

@ -1,11 +1,13 @@
package ru.dbotthepony.kbox2d.api package ru.dbotthepony.kbox2d.api
import ru.dbotthepony.kbox2d.collision.DistanceProxy
/** /**
* Input parameters for b2TimeOfImpact * Input parameters for b2TimeOfImpact
*/ */
data class TOIInput( data class TOIInput(
var proxyA: IDistanceProxy, var proxyA: DistanceProxy,
var proxyB: IDistanceProxy, var proxyB: DistanceProxy,
var sweepA: Sweep, var sweepA: Sweep,
var sweepB: Sweep, var sweepB: Sweep,
var tMax: Double, // defines sweep interval [0, tMax] var tMax: Double, // defines sweep interval [0, tMax]

View File

@ -1,245 +0,0 @@
package ru.dbotthepony.kbox2d.api
import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
/**
* The world class manages all physics entities, dynamic simulation,
* and asynchronous queries. The world also contains efficient memory
* management facilities.
*/
interface IB2World : IMovable {
/**
* Register a destruction listener. The listener is owned by you and must
* remain in scope.
*/
var destructionListener: IDestructionListener?
/**
* Register a contact filter to provide specific control over collision.
* Otherwise the default filter is used (b2_defaultFilter). The listener is
* owned by you and must remain in scope.
*/
var contactFilter: IContactFilter?
/**
* Register a contact event listener. The listener is owned by you and must
* remain in scope.
*/
var contactListener: IContactListener?
/**
* Register a routine for debug drawing. The debug draw functions are called
* inside with b2World::DebugDraw method. The debug draw object is owned
* by you and must remain in scope.
*/
var debugDraw: IDebugDraw?
/**
* Create a rigid body given a definition. No reference to the definition
* is retained.
* @warning This function is locked during callbacks.
*/
fun createBody(bodyDef: ru.dbotthepony.kbox2d.api.BodyDef): ru.dbotthepony.kbox2d.api.IBody
/**
* Destroy a rigid body given a definition. No reference to the definition
* is retained. This function is locked during callbacks.
* @warning This automatically deletes all associated shapes and joints.
* @warning This function is locked during callbacks.
*/
fun destroyBody(body: ru.dbotthepony.kbox2d.api.IBody)
/**
* Create a joint to constrain bodies together. No reference to the definition
* is retained. This may cause the connected bodies to cease colliding.
* @warning This function is locked during callbacks.
*/
fun createJoint(jointDef: IJointDef): IJoint
/**
* Destroy a joint. This may cause the connected bodies to begin colliding.
* @warning This function is locked during callbacks.
*/
fun destroyJoint(joint: IJoint)
/**
* Take a time step. This performs collision detection, integration,
* and constraint solution.
* @param dt the amount of time to simulate, this should not vary.
* @param velocityIterations for the velocity constraint solver.
* @param positionIterations for the position constraint solver.
*/
fun step(dt: Double, velocityIterations: Int, positionIterations: Int)
/**
* Manually clear the force buffer on all bodies. By default, forces are cleared automatically
* after each call to Step. The default behavior is modified by calling SetAutoClearForces.
* The purpose of this function is to support sub-stepping. Sub-stepping is often used to maintain
* a fixed sized time step under a variable frame-rate.
* When you perform sub-stepping you will disable auto clearing of forces and instead call
* ClearForces after all sub-steps are complete in one pass of your game loop.
* @see SetAutoClearForces
*/
fun clearForces()
/**
* Call this to draw shapes and other debug draw data. This is intentionally non-const.
*/
fun debugDraw()
/**
* Query the world for all fixtures that potentially overlap the
* provided AABB.
* @param callback a user implemented callback class.
* @param aabb the query box.
*/
fun queryAABB(aabb: AABB, callback: IQueryCallback)
/**
* Ray-cast the world for all fixtures in the path of the ray. Your callback
* controls whether you get the closest point, any point, or n-points.
* The ray-cast ignores shapes that contain the starting point.
* @param callback a user implemented callback class.
* @param point1 the ray starting point
* @param point2 the ray ending point
*/
fun rayCast(point1: Vector2d, point2: Vector2d, callback: IRayCastCallback)
/**
* Get the world body list. With the returned body, use b2Body::GetNext to get
* the next body in the world list. A nullptr body indicates the end of the list.
* @return the head of the world body list.
*/
val bodyList: ru.dbotthepony.kbox2d.api.IBody?
val bodyListIterator: Iterator<ru.dbotthepony.kbox2d.api.IBody> get() {
return object : Iterator<ru.dbotthepony.kbox2d.api.IBody> {
private var node = bodyList
override fun hasNext(): Boolean {
return node != null
}
override fun next(): ru.dbotthepony.kbox2d.api.IBody {
val old = node!!
node = old.next
check(node != old) { "Hard loop detected at $old" }
return old
}
}
}
/**
* Get the world joint list. With the returned joint, use b2Joint::GetNext to get
* the next joint in the world list. A nullptr joint indicates the end of the list.
* @return the head of the world joint list.
*/
val jointList: IJoint?
val jointListIterator: Iterator<IJoint> get() {
return object : Iterator<IJoint> {
private var node = jointList
override fun hasNext(): Boolean {
return node != null
}
override fun next(): IJoint {
val old = node!!
node = old.next
check(node != old) { "Hard loop detected at $old" }
return old
}
}
}
/**
* Get the world contact list. With the returned contact, use b2Contact::GetNext to get
* the next contact in the world list. A nullptr contact indicates the end of the list.
* @return the head of the world contact list.
* @warning contacts are created and destroyed in the middle of a time step.
* Use b2ContactListener to avoid missing contacts.
*/
val contactList: IContact? get() = contactManager.contactList
val contactListIterator: Iterator<IContact> get() {
return object : Iterator<IContact> {
private var node = contactList
override fun hasNext(): Boolean {
return node != null
}
override fun next(): IContact {
val old = node!!
node = old.next
check(node != old) { "Hard loop detected at $old" }
return old
}
}
}
/**
* Enable/disable sleep.
*/
var allowAutoSleep: Boolean
/**
* Enable/disable warm starting. For testing.
*/
var warmStarting: Boolean
/**
* Enable/disable continuous physics. For testing.
*/
var continuousPhysics: Boolean
/**
* Enable/disable single stepped continuous physics. For testing.
*/
var enableSubStepping: Boolean
val proxyCount: Int get() = contactManager.broadPhase.proxyCount
val bodyCount: Int
val jointCount: Int
val contactCount: Int get() = contactManager.contactCount
val treeHeight: Int get() = contactManager.broadPhase.treeHeight
val treeBalance: Int get() = contactManager.broadPhase.treeBalance
/**
* Get the quality metric of the dynamic tree. The smaller the better.
* The minimum is 1.
*/
val treeQuality: Double get() = contactManager.broadPhase.treeQuality
/**
* Change the global gravity vector.
*/
var gravity: Vector2d
/**
* Is the world locked (in the middle of a time step).
*/
val isLocked: Boolean
/**
* Set flag to control automatic clearing of forces after each time step.
*/
var autoClearForces: Boolean
/**
* Get the current profile.
*/
val profileData: ru.dbotthepony.kbox2d.api.IProfileData
/**
* Dump the world into the log file.
* @warning this should be called outside of a time step.
*/
fun dump()
val contactManager: IContactManager
fun notifyNewContacts()
}

View File

@ -1,5 +1,8 @@
package ru.dbotthepony.kbox2d.api package ru.dbotthepony.kbox2d.api
import ru.dbotthepony.kbox2d.dynamics.Fixture
import ru.dbotthepony.kbox2d.dynamics.contact.AbstractContact
import ru.dbotthepony.kbox2d.dynamics.joint.AbstractJoint
import ru.dbotthepony.kvector.vector.ndouble.Vector2d import ru.dbotthepony.kvector.vector.ndouble.Vector2d
/** /**
@ -11,7 +14,7 @@ interface IContactFilter {
* Return true if contact calculations should be performed between these two shapes. * Return true if contact calculations should be performed between these two shapes.
* @warning for performance reasons this is only called when the AABBs begin to overlap. * @warning for performance reasons this is only called when the AABBs begin to overlap.
*/ */
fun shouldCollide(fixtureA: IFixture, fixtureB: IFixture): Boolean fun shouldCollide(fixtureA: Fixture, fixtureB: Fixture): Boolean
} }
/** /**
@ -24,13 +27,13 @@ interface IDestructionListener {
* Called when any joint is about to be destroyed due * Called when any joint is about to be destroyed due
* to the destruction of one of its attached bodies. * to the destruction of one of its attached bodies.
*/ */
fun sayGoodbye(joint: IJoint) fun sayGoodbye(joint: AbstractJoint)
/** /**
* Called when any fixture is about to be destroyed due * Called when any fixture is about to be destroyed due
* to the destruction of its parent body. * to the destruction of its parent body.
*/ */
fun sayGoodbye(fixture: IFixture) fun sayGoodbye(fixture: Fixture)
} }
/** /**
@ -60,12 +63,12 @@ interface IContactListener {
/** /**
* Called when two fixtures begin to touch. * Called when two fixtures begin to touch.
*/ */
fun beginContact(contact: IContact) fun beginContact(contact: AbstractContact)
/** /**
* Called when two fixtures cease to touch. * Called when two fixtures cease to touch.
*/ */
fun endContact(contact: IContact) fun endContact(contact: AbstractContact)
/** /**
* This is called after a contact is updated. This allows you to inspect a * This is called after a contact is updated. This allows you to inspect a
@ -84,7 +87,7 @@ interface IContactListener {
* get an EndContact callback. However, you may get a BeginContact callback * get an EndContact callback. However, you may get a BeginContact callback
* the next step. * the next step.
*/ */
fun preSolve(contact: IContact, oldManifold: Manifold) fun preSolve(contact: AbstractContact, oldManifold: Manifold)
/** /**
* This lets you inspect a contact after the solver is finished. This is useful * This lets you inspect a contact after the solver is finished. This is useful
@ -96,7 +99,7 @@ interface IContactListener {
* *
* Note: this is only called for contacts that are touching, solid, and awake. * Note: this is only called for contacts that are touching, solid, and awake.
*/ */
fun postSolve(contact: IContact, impulse: ContactImpulse) fun postSolve(contact: AbstractContact, impulse: ContactImpulse)
} }
/** /**
@ -108,7 +111,7 @@ fun interface IQueryCallback {
* Called for each fixture found in the query AABB. * Called for each fixture found in the query AABB.
* @return false to terminate the query. * @return false to terminate the query.
*/ */
fun reportFixture(fixture: IFixture): Boolean fun reportFixture(fixture: Fixture): Boolean
} }
/** /**
@ -135,5 +138,5 @@ fun interface IRayCastCallback {
* @return -1 to filter, 0 to terminate, fraction to clip the ray for * @return -1 to filter, 0 to terminate, fraction to clip the ray for
* closest hit, 1 to continue * closest hit, 1 to continue
*/ */
fun reportFixture(fixture: IFixture, point: Vector2d, normal: Vector2d, fraction: Double): Double fun reportFixture(fixture: Fixture, point: Vector2d, normal: Vector2d, fraction: Double): Double
} }

View File

@ -4,6 +4,10 @@ import ru.dbotthepony.kbox2d.api.*
import ru.dbotthepony.kvector.util2d.AABB import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.vector.ndouble.Vector2d import ru.dbotthepony.kvector.vector.ndouble.Vector2d
typealias b2Pair = Pair<Int, Int>
const val e_nullProxy = -1
private class IntArrayList { private class IntArrayList {
private var buffer = IntArray(16) private var buffer = IntArray(16)
var size: Int = 0 var size: Int = 0
@ -33,17 +37,28 @@ private class IntArrayList {
} }
} }
class BroadPhase : IBroadPhase { /**
* The broad-phase is used for computing pairs and performing volume queries and ray casts.
* This broad-phase does not persist pairs. Instead, this reports potentially new pairs.
* It is up to the client to consume the new pairs and to track subsequent overlap.
*/
class BroadPhase : IProxieable, IMovable {
private val moveBuffer = IntArrayList() private val moveBuffer = IntArrayList()
private val pairBuffer = ArrayList<b2Pair>() private val pairBuffer = ArrayList<b2Pair>()
private var moveCount = 0 private var moveCount = 0
private val tree = DynamicTree() private val tree = DynamicTree()
override var proxyCount: Int = 0 /**
* Get the number of proxies.
*/
var proxyCount: Int = 0
private set private set
override fun touchProxy(proxyID: Int) { /**
* Call to trigger a re-processing of it's pairs on the next call to UpdatePairs.
*/
fun touchProxy(proxyID: Int) {
bufferMove(proxyID) bufferMove(proxyID)
} }
@ -107,16 +122,28 @@ class BroadPhase : IBroadPhase {
return tree.getFatAABB(proxyID) return tree.getFatAABB(proxyID)
} }
override val treeHeight: Int /**
* Get the height of the embedded tree.
*/
val treeHeight: Int
get() = tree.height get() = tree.height
override val treeBalance: Int /**
* Get the balance of the embedded tree.
*/
val treeBalance: Int
get() = tree.maxBalance get() = tree.maxBalance
override val treeQuality: Double /**
* Get the quality metric of the embedded tree.
*/
val treeQuality: Double
get() = tree.getAreaRatio get() = tree.getAreaRatio
override fun testOverlap(proxyIDA: Int, proxyIDB: Int): Boolean { /**
* Test overlap of fat AABBs.
*/
fun testOverlap(proxyIDA: Int, proxyIDB: Int): Boolean {
return tree.getFatAABB(proxyIDA).intersect(tree.getFatAABB(proxyIDB)) return tree.getFatAABB(proxyIDA).intersect(tree.getFatAABB(proxyIDB))
} }
@ -143,7 +170,10 @@ class BroadPhase : IBroadPhase {
return true return true
} }
override fun updatePairs(callback: (Any?, Any?) -> Unit) { /**
* Update the pairs. This results in pair callbacks. This can only add pairs.
*/
fun updatePairs(callback: (Any?, Any?) -> Unit) {
pairBuffer.clear() pairBuffer.clear()
for (i in 0 until moveCount) { for (i in 0 until moveCount) {

View File

@ -17,9 +17,13 @@ var b2_gjkIters = 0
var b2_gjkMaxIters = 0 var b2_gjkMaxIters = 0
private set private set
class DistanceProxy : IDistanceProxy { /**
override val vertices: List<Vector2d> * A distance proxy is used by the GJK algorithm.
override val radius: Double * It encapsulates any shape.
*/
class DistanceProxy {
val vertices: List<Vector2d>
val radius: Double
constructor(shape: IShape<*>, index: Int) { constructor(shape: IShape<*>, index: Int) {
when (shape.type) { when (shape.type) {
@ -63,7 +67,10 @@ class DistanceProxy : IDistanceProxy {
this.radius = radius this.radius = radius
} }
override fun getSupport(d: Vector2d): Int { /**
* Get the supporting vertex index in the given direction.
*/
fun getSupport(d: Vector2d): Int {
var bestIndex = 0 var bestIndex = 0
var bestValue = b2Dot(vertices[0], d) var bestValue = b2Dot(vertices[0], d)
@ -79,7 +86,10 @@ class DistanceProxy : IDistanceProxy {
return bestIndex return bestIndex
} }
override fun getSupportVertex(d: Vector2d): Vector2d { /**
* Get the supporting vertex in the given direction.
*/
fun getSupportVertex(d: Vector2d): Vector2d {
return vertices[getSupport(d)] return vertices[getSupport(d)]
} }
} }
@ -116,9 +126,9 @@ class Simplex() {
constructor( constructor(
cache: SimplexCache, cache: SimplexCache,
proxyA: IDistanceProxy, proxyA: DistanceProxy,
transformA: Transform, transformA: Transform,
proxyB: IDistanceProxy, proxyB: DistanceProxy,
transformB: Transform, transformB: Transform,
) : this() { ) : this() {
check(cache.count <= 3) check(cache.count <= 3)
@ -413,8 +423,8 @@ private const val k_maxIters = 20
*/ */
fun b2Distance( fun b2Distance(
cache: SimplexCache, cache: SimplexCache,
proxyA: IDistanceProxy, proxyA: DistanceProxy,
proxyB: IDistanceProxy, proxyB: DistanceProxy,
transformA: Transform, transformA: Transform,
transformB: Transform, transformB: Transform,
useRadii: Boolean = false useRadii: Boolean = false
@ -553,8 +563,8 @@ private const val gjk_tolerance = 0.5 * b2_linearSlop
*/ */
fun b2ShapeCast( fun b2ShapeCast(
output: ShapeCastOutput, output: ShapeCastOutput,
proxyA: IDistanceProxy, proxyA: DistanceProxy,
proxyB: IDistanceProxy, proxyB: DistanceProxy,
xfA: Transform, xfA: Transform,
xfB: Transform, xfB: Transform,
r: Vector2d, r: Vector2d,

View File

@ -5,9 +5,36 @@ import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.vector.ndouble.Vector2d import ru.dbotthepony.kvector.vector.ndouble.Vector2d
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
/**
* A node in the dynamic tree. The client does not interact with this directly.
*/
private data class TreeNode(
val tree: DynamicTree,
/// Enlarged AABB
var aabb: AABB? = null,
var child1: Int = 0,
var child2: Int = 0,
// leaf = 0, free node = -1
var height: Int = 0,
var moved: Boolean = false,
var userData: Any? = null,
) {
val isLeaf get() = child1 == b2_nullNode
// union
private var _union: Int = b2_nullNode
var parent by this::_union
var next by this::_union
}
const val b2_nullNode = -1 const val b2_nullNode = -1
class DynamicTree : IDynamicTree { class DynamicTree : IProxieable, IMovable {
private var root: Int = b2_nullNode private var root: Int = b2_nullNode
private val nodeCapacity get() = nodes.size private val nodeCapacity get() = nodes.size
@ -15,7 +42,7 @@ class DynamicTree : IDynamicTree {
private var freeList = 0 private var freeList = 0
private var insertionCount = 0 private var insertionCount = 0
internal var nodes = Array(16) { TreeNode(this) } private var nodes = Array(16) { TreeNode(this) }
init { init {
// Build a linked list for the free list. // Build a linked list for the free list.
@ -162,11 +189,11 @@ class DynamicTree : IDynamicTree {
return nodes[proxyID].userData return nodes[proxyID].userData
} }
override fun wasMoved(proxyID: Int): Boolean { fun wasMoved(proxyID: Int): Boolean {
return nodes[proxyID].moved return nodes[proxyID].moved
} }
override fun clearMoved(proxyID: Int) { fun clearMoved(proxyID: Int) {
nodes[proxyID].moved = false nodes[proxyID].moved = false
} }
@ -476,9 +503,16 @@ class DynamicTree : IDynamicTree {
return iA return iA
} }
override val height: Int get() = if (root == b2_nullNode) 0 else nodes[root].height /**
* Compute the height of the binary tree in O(N) time. Should not be
* called often.
*/
val height: Int get() = if (root == b2_nullNode) 0 else nodes[root].height
override val getAreaRatio: Double get() { /**
* Get the ratio of the sum of the node areas to the root area.
*/
val getAreaRatio: Double get() {
if (root == b2_nullNode) if (root == b2_nullNode)
return 0.0 return 0.0
@ -562,7 +596,10 @@ class DynamicTree : IDynamicTree {
validateMetrics(child2) validateMetrics(child2)
} }
override fun validate() { /**
* Validate this tree. For testing.
*/
fun validate() {
validateStructure(root) validateStructure(root)
validateMetrics(root) validateMetrics(root)
@ -578,7 +615,11 @@ class DynamicTree : IDynamicTree {
check(nodeCount + freeCount == nodeCapacity) check(nodeCount + freeCount == nodeCapacity)
} }
override val maxBalance: Int get() { /**
* Get the maximum balance of an node in the tree. The balance is the difference
* in height of the two children of a node.
*/
val maxBalance: Int get() {
var maxBalance = 0 var maxBalance = 0
for (node in nodes) { for (node in nodes) {
@ -596,7 +637,10 @@ class DynamicTree : IDynamicTree {
return maxBalance return maxBalance
} }
override fun rebuildBottomUp() { /**
* Build an optimal tree. Very expensive. For testing.
*/
fun rebuildBottomUp() {
TODO("Not Yet Implemented") TODO("Not Yet Implemented")
} }

View File

@ -34,8 +34,8 @@ private data class MinSeparationResult(
private class SeparationFunction( private class SeparationFunction(
cache: SimplexCache, cache: SimplexCache,
val proxyA: IDistanceProxy, val proxyA: DistanceProxy,
val proxyB: IDistanceProxy, val proxyB: DistanceProxy,
val sweepA: Sweep, val sweepA: Sweep,
val sweepB: Sweep, val sweepB: Sweep,
t1: Double t1: Double
@ -228,8 +228,8 @@ const val k_maxIterations = 20
* Note: use [b2Distance] to compute the contact point and normal at the time of impact. * Note: use [b2Distance] to compute the contact point and normal at the time of impact.
*/ */
fun b2TimeOfImpact( fun b2TimeOfImpact(
proxyA: IDistanceProxy, proxyA: DistanceProxy,
proxyB: IDistanceProxy, proxyB: DistanceProxy,
_sweepA: Sweep, _sweepA: Sweep,
_sweepB: Sweep, _sweepB: Sweep,
tMax: Double, // defines sweep interval [0, tMax] tMax: Double, // defines sweep interval [0, tMax]

View File

@ -1,3 +1,6 @@
@file:Suppress("unused")
package ru.dbotthepony.kbox2d.dynamics package ru.dbotthepony.kbox2d.dynamics
import ru.dbotthepony.kbox2d.api.* import ru.dbotthepony.kbox2d.api.*
@ -15,30 +18,141 @@ import ru.dbotthepony.kvector.vector.Color
import ru.dbotthepony.kvector.vector.ndouble.Vector2d import ru.dbotthepony.kvector.vector.ndouble.Vector2d
import ru.dbotthepony.kvector.vector.ndouble.times import ru.dbotthepony.kvector.vector.ndouble.times
class B2World(override var gravity: Vector2d) : IB2World { /**
override var bodyCount: Int = 0 * The world class manages all physics entities, dynamic simulation,
* and asynchronous queries. The world also contains efficient memory
* management facilities.
*/
class B2World(
/**
* Change the global gravity vector.
*/
var gravity: Vector2d
) : IMovable {
var bodyCount: Int = 0
private set private set
override var jointCount: Int = 0 var jointCount: Int = 0
private set private set
override val contactManager: IContactManager = ContactManager() val contactManager = ContactManager()
override var destructionListener: IDestructionListener? = null /**
override var contactFilter: IContactFilter? by contactManager::contactFilter * Get the world contact list. With the returned contact, use b2Contact::GetNext to get
override var contactListener: IContactListener? by contactManager::contactListener * the next contact in the world list. A nullptr contact indicates the end of the list.
* @return the head of the world contact list.
* @warning contacts are created and destroyed in the middle of a time step.
* Use b2ContactListener to avoid missing contacts.
*/
val contactList: AbstractContact? get() = contactManager.contactList
val contactListIterator: Iterator<AbstractContact> get() = contactManager.contactListIterator
override var debugDraw: IDebugDraw? = null /**
* Register a destruction listener. The listener is owned by you and must
* remain in scope.
*/
var destructionListener: IDestructionListener? = null
override var bodyList: IBody? = null /**
private set * Register a contact filter to provide specific control over collision.
override var jointList: IJoint? = null * Otherwise the default filter is used (b2_defaultFilter). The listener is
* owned by you and must remain in scope.
*/
var contactFilter: IContactFilter? by contactManager::contactFilter
/**
* Register a contact event listener. The listener is owned by you and must
* remain in scope.
*/
var contactListener: IContactListener? by contactManager::contactListener
/**
* Register a routine for debug drawing. The debug draw functions are called
* inside with b2World::DebugDraw method. The debug draw object is owned
* by you and must remain in scope.
*/
var debugDraw: IDebugDraw? = null
/**
* Get the world body list. With the returned body, use b2Body::GetNext to get
* the next body in the world list. A nullptr body indicates the end of the list.
* @return the head of the world body list.
*/
var bodyList: Body? = null
private set private set
override var warmStarting: Boolean = true /**
override var continuousPhysics: Boolean = true * Get the world joint list. With the returned joint, use b2Joint::GetNext to get
override var enableSubStepping: Boolean = false * the next joint in the world list. A nullptr joint indicates the end of the list.
* @return the head of the world joint list.
*/
var jointList: AbstractJoint? = null
private set
override var allowAutoSleep: Boolean = true val bodyListIterator: Iterator<Body> get() {
return object : Iterator<Body> {
private var node = bodyList
override fun hasNext(): Boolean {
return node != null
}
override fun next(): Body {
val old = node!!
node = old.next
check(node != old) { "Hard loop detected at $old" }
return old
}
}
}
val jointListIterator: Iterator<AbstractJoint> get() {
return object : Iterator<AbstractJoint> {
private var node = jointList
override fun hasNext(): Boolean {
return node != null
}
override fun next(): AbstractJoint {
val old = node!!
node = old.next
check(node != old) { "Hard loop detected at $old" }
return old
}
}
}
/**
* Enable/disable warm starting. For testing.
*/
var warmStarting: Boolean = true
/**
* Enable/disable continuous physics. For testing.
*/
var continuousPhysics: Boolean = true
/**
* Enable/disable single stepped continuous physics. For testing.
*/
var enableSubStepping: Boolean = false
/**
* Get the quality metric of the dynamic tree. The smaller the better.
* The minimum is 1.
*/
val treeQuality: Double get() = contactManager.broadPhase.treeQuality
val proxyCount: Int get() = contactManager.broadPhase.proxyCount
val contactCount: Int get() = contactManager.contactCount
val treeHeight: Int get() = contactManager.broadPhase.treeHeight
val treeBalance: Int get() = contactManager.broadPhase.treeBalance
/**
* Enable/disable sleep.
*/
var allowAutoSleep: Boolean = true
set(value) { set(value) {
if (value == field) if (value == field)
return return
@ -52,40 +166,57 @@ class B2World(override var gravity: Vector2d) : IB2World {
} }
} }
override var isLocked: Boolean = false /**
* Is the world locked (in the middle of a time step).
*/
var isLocked: Boolean = false
private set private set
override var autoClearForces: Boolean = true /**
* Set flag to control automatic clearing of forces after each time step.
*/
var autoClearForces: Boolean = true
private var stepComplete = true private var stepComplete = true
private var newContacts = false private var newContacts = false
private val profile = ProfileData() private val profile = ProfileData()
override fun notifyNewContacts() { fun notifyNewContacts() {
newContacts = true newContacts = true
} }
override fun createBody(bodyDef: BodyDef): IBody { /**
* Create a rigid body given a definition. No reference to the definition
* is retained.
* @warning This function is locked during callbacks.
*/
fun createBody(bodyDef: BodyDef): Body {
if (isLocked) if (isLocked)
throw ConcurrentModificationException() throw ConcurrentModificationException()
val body = Body(bodyDef, this) val body = Body(bodyDef, this)
body.next = bodyList body.next = bodyList
(bodyList as Body?)?.prev = body bodyList?.prev = body
bodyList = body bodyList = body
bodyCount++ bodyCount++
return body return body
} }
override fun destroyBody(body: IBody) { /**
* Destroy a rigid body given a definition. No reference to the definition
* is retained. This function is locked during callbacks.
* @warning This automatically deletes all associated shapes and joints.
* @warning This function is locked during callbacks.
*/
fun destroyBody(body: Body) {
if (isLocked) if (isLocked)
throw ConcurrentModificationException() throw ConcurrentModificationException()
check(body.world == this) { "$body does not belong to $this" } check(body.world == this) { "$body does not belong to $this" }
check(bodyCount > 0) { "I have ${bodyCount} bodies, can't remove one" } check(bodyCount > 0) { "I have $bodyCount bodies, can't remove one" }
// Delete the attached joints. // Delete the attached joints.
for (jointEdge in body.jointIterator) { for (jointEdge in body.jointIterator) {
@ -101,12 +232,12 @@ class B2World(override var gravity: Vector2d) : IB2World {
// Delete the attached fixtures. This destroys broad-phase proxies. // Delete the attached fixtures. This destroys broad-phase proxies.
for (fixture in body.fixtureIterator) { for (fixture in body.fixtureIterator) {
destructionListener?.sayGoodbye(fixture) destructionListener?.sayGoodbye(fixture)
(fixture as Fixture).destroyProxies(contactManager.broadPhase) fixture.destroyProxies(contactManager.broadPhase)
} }
// Remove world body list. // Remove world body list.
val prev = body.prev as Body? val prev = body.prev
val next = body.next as Body? val next = body.next
prev?.next = next prev?.next = next
next?.prev = prev next?.prev = prev
@ -116,10 +247,15 @@ class B2World(override var gravity: Vector2d) : IB2World {
} }
bodyCount-- bodyCount--
(body as Body).unlink() body.unlink()
} }
override fun createJoint(jointDef: IJointDef): AbstractJoint { /**
* Create a joint to constrain bodies together. No reference to the definition
* is retained. This may cause the connected bodies to cease colliding.
* @warning This function is locked during callbacks.
*/
fun createJoint(jointDef: IJointDef): AbstractJoint {
if (isLocked) if (isLocked)
throw ConcurrentModificationException() throw ConcurrentModificationException()
@ -127,7 +263,7 @@ class B2World(override var gravity: Vector2d) : IB2World {
// Connect to the world list. // Connect to the world list.
joint.next = jointList joint.next = jointList
(jointList as AbstractJoint?)?.prev = joint jointList?.prev = joint
jointList = joint jointList = joint
jointCount++ jointCount++
@ -150,12 +286,15 @@ class B2World(override var gravity: Vector2d) : IB2World {
return joint return joint
} }
override fun destroyJoint(joint: IJoint) { /**
* Destroy a joint. This may cause the connected bodies to begin colliding.
* @warning This function is locked during callbacks.
*/
fun destroyJoint(joint: AbstractJoint) {
if (isLocked) if (isLocked)
throw ConcurrentModificationException() throw ConcurrentModificationException()
check(jointCount > 0) { "No joints tracked to remove" } check(jointCount > 0) { "No joints tracked to remove" }
joint as AbstractJoint
require((joint.nullableBodyA?.world == this || joint.nullableBodyA == null) && (joint.nullableBodyB?.world == this || joint.nullableBodyB == null)) { "$joint does not belong to $this" } require((joint.nullableBodyA?.world == this || joint.nullableBodyA == null) && (joint.nullableBodyB?.world == this || joint.nullableBodyB == null)) { "$joint does not belong to $this" }
if (!joint.isValid) { if (!joint.isValid) {
@ -164,8 +303,8 @@ class B2World(override var gravity: Vector2d) : IB2World {
// Remove from the doubly linked list. // Remove from the doubly linked list.
this.run { this.run {
val prev = joint.prev as AbstractJoint? val prev = joint.prev
val next = joint.next as AbstractJoint? val next = joint.next
prev?.next = next prev?.next = next
next?.prev = prev next?.prev = prev
@ -241,25 +380,20 @@ class B2World(override var gravity: Vector2d) : IB2World {
// Clear all the island flags. // Clear all the island flags.
for (body in bodyListIterator) { for (body in bodyListIterator) {
body as Body
body.isOnIsland = false body.isOnIsland = false
} }
for (contact in contactListIterator) { for (contact in contactListIterator) {
contact as AbstractContact
contact.isOnIsland = false contact.isOnIsland = false
} }
for (joint in jointListIterator) { for (joint in jointListIterator) {
joint as AbstractJoint
check(joint.isValid) { "$joint is no longer valid, but present in linked list" } check(joint.isValid) { "$joint is no longer valid, but present in linked list" }
joint.isOnIsland = false joint.isOnIsland = false
} }
// Build and simulate all awake islands. // Build and simulate all awake islands.
for (seed in bodyListIterator) { for (seed in bodyListIterator) {
seed as Body
if (seed.type == BodyType.STATIC || !seed.isAwake || !seed.isEnabled || seed.isOnIsland) { if (seed.type == BodyType.STATIC || !seed.isAwake || !seed.isEnabled || seed.isOnIsland) {
continue continue
} }
@ -287,7 +421,7 @@ class B2World(override var gravity: Vector2d) : IB2World {
// Search all contacts connected to this body. // Search all contacts connected to this body.
for (ce in body.contactEdgeIterator) { for (ce in body.contactEdgeIterator) {
val contact = ce.contact as AbstractContact val contact = ce.contact
// Has this contact already been added to an island? // Has this contact already been added to an island?
if (contact.isOnIsland) if (contact.isOnIsland)
@ -304,7 +438,7 @@ class B2World(override var gravity: Vector2d) : IB2World {
island.add(contact) island.add(contact)
contact.isOnIsland = true contact.isOnIsland = true
val other = ce.other as Body val other = ce.other
// Was the other body already added to this island? // Was the other body already added to this island?
if (!other.isOnIsland) { if (!other.isOnIsland) {
@ -315,13 +449,13 @@ class B2World(override var gravity: Vector2d) : IB2World {
// Search all joints connect to this body. // Search all joints connect to this body.
for (je in body.jointIterator) { for (je in body.jointIterator) {
val joint = je.joint as AbstractJoint val joint = je.joint
check(joint.isValid) { "$joint is no longer valid, but present in linked list of $body" } check(joint.isValid) { "$joint is no longer valid, but present in linked list of $body" }
if (joint.isOnIsland) if (joint.isOnIsland)
continue continue
val other = je.otherNullable as Body? val other = je.otherNullable
// Don't simulate joints connected to disabled bodies. // Don't simulate joints connected to disabled bodies.
if (other != null && !other.isEnabled) if (other != null && !other.isEnabled)
@ -357,8 +491,6 @@ class B2World(override var gravity: Vector2d) : IB2World {
// Synchronize fixtures, check for out of range bodies. // Synchronize fixtures, check for out of range bodies.
for (body in bodyListIterator) { for (body in bodyListIterator) {
body as Body
// If a body was not in an island then it did not move. // If a body was not in an island then it did not move.
if (!body.isOnIsland || body.type == BodyType.STATIC) { if (!body.isOnIsland || body.type == BodyType.STATIC) {
continue continue
@ -381,14 +513,12 @@ class B2World(override var gravity: Vector2d) : IB2World {
if (stepComplete) { if (stepComplete) {
for (body in bodyListIterator) { for (body in bodyListIterator) {
body as Body
body.isOnIsland = false body.isOnIsland = false
body.sweep.alpha0 = 0.0 body.sweep.alpha0 = 0.0
} }
for (c in contactManager.contactListIterator) { for (c in contactManager.contactListIterator) {
// Invalidate TOI // Invalidate TOI
c as AbstractContact
c.isOnIsland = false c.isOnIsland = false
c.toiFlag = false c.toiFlag = false
c.toiCount = 0 c.toiCount = 0
@ -406,13 +536,11 @@ class B2World(override var gravity: Vector2d) : IB2World {
continue continue
} }
c as AbstractContact
if (c.toiCount > b2_maxSubSteps) { if (c.toiCount > b2_maxSubSteps) {
continue continue
} }
var alpha = 1.0 var alpha: Double
if (c.toiFlag) { if (c.toiFlag) {
// This contact has a valid cached TOI. // This contact has a valid cached TOI.
@ -550,7 +678,7 @@ class B2World(override var gravity: Vector2d) : IB2World {
if (body.type == BodyType.DYNAMIC) { if (body.type == BodyType.DYNAMIC) {
for (ce in body.contactEdgeIterator) { for (ce in body.contactEdgeIterator) {
val contact = ce.contact as AbstractContact val contact = ce.contact
// Has this contact already been added to the island? // Has this contact already been added to the island?
if (contact.isOnIsland) { if (contact.isOnIsland) {
@ -569,8 +697,6 @@ class B2World(override var gravity: Vector2d) : IB2World {
continue continue
} }
other as Body
// Tentatively advance the body to the TOI. // Tentatively advance the body to the TOI.
val backup = other.sweep val backup = other.sweep
if (!other.isOnIsland) { if (!other.isOnIsland) {
@ -639,7 +765,7 @@ class B2World(override var gravity: Vector2d) : IB2World {
// Invalidate all contact TOIs on this displaced body. // Invalidate all contact TOIs on this displaced body.
for (ce in body.contactEdgeIterator) { for (ce in body.contactEdgeIterator) {
val contact = ce.contact as AbstractContact val contact = ce.contact
contact.toiFlag = false contact.toiFlag = false
contact.isOnIsland = false contact.isOnIsland = false
} }
@ -658,8 +784,15 @@ class B2World(override var gravity: Vector2d) : IB2World {
private var m_inv_dt0 = 0.0 private var m_inv_dt0 = 0.0
override fun step(dt: Double, velocityIterations: Int, positionIterations: Int) { /**
var stepTimer = System.nanoTime() * Take a time step. This performs collision detection, integration,
* and constraint solution.
* @param dt the amount of time to simulate, this should not vary.
* @param velocityIterations for the velocity constraint solver.
* @param positionIterations for the position constraint solver.
*/
fun step(dt: Double, velocityIterations: Int, positionIterations: Int) {
val stepTimer = System.nanoTime()
// If new fixtures were added, we need to find the new contacts. // If new fixtures were added, we need to find the new contacts.
if (newContacts) { if (newContacts) {
@ -724,19 +857,41 @@ class B2World(override var gravity: Vector2d) : IB2World {
profile.step = System.nanoTime() - stepTimer profile.step = System.nanoTime() - stepTimer
} }
override fun clearForces() { /**
* Manually clear the force buffer on all bodies. By default, forces are cleared automatically
* after each call to Step. The default behavior is modified by calling SetAutoClearForces.
* The purpose of this function is to support sub-stepping. Sub-stepping is often used to maintain
* a fixed sized time step under a variable frame-rate.
* When you perform sub-stepping you will disable auto clearing of forces and instead call
* ClearForces after all sub-steps are complete in one pass of your game loop.
* @see autoClearForces
*/
fun clearForces() {
for (body in bodyListIterator) { for (body in bodyListIterator) {
body as Body
body.force = Vector2d.ZERO body.force = Vector2d.ZERO
body.torque = 0.0 body.torque = 0.0
} }
} }
override fun queryAABB(aabb: AABB, callback: IQueryCallback) { /**
* Query the world for all fixtures that potentially overlap the
* provided AABB.
* @param callback a user implemented callback class.
* @param aabb the query box.
*/
fun queryAABB(aabb: AABB, callback: IQueryCallback) {
contactManager.broadPhase.query(aabb) { nodeId, userData -> callback.reportFixture((userData as FixtureProxy).fixture) } contactManager.broadPhase.query(aabb) { nodeId, userData -> callback.reportFixture((userData as FixtureProxy).fixture) }
} }
override fun rayCast(point1: Vector2d, point2: Vector2d, callback: IRayCastCallback) { /**
* Ray-cast the world for all fixtures in the path of the ray. Your callback
* controls whether you get the closest point, any point, or n-points.
* The ray-cast ignores shapes that contain the starting point.
* @param callback a user implemented callback class.
* @param point1 the ray starting point
* @param point2 the ray ending point
*/
fun rayCast(point1: Vector2d, point2: Vector2d, callback: IRayCastCallback) {
val input = RayCastInput(point1, point2, 1.0) val input = RayCastInput(point1, point2, 1.0)
contactManager.broadPhase.rayCast(input, object : ProxyRayCastCallback { contactManager.broadPhase.rayCast(input, object : ProxyRayCastCallback {
@ -756,7 +911,7 @@ class B2World(override var gravity: Vector2d) : IB2World {
}) })
} }
private fun drawShape(fixture: IFixture, xf: Transform, color: Color) { private fun drawShape(fixture: Fixture, xf: Transform, color: Color) {
when (fixture.type) { when (fixture.type) {
IShape.Type.CIRCLE -> { IShape.Type.CIRCLE -> {
val circle = fixture.shape as CircleShape val circle = fixture.shape as CircleShape
@ -799,7 +954,10 @@ class B2World(override var gravity: Vector2d) : IB2World {
} }
} }
override fun debugDraw() { /**
* Call this to draw shapes and other debug draw data. This is intentionally non-const.
*/
fun debugDraw() {
val debugDraw = debugDraw ?: return val debugDraw = debugDraw ?: return
if (debugDraw.drawShapes) { if (debugDraw.drawShapes) {
@ -872,17 +1030,6 @@ class B2World(override var gravity: Vector2d) : IB2World {
} }
} }
companion object {
private val BAD_BODY_COLOR = Color(1f, 0f, 0f)
private val DISABLED_BODY_COLOR = Color(0.5f, 0.5f, 0.3f)
private val STATIC_BODY_COLOR = Color(0.5f, 0.9f, 0.5f)
private val KINEMATIC_BODY_COLOR = Color(0.5f, 0.5f, 0.9f)
private val SLEEPING_BODY_COLOR = Color(0.6f, 0.6f, 0.6f)
private val NORMAL_BODY_COLOR = Color(0.9f, 0.7f, 0.7f)
private val PAIR_COLOR = Color(0.3f, 0.9f, 0.9f)
private val AABB_COLOR = Color(0.9f, 0.3f, 0.9f)
}
override fun shiftOrigin(newOrigin: Vector2d) { override fun shiftOrigin(newOrigin: Vector2d) {
if (isLocked) if (isLocked)
throw ConcurrentModificationException() throw ConcurrentModificationException()
@ -891,7 +1038,6 @@ class B2World(override var gravity: Vector2d) : IB2World {
try { try {
for (body in bodyListIterator) { for (body in bodyListIterator) {
body as Body
body.transform.p -= newOrigin body.transform.p -= newOrigin
body.sweep.c0 -= newOrigin body.sweep.c0 -= newOrigin
body.sweep.c -= newOrigin body.sweep.c -= newOrigin
@ -907,10 +1053,29 @@ class B2World(override var gravity: Vector2d) : IB2World {
} }
} }
override val profileData: IProfileData
/**
* Get the current profile.
*/
val profileData: IProfileData
get() = profile.snapshot() get() = profile.snapshot()
override fun dump() { /**
* Dump the world into the log file.
* @warning this should be called outside of a time step.
*/
fun dump() {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
companion object {
private val BAD_BODY_COLOR = Color(1f, 0f, 0f)
private val DISABLED_BODY_COLOR = Color(0.5f, 0.5f, 0.3f)
private val STATIC_BODY_COLOR = Color(0.5f, 0.9f, 0.5f)
private val KINEMATIC_BODY_COLOR = Color(0.5f, 0.5f, 0.9f)
private val SLEEPING_BODY_COLOR = Color(0.6f, 0.6f, 0.6f)
private val NORMAL_BODY_COLOR = Color(0.9f, 0.7f, 0.7f)
private val PAIR_COLOR = Color(0.3f, 0.9f, 0.9f)
private val AABB_COLOR = Color(0.9f, 0.3f, 0.9f)
}
} }

View File

@ -3,10 +3,13 @@ package ru.dbotthepony.kbox2d.dynamics
import ru.dbotthepony.kbox2d.api.* import ru.dbotthepony.kbox2d.api.*
import ru.dbotthepony.kvector.vector.ndouble.Vector2d import ru.dbotthepony.kvector.vector.ndouble.Vector2d
open class Body(def: BodyDef, world: IB2World) : IBody { class Body(def: BodyDef, world: B2World) {
private var _world: IB2World? = world private var _world: B2World? = world
final override val world: IB2World /**
* Get the parent world of this body.
*/
val world: B2World
get() = _world ?: throw IllegalStateException("Tried to use removed body") get() = _world ?: throw IllegalStateException("Tried to use removed body")
internal var flags: Int = 0 internal var flags: Int = 0
@ -43,21 +46,37 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
internal val sweep: Sweep = Sweep() internal val sweep: Sweep = Sweep()
final override val transform: Transform = Transform(def.position, def.angle) /**
* Get the body transform for the body's origin.
* @return the world transform of the body's origin.
*/
val transform: Transform = Transform(def.position, def.angle)
val xf by this::transform val xf by this::transform
override val position: Vector2d /**
* Get the world body origin position.
* @return the world position of the body's origin.
*/
val position: Vector2d
get() = transform.position get() = transform.position
override val angle: Double
/**
* Get the angle in radians.
* @return the current world rotation angle in radians.
*/
val angle: Double
get() = sweep.a get() = sweep.a
override val userData: Any? = def.userData val userData: Any? = def.userData
internal var sleepTime: Double = 0.0 internal var sleepTime: Double = 0.0
internal var torque: Double = 0.0 internal var torque: Double = 0.0
internal var force: Vector2d = Vector2d.ZERO internal var force: Vector2d = Vector2d.ZERO
override var linearVelocity: Vector2d = def.linearVelocity /**
* The linear velocity of the body's origin in world co-ordinates.
*/
var linearVelocity: Vector2d = def.linearVelocity
set(value) { set(value) {
if (type == BodyType.STATIC) if (type == BodyType.STATIC)
return return
@ -68,7 +87,10 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
field = value field = value
} }
override var angularVelocity: Double = def.angularVelocity /**
* The angular velocity of the body.
*/
var angularVelocity: Double = def.angularVelocity
set(value) { set(value) {
if (type == BodyType.STATIC) if (type == BodyType.STATIC)
return return
@ -79,27 +101,95 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
field = value field = value
} }
override var fixtureList: Fixture? = null var fixtureList: Fixture? = null
protected set protected set
protected var fixtureCount: Int = 0 var fixtureCount: Int = 0
private set
override var jointList: JointEdge? = null /**
internal set * Get the list of all joints attached to this body.
override var contactEdge: ContactEdge? = null *
* Kotlin: This is not a list, but, as in C++ impl, a custom
* linked list.
*/
var jointList: JointEdge? = null
internal set internal set
override var next: IBody? = null /**
internal set * Get the list of all contacts attached to this body.
override var prev: IBody? = null * @warning this list changes during the time step and you may
* miss some collisions if you don't use b2ContactListener.
*
* Kotlin: This is not a list, but, as in C++ impl, a custom
* linked list.
*/
var contactEdge: ContactEdge? = null
internal set internal set
override var linearDamping: Double = def.linearDamping val contactEdgeIterator: Iterator<ContactEdge> get() {
override var angularDamping: Double = def.angularDamping return object : Iterator<ContactEdge> {
override var gravityScale: Double = def.gravityScale private var node = contactEdge
override var mass: Double = 0.0 override fun hasNext(): Boolean {
protected set return node != null
}
override fun next(): ContactEdge {
val old = node!!
node = old.next
return old
}
}
}
val jointIterator: Iterator<JointEdge> get() {
return object : Iterator<JointEdge> {
private var node = jointList
override fun hasNext(): Boolean {
return node != null
}
override fun next(): JointEdge {
val old = node!!
node = old.next
return old
}
}
}
var next: Body? = null
internal set
var prev: Body? = null
internal set
val fixtureIterator: Iterator<Fixture> get() {
return object : Iterator<Fixture> {
private var node = fixtureList
override fun hasNext(): Boolean {
return node != null
}
override fun next(): Fixture {
val old = node!!
node = old.next
return old
}
}
}
var linearDamping: Double = def.linearDamping
var angularDamping: Double = def.angularDamping
var gravityScale: Double = def.gravityScale
/**
* Get the total mass of the body.
* @return the mass, usually in kilograms (kg).
*/
var mass: Double = 0.0
private set
internal var invMass: Double = 0.0 internal var invMass: Double = 0.0
@ -116,11 +206,19 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
internal var I by this::rotInertia internal var I by this::rotInertia
internal var invI by this::rotInertiaInv internal var invI by this::rotInertiaInv
override var isBullet: Boolean /**
* Should this body be treated like a bullet for continuous collision detection?
*/
var isBullet: Boolean
get() = BodyFlags.BULLET.isit(flags) get() = BodyFlags.BULLET.isit(flags)
set(value) { flags = BodyFlags.BULLET.update(flags, value) } set(value) { flags = BodyFlags.BULLET.update(flags, value) }
override var isAwake: Boolean /**
* Set the sleep state of the body. A sleeping body has very
* low CPU cost.
* @param flag set to true to wake the body, false to put it to sleep.
*/
var isAwake: Boolean
get() = BodyFlags.AWAKE.isit(flags) get() = BodyFlags.AWAKE.isit(flags)
set(value) { set(value) {
if (type == BodyType.STATIC || BodyFlags.AWAKE.isit(flags) == value) if (type == BodyType.STATIC || BodyFlags.AWAKE.isit(flags) == value)
@ -139,7 +237,27 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
} }
} }
override var isEnabled: Boolean /**
* Allow a body to be disabled. A disabled body is not simulated and cannot
* be collided with or woken up.
*
* If you pass a flag of true, all fixtures will be added to the broad-phase.
*
* If you pass a flag of false, all fixtures will be removed from the
* broad-phase and all contacts will be destroyed.
*
* Fixtures and joints are otherwise unaffected. You may continue
* to create/destroy fixtures and joints on disabled bodies.
*
* Fixtures on a disabled body are implicitly disabled and will
* not participate in collisions, ray-casts, or queries.
*
* Joints connected to a disabled body are implicitly disabled.
*
* An diabled body is still owned by a b2World object and remains
* in the body list.
*/
var isEnabled: Boolean
get() = BodyFlags.ENABLED.isit(flags) get() = BodyFlags.ENABLED.isit(flags)
set(value) { set(value) {
if (world.isLocked) if (world.isLocked)
@ -178,7 +296,10 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
} }
} }
override var isFixedRotation: Boolean /**
* Set this body to have fixed rotation. This causes the mass to be reset.
*/
var isFixedRotation: Boolean
get() = BodyFlags.FIXED_ROTATION.isit(flags) get() = BodyFlags.FIXED_ROTATION.isit(flags)
set(value) { set(value) {
if (value == isFixedRotation) if (value == isFixedRotation)
@ -190,7 +311,11 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
resetMassData() resetMassData()
} }
override var allowAutoSleep: Boolean /**
* You can disable sleeping on this body. If you disable sleeping, the
* body will be woken.
*/
var allowAutoSleep: Boolean
get() = BodyFlags.AUTO_SLEEP.isit(flags) get() = BodyFlags.AUTO_SLEEP.isit(flags)
set(value) { set(value) {
flags = BodyFlags.AUTO_SLEEP.update(flags, value) flags = BodyFlags.AUTO_SLEEP.update(flags, value)
@ -200,7 +325,10 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
} }
} }
override var type: BodyType = def.type /**
* Set the type of this body. This may alter the mass and velocity.
*/
var type: BodyType = def.type
set(value) { set(value) {
if (world.isLocked) if (world.isLocked)
throw ConcurrentModificationException() throw ConcurrentModificationException()
@ -234,7 +362,7 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
contactEdge = null contactEdge = null
val broadPhase = world.contactManager.broadPhase val broadPhase = world.contactManager.broadPhase
var f: IFixture? = fixtureList var f: Fixture? = fixtureList
while (f != null) { while (f != null) {
for (proxy in f.proxies) { for (proxy in f.proxies) {
@ -245,7 +373,15 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
} }
} }
override fun createFixture(def: FixtureDef): IFixture { /** Creates a fixture and attach it to this body. Use this function if you need
* to set some fixture parameters, like friction. Otherwise you can create the
* fixture directly from a shape.
* If the density is non-zero, this function automatically updates the mass of the body.
* Contacts are not created until the next time step.
* @param def the fixture definition.
* @warning This function is locked during callbacks.
*/
fun createFixture(def: FixtureDef): Fixture {
if (world.isLocked) if (world.isLocked)
throw ConcurrentModificationException() throw ConcurrentModificationException()
@ -271,21 +407,48 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
return fixture return fixture
} }
override fun destroyFixture(fixture: IFixture) { /**
* Creates a fixture from a shape and attach it to this body.
* This is a convenience function. Use b2FixtureDef if you need to set parameters
* like friction, restitution, user data, or filtering.
* If the density is non-zero, this function automatically updates the mass of the body.
* @param shape the shape to be cloned.
* @param density the shape density (set to zero for static bodies).
* @warning This function is locked during callbacks.
*/
fun createFixture(shape: IShape<*>, density: Double): Fixture {
return createFixture(
FixtureDef(
shape = shape,
density = density
)
)
}
/**
* Destroy a fixture. This removes the fixture from the broad-phase and
* destroys all contacts associated with this fixture. This will
* automatically adjust the mass of the body if the body is dynamic and the
* fixture has positive density.
* All fixtures attached to a body are implicitly destroyed when the body is destroyed.
* @param fixture the fixture to be removed.
* @warning This function is locked during callbacks.
*/
fun destroyFixture(fixture: Fixture) {
if (world.isLocked) if (world.isLocked)
throw ConcurrentModificationException() throw ConcurrentModificationException()
require(fixture.body == this) { "$fixture does not belong to $this (belongs to ${fixture.body})" } require(fixture.body == this) { "$fixture does not belong to $this (belongs to ${fixture.body})" }
check(fixtureCount > 0) { "Having no tracked fixtures, but $fixture belongs to us" } check(fixtureCount > 0) { "Having no tracked fixtures, but $fixture belongs to us" }
var node: IFixture? = fixtureList var node: Fixture? = fixtureList
var found = false var found = false
var previous: IFixture? = null var previous: Fixture? = null
while (node != null) { while (node != null) {
if (node == fixture) { if (node == fixture) {
// TODO: Это должно работать // TODO: Это должно работать
(previous as Fixture?)?.next = node.next previous?.next = node.next
found = true found = true
break break
} else { } else {
@ -330,7 +493,12 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
} }
} }
override fun resetMassData() { /**
* This resets the mass properties to the sum of the mass properties of the fixtures.
* This normally does not need to be called unless you called SetMassData to override
* the mass and you later want to reset the mass.
*/
fun resetMassData() {
// Compute mass data from shapes. Each shape has its own density. // Compute mass data from shapes. Each shape has its own density.
mass = 0.0 mass = 0.0
invMass = 0.0 invMass = 0.0
@ -387,7 +555,16 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
linearVelocity += b2Cross(angularVelocity, sweep.c - oldCenter) linearVelocity += b2Cross(angularVelocity, sweep.c - oldCenter)
} }
override var massData: MassData /**
* Get the mass data of the body.
* @return a struct containing the mass, inertia and center of the body.
* Set the mass properties to override the mass properties of the fixtures.
* Note that this changes the center of mass position.
* Note that creating or destroying fixtures can also alter the mass.
* This function has no effect if the body isn't dynamic.
* @param data the mass properties.
*/
var massData: MassData
get() = MassData( get() = MassData(
mass = mass, mass = mass,
inertia = inertia, inertia = inertia,
@ -428,7 +605,7 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
linearVelocity += b2Cross(angularVelocity, sweep.c - oldCenter) linearVelocity += b2Cross(angularVelocity, sweep.c - oldCenter)
} }
internal fun shouldCollide(other: IBody): Boolean { internal fun shouldCollide(other: Body): Boolean {
// At least one body should be dynamic. // At least one body should be dynamic.
if (type != BodyType.DYNAMIC && other.type != BodyType.DYNAMIC) if (type != BodyType.DYNAMIC && other.type != BodyType.DYNAMIC)
return false return false
@ -454,7 +631,14 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
sweep.a = def.angle sweep.a = def.angle
} }
override fun setTransform(position: Vector2d, angle: Double) { /**
* Set the position of the body's origin and rotation.
* Manipulating a body's transform may cause non-physical behavior.
* Note: contacts are updated on the next call to b2World::Step.
* @param position the world position of the body's local origin.
* @param angle the world rotation in radians.
*/
fun setTransform(position: Vector2d, angle: Double) {
if (world.isLocked) if (world.isLocked)
throw ConcurrentModificationException() throw ConcurrentModificationException()
@ -477,40 +661,88 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
world.notifyNewContacts() world.notifyNewContacts()
} }
override val inertia: Double /**
* Get the rotational inertia of the body about the local origin.
* @return the rotational inertia, usually in kg-m^2.
*/
val inertia: Double
get() = rotInertia + mass * sweep.localCenter.dot(sweep.localCenter) get() = rotInertia + mass * sweep.localCenter.dot(sweep.localCenter)
override val localCenter: Vector2d /**
* Get the local position of the center of mass.
*/
val localCenter: Vector2d
get() = sweep.localCenter get() = sweep.localCenter
override val worldCenter: Vector2d /**
* Get the world position of the center of mass.
*/
val worldCenter: Vector2d
get() = sweep.c get() = sweep.c
override fun getWorldPoint(localPoint: Vector2d): Vector2d { /**
* Get the world coordinates of a point given the local coordinates.
* @param localPoint a point on the body measured relative the the body's origin.
* @return the same point expressed in world coordinates.
*/
fun getWorldPoint(localPoint: Vector2d): Vector2d {
return transform.times(localPoint) return transform.times(localPoint)
} }
override fun getWorldVector(localPoint: Vector2d): Vector2d { /**
* Get the world coordinates of a vector given the local coordinates.
* @param localVector a vector fixed in the body.
* @return the same vector expressed in world coordinates.
*/
fun getWorldVector(localPoint: Vector2d): Vector2d {
return transform.rotation.times(localPoint) return transform.rotation.times(localPoint)
} }
override fun getLocalPoint(worldPoint: Vector2d): Vector2d { /**
* Gets a local point relative to the body's origin given a world point.
* @param worldPoint a point in world coordinates.
* @return the corresponding local point relative to the body's origin.
*/
fun getLocalPoint(worldPoint: Vector2d): Vector2d {
return transform.timesT(worldPoint) return transform.timesT(worldPoint)
} }
override fun getLocalVector(worldVector: Vector2d): Vector2d { /**
* Gets a local vector given a world vector.
* @param worldVector a vector in world coordinates.
* @return the corresponding local vector.
*/
fun getLocalVector(worldVector: Vector2d): Vector2d {
return transform.rotation.timesT(worldVector) return transform.rotation.timesT(worldVector)
} }
override fun getLinearVelocityFromWorldPoint(worldPoint: Vector2d): Vector2d { /**
* Get the world linear velocity of a world point attached to this body.
* @param worldPoint a point in world coordinates.
* @return the world velocity of a point.
*/
fun getLinearVelocityFromWorldPoint(worldPoint: Vector2d): Vector2d {
return linearVelocity + b2Cross(angularVelocity, worldPoint - sweep.c) return linearVelocity + b2Cross(angularVelocity, worldPoint - sweep.c)
} }
override fun getLinearVelocityFromLocalPoint(localPoint: Vector2d): Vector2d { /**
* Get the world velocity of a local point.
* @param localPoint a point in local coordinates.
* @return the world velocity of a point.
*/
fun getLinearVelocityFromLocalPoint(localPoint: Vector2d): Vector2d {
return getLinearVelocityFromWorldPoint(getWorldPoint(localPoint)) return getLinearVelocityFromWorldPoint(getWorldPoint(localPoint))
} }
override fun applyForce(force: Vector2d, point: Vector2d, wake: Boolean) { /**
* Apply a force at a world point. If the force is not
* applied at the center of mass, it will generate a torque and
* affect the angular velocity. This wakes up the body.
* @param force the world force vector, usually in Newtons (N).
* @param point the world position of the point of application.
* @param wake also wake up the body
*/
fun applyForce(force: Vector2d, point: Vector2d, wake: Boolean) {
if (type != BodyType.DYNAMIC) if (type != BodyType.DYNAMIC)
return return
@ -524,7 +756,12 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
} }
} }
override fun applyForceToCenter(force: Vector2d, wake: Boolean) { /**
* Apply a force to the center of mass. This wakes up the body.
* @param force the world force vector, usually in Newtons (N).
* @param wake also wake up the body
*/
fun applyForceToCenter(force: Vector2d, wake: Boolean) {
if (type != BodyType.DYNAMIC) if (type != BodyType.DYNAMIC)
return return
@ -537,7 +774,13 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
} }
} }
override fun applyTorque(torque: Double, wake: Boolean) { /**
* Apply a torque. This affects the angular velocity
* without affecting the linear velocity of the center of mass.
* @param torque about the z-axis (out of the screen), usually in N-m.
* @param wake also wake up the body
*/
fun applyTorque(torque: Double, wake: Boolean) {
if (type != BodyType.DYNAMIC) if (type != BodyType.DYNAMIC)
return return
@ -549,7 +792,15 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
} }
} }
override fun applyLinearImpulse(impulse: Vector2d, point: Vector2d, wake: Boolean) { /**
* Apply an impulse at a point. This immediately modifies the velocity.
* It also modifies the angular velocity if the point of application
* is not at the center of mass. This wakes up the body.
* @param impulse the world impulse vector, usually in N-seconds or kg-m/s.
* @param point the world position of the point of application.
* @param wake also wake up the body
*/
fun applyLinearImpulse(impulse: Vector2d, point: Vector2d, wake: Boolean) {
if (type != BodyType.DYNAMIC) if (type != BodyType.DYNAMIC)
return return
@ -562,7 +813,12 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
} }
} }
override fun applyLinearImpulseToCenter(impulse: Vector2d, wake: Boolean) { /**
* Apply an impulse to the center of mass. This immediately modifies the velocity.
* @param impulse the world impulse vector, usually in N-seconds or kg-m/s.
* @param wake also wake up the body
*/
fun applyLinearImpulseToCenter(impulse: Vector2d, wake: Boolean) {
if (type != BodyType.DYNAMIC) if (type != BodyType.DYNAMIC)
return return
@ -574,7 +830,12 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
} }
} }
override fun applyAngularImpulse(impulse: Double, wake: Boolean) { /**
* Apply an angular impulse.
* @param impulse the angular impulse in units of kg*m*m/s
* @param wake also wake up the body
*/
fun applyAngularImpulse(impulse: Double, wake: Boolean) {
if (type != BodyType.DYNAMIC) if (type != BodyType.DYNAMIC)
return return
@ -616,7 +877,7 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
} }
} }
override fun dump() { fun dump() {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
} }

View File

@ -4,18 +4,35 @@ import ru.dbotthepony.kbox2d.api.*
import ru.dbotthepony.kbox2d.collision.BroadPhase import ru.dbotthepony.kbox2d.collision.BroadPhase
import ru.dbotthepony.kbox2d.dynamics.contact.AbstractContact import ru.dbotthepony.kbox2d.dynamics.contact.AbstractContact
class ContactManager : IContactManager { /**
override val broadPhase: IBroadPhase = BroadPhase() * Delegate of b2World.
override var contactList: IContact? = null */
override var contactCount: Int = 0 class ContactManager {
val broadPhase = BroadPhase()
var contactList: AbstractContact? = null
var contactCount: Int = 0
private set private set
override var contactFilter: IContactFilter? = null var contactFilter: IContactFilter? = null
override var contactListener: IContactListener? = null var contactListener: IContactListener? = null
override fun destroy(contact: IContact) { val contactListIterator: Iterator<AbstractContact> get() {
contact as AbstractContact return object : Iterator<AbstractContact> {
private var node = contactList
override fun hasNext(): Boolean {
return node != null
}
override fun next(): AbstractContact {
val old = node!!
node = old.next
return old
}
}
}
fun destroy(contact: AbstractContact) {
val fixtureA = contact.fixtureA val fixtureA = contact.fixtureA
val fixtureB = contact.fixtureB val fixtureB = contact.fixtureB
val bodyA = fixtureA.body as Body val bodyA = fixtureA.body as Body
@ -72,7 +89,7 @@ class ContactManager : IContactManager {
* all the narrow phase collision is processed for the world * all the narrow phase collision is processed for the world
* contact list. * contact list.
*/ */
override fun collide() { fun collide() {
// Update awake contacts. // Update awake contacts.
for (c in contactListIterator) { for (c in contactListIterator) {
@ -123,11 +140,11 @@ class ContactManager : IContactManager {
} }
} }
override fun findNewContacts() { fun findNewContacts() {
broadPhase.updatePairs(this::addPair) broadPhase.updatePairs(this::addPair)
} }
override fun addPair(proxyUserDataA: Any?, proxyUserDataB: Any?) { fun addPair(proxyUserDataA: Any?, proxyUserDataB: Any?) {
val proxyA = proxyUserDataA as FixtureProxy val proxyA = proxyUserDataA as FixtureProxy
val proxyB = proxyUserDataB as FixtureProxy val proxyB = proxyUserDataB as FixtureProxy

View File

@ -1,28 +1,77 @@
package ru.dbotthepony.kbox2d.dynamics package ru.dbotthepony.kbox2d.dynamics
import ru.dbotthepony.kbox2d.api.* import ru.dbotthepony.kbox2d.api.*
import ru.dbotthepony.kbox2d.collision.BroadPhase
import ru.dbotthepony.kbox2d.collision.shapes.ChainShape import ru.dbotthepony.kbox2d.collision.shapes.ChainShape
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
open class Fixture( /**
body: ru.dbotthepony.kbox2d.api.IBody, * A fixture is used to attach a shape to a body for collision detection. A fixture
* inherits its transform from its parent. Fixtures hold additional non-geometric data
* such as friction, collision filters, etc.
* Fixtures are created via b2Body::CreateFixture.
* @warning you cannot reuse fixtures.
*/
class Fixture(
body: Body,
def: FixtureDef def: FixtureDef
) : IFixture { ) {
final override var body: ru.dbotthepony.kbox2d.api.IBody? = body /**
protected set * Get the parent body of this fixture. This is nullptr if the fixture is not attached.
* @return the parent body.
*/
var body: Body? = body
private set
final override var next: IFixture? = null /**
* Get the next fixture in the parent body's fixture list.
* @return the next shape.
*/
var next: Fixture? = null
override val userData: Any? = def.userData /**
override var friction: Double = def.friction * Get the user data that was assigned in the fixture definition. Use this to
override var restitution: Double = def.restitution * store your application specific data.
override var restitutionThreshold: Double = def.restitutionThreshold */
var userData: Any? = def.userData
override val type: IShape.Type get() = shape.type /**
* Get the coefficient of friction.
* Set the coefficient of friction. This will _not_ change the friction of
* existing contacts.
*/
var friction: Double = def.friction
override var isSensor: Boolean = def.isSensor /**
* Get the coefficient of restitution.
* Set the coefficient of restitution. This will _not_ change the restitution of
* existing contacts.
*/
var restitution: Double = def.restitution
/**
* Get the restitution velocity threshold.
* Set the restitution threshold. This will _not_ change the restitution threshold of
* existing contacts.
*/
var restitutionThreshold: Double = def.restitutionThreshold
/**
* Get the type of the child shape. You can use this to down cast to the concrete shape.
* @return the shape type.
*/
val type: IShape.Type get() = shape.type
/**
* Set if this fixture is a sensor.
* Is this fixture a sensor (non-solid)?
* @return the true if the shape is a sensor.
*/
var isSensor: Boolean = def.isSensor
set(value) { set(value) {
if (field != value) { if (field != value) {
checkNotNull(body) { "Already destroyed" }.isAwake = true checkNotNull(body) { "Already destroyed" }.isAwake = true
@ -30,7 +79,12 @@ open class Fixture(
} }
} }
final override val shape: IShape<*> = requireNotNull(def.shape) { "null shape provided" }.copy() /**
* Get the child shape. You can modify the child shape, however you should not change the
* number of vertices because this will crash some collision caching mechanisms.
* Manipulating the shape may lead to non-physical behavior.
*/
val shape: IShape<*> = requireNotNull(def.shape) { "null shape provided" }.copy()
init { init {
if (shape is PolygonShape) { if (shape is PolygonShape) {
@ -42,10 +96,15 @@ open class Fixture(
} }
} }
protected val internalProxies = ArrayList<FixtureProxy>(shape.childCount) private val internalProxies = ArrayList<FixtureProxy>(shape.childCount)
final override val proxies: List<FixtureProxy> = Collections.unmodifiableList(internalProxies) val proxies: List<FixtureProxy> = Collections.unmodifiableList(internalProxies)
override var density: Double = def.density /**
* Set the density of this fixture. This will _not_ automatically adjust the mass
* of the body. You must call b2Body::ResetMassData to update the body's mass.
* Get the density of this fixture.
*/
var density: Double = def.density
set(value) { set(value) {
require(value.isFinite()) { "Infinite density" } require(value.isFinite()) { "Infinite density" }
require(!value.isNaN()) { "NaN density" } require(!value.isNaN()) { "NaN density" }
@ -53,7 +112,12 @@ open class Fixture(
field = value field = value
} }
override var filter: IFilter = def.filter.immutable() /**
* Set the contact filtering data. This will not update contacts until the next time
* step when either parent body is active and awake.
* This automatically calls Refilter.
*/
var filter: IFilter = def.filter.immutable()
set(value) { set(value) {
if (value is ImmutableFilter) if (value is ImmutableFilter)
field = value field = value
@ -76,7 +140,7 @@ open class Fixture(
/** /**
* These support body activation/deactivation. * These support body activation/deactivation.
*/ */
internal fun createProxies(broadPhase: IBroadPhase, xf: Transform) { internal fun createProxies(broadPhase: BroadPhase, xf: Transform) {
check(body != null) { "Already destroyed" } check(body != null) { "Already destroyed" }
check(internalProxies.isEmpty()) { "Already having proxies" } check(internalProxies.isEmpty()) { "Already having proxies" }
@ -97,7 +161,7 @@ open class Fixture(
/** /**
* These support body activation/deactivation. * These support body activation/deactivation.
*/ */
internal fun destroyProxies(broadPhase: IBroadPhase) { internal fun destroyProxies(broadPhase: BroadPhase) {
check(body != null) { "Already destroyed" } check(body != null) { "Already destroyed" }
// Destroy proxies in the broad-phase. // Destroy proxies in the broad-phase.
@ -108,7 +172,7 @@ open class Fixture(
internalProxies.clear() internalProxies.clear()
} }
internal fun synchronize(broadPhase: IBroadPhase, transform1: Transform, transform2: Transform) { internal fun synchronize(broadPhase: BroadPhase, transform1: Transform, transform2: Transform) {
check(body != null) { "Already destroyed" } check(body != null) { "Already destroyed" }
for (proxy in internalProxies) { for (proxy in internalProxies) {
@ -121,7 +185,10 @@ open class Fixture(
} }
} }
override fun refilter() { /**
* Call this if you want to establish collision that was previously disabled by b2ContactFilter::ShouldCollide.
*/
fun refilter() {
val body = body val body = body
check(body != null) { "Already destroyed" } check(body != null) { "Already destroyed" }
var edge = body.contactEdge var edge = body.contactEdge
@ -144,4 +211,45 @@ open class Fixture(
broadPhase.touchProxy(proxy.proxyId) broadPhase.touchProxy(proxy.proxyId)
} }
} }
/**
* Test a point for containment in this fixture.
* @param p a point in world coordinates.
*/
fun testPoint(p: Vector2d): Boolean {
return shape.testPoint(checkNotNull(body) { "Tried to use detached fixture" }.transform, p)
}
/**
* Cast a ray against this shape.
* @param output the ray-cast results.
* @param input the ray-cast input parameters.
* @param childIndex the child shape index (e.g. edge index)
*/
fun rayCast(input: RayCastInput, childIndex: Int): RayCastOutput {
return shape.rayCast(input, checkNotNull(body) { "Tried to use detached fixture" }.transform, childIndex)
}
/**
* Get the mass data for this fixture. The mass data is based on the density and
* the shape. The rotational inertia is about the shape's origin. This operation
* may be expensive.
*/
fun getMassData(): MassData {
return shape.computeMass(density)
}
/**
* Get the fixture's AABB. This AABB may be enlarge and/or stale.
* If you need a more accurate AABB, compute it using the shape and
* the body transform.
*/
fun getAABB(childIndex: Int): AABB {
return proxies[childIndex].aabb
}
/**
* Dump this fixture to the log file.
*/
fun dump(childIndex: Int) { }
} }

View File

@ -4,30 +4,61 @@ import ru.dbotthepony.kbox2d.api.*
import ru.dbotthepony.kbox2d.collision.WorldManifold import ru.dbotthepony.kbox2d.collision.WorldManifold
import ru.dbotthepony.kbox2d.collision.b2TestOverlap import ru.dbotthepony.kbox2d.collision.b2TestOverlap
import ru.dbotthepony.kbox2d.dynamics.Body import ru.dbotthepony.kbox2d.dynamics.Body
import ru.dbotthepony.kbox2d.dynamics.Fixture
import java.util.* import java.util.*
import kotlin.collections.HashMap import kotlin.collections.HashMap
fun interface ContactFactory { fun interface ContactFactory {
fun factorize(fixtureA: IFixture, childIndexA: Int, fixtureB: IFixture, childIndexB: Int): AbstractContact fun factorize(fixtureA: Fixture, childIndexA: Int, fixtureB: Fixture, childIndexB: Int): AbstractContact
} }
/**
* The class manages contact between two shapes. A contact exists for each overlapping
* AABB in the broad-phase (except if filtered). Therefore a contact object may exist
* that has no contact points.
*/
sealed class AbstractContact( sealed class AbstractContact(
final override val fixtureA: IFixture, /**
final override val childIndexA: Int, * Get fixture A in this contact.
final override val fixtureB: IFixture, */
final override val childIndexB: Int, val fixtureA: Fixture,
) : IContact {
/**
* Get the child primitive index for fixture A.
*/
val childIndexA: Int,
/**
* Get fixture B in this contact.
*/
val fixtureB: Fixture,
/**
* Get the child primitive index for fixture B.
*/
val childIndexB: Int,
) {
internal var flags: Int = ContactFlags.ENABLED.bitmask internal var flags: Int = ContactFlags.ENABLED.bitmask
internal var isOnIsland = false internal var isOnIsland = false
internal var toiFlag = false internal var toiFlag = false
final override var manifold: Manifold = Manifold() /**
* Get the contact manifold. Do not modify the manifold unless you understand the
* internals of Box2D.
*/
var manifold: Manifold = Manifold()
private set private set
override var next: IContact? = null /**
* Get the next contact in the world's contact list.
*/
var next: AbstractContact? = null
internal set internal set
override var prev: IContact? = null /**
* Get the previous contact in the world's contact list.
*/
var prev: AbstractContact? = null
internal set internal set
internal var isFlaggedForFiltering: Boolean internal var isFlaggedForFiltering: Boolean
@ -51,15 +82,44 @@ sealed class AbstractContact(
bodyB.contactEdge = nodeB bodyB.contactEdge = nodeB
} }
override var friction: Double = b2MixFriction(fixtureA.friction, fixtureB.friction) /**
override var restitution: Double = b2MixRestitution(fixtureA.restitution, fixtureB.restitution) * Override the default friction mixture. You can call this in b2ContactListener::PreSolve.
override var restitutionThreshold: Double = b2MixRestitutionThreshold(fixtureA.restitutionThreshold, fixtureB.restitutionThreshold) * This value persists until set or reset.
override var tangentSpeed: Double = 0.0 * Get the friction.
*/
var friction: Double = b2MixFriction(fixtureA.friction, fixtureB.friction)
/**
* Override the default restitution mixture. You can call this in b2ContactListener::PreSolve.
* The value persists until you set or reset.
*/
var restitution: Double = b2MixRestitution(fixtureA.restitution, fixtureB.restitution)
/**
* Override the default restitution velocity threshold mixture. You can call this in b2ContactListener::PreSolve.
* The value persists until you set or reset.
* Get the restitution threshold.
*/
var restitutionThreshold: Double = b2MixRestitutionThreshold(fixtureA.restitutionThreshold, fixtureB.restitutionThreshold)
/**
* Set the desired tangent speed for a conveyor belt behavior. In meters per second.
* Get the desired tangent speed. In meters per second.
*/
var tangentSpeed: Double = 0.0
internal var toiCount: Int = 0 internal var toiCount: Int = 0
internal var toi: Double = 0.0 internal var toi: Double = 0.0
override val worldManifold: IWorldManifold get() { /**
* Evaluate this contact with your own manifold and transforms.
*/
abstract fun evaluate(xfA: Transform, xfB: Transform): Manifold
/**
* Get the world manifold.
*/
val worldManifold: IWorldManifold get() {
val bodyA = checkNotNull(fixtureA.body) { "FixtureA has no body attached" } val bodyA = checkNotNull(fixtureA.body) { "FixtureA has no body attached" }
val bodyB = checkNotNull(fixtureB.body) { "FixtureB has no body attached" } val bodyB = checkNotNull(fixtureB.body) { "FixtureB has no body attached" }
@ -69,15 +129,23 @@ sealed class AbstractContact(
return WorldManifold(manifold, bodyA.transform, shapeA.radius, bodyB.transform, shapeB.radius) return WorldManifold(manifold, bodyA.transform, shapeA.radius, bodyB.transform, shapeB.radius)
} }
override var isTouching: Boolean /**
* Is this contact touching?
*/
var isTouching: Boolean
get() = (flags and ContactFlags.TOUCHING.bitmask) == ContactFlags.TOUCHING.bitmask get() = (flags and ContactFlags.TOUCHING.bitmask) == ContactFlags.TOUCHING.bitmask
internal set(value) { flags = ContactFlags.TOUCHING.update(flags, value) } internal set(value) { flags = ContactFlags.TOUCHING.update(flags, value) }
override var isEnabled: Boolean /**
* Enable/disable this contact. This can be used inside the pre-solve
* contact listener. The contact is only disabled for the current
* time step (or sub-step in continuous collisions).
*/
var isEnabled: Boolean
get() = (flags and ContactFlags.ENABLED.bitmask) == ContactFlags.ENABLED.bitmask get() = (flags and ContactFlags.ENABLED.bitmask) == ContactFlags.ENABLED.bitmask
set(value) { flags = ContactFlags.ENABLED.update(flags, value) } set(value) { flags = ContactFlags.ENABLED.update(flags, value) }
override fun flagForFiltering() { fun flagForFiltering() {
flags = flags or ContactFlags.FILTER.bitmask flags = flags or ContactFlags.FILTER.bitmask
} }
@ -159,6 +227,27 @@ sealed class AbstractContact(
} }
} }
/**
* Reset the friction mixture to the default value.
*/
fun resetFriction() {
friction = b2MixFriction(fixtureA.friction, fixtureB.friction)
}
/**
* Reset the restitution to the default value.
*/
fun resetRestitution() {
restitution = b2MixRestitution(fixtureA.restitution, fixtureB.restitution)
}
/**
* Reset the restitution threshold to the default value.
*/
fun resetRestitutionThreshold() {
restitutionThreshold = b2MixRestitutionThreshold(fixtureA.restitutionThreshold, fixtureB.restitutionThreshold)
}
companion object { companion object {
private val registry = private val registry =
EnumMap<IShape.Type, EnumMap<IShape.Type, ContactFactory>>(IShape.Type::class.java) EnumMap<IShape.Type, EnumMap<IShape.Type, ContactFactory>>(IShape.Type::class.java)
@ -168,9 +257,9 @@ sealed class AbstractContact(
} }
internal fun create( internal fun create(
fixtureA: IFixture, fixtureA: Fixture,
indexA: Int, indexA: Int,
fixtureB: IFixture, fixtureB: Fixture,
indexB: Int, indexB: Int,
): AbstractContact { ): AbstractContact {
val type1 = fixtureA.type val type1 = fixtureA.type
@ -182,31 +271,31 @@ sealed class AbstractContact(
} }
init { init {
register(IShape.Type.POLYGON, IShape.Type.POLYGON) { fixtureA: IFixture, _: Int, fixtureB: IFixture, _: Int -> register(IShape.Type.POLYGON, IShape.Type.POLYGON) { fixtureA: Fixture, _: Int, fixtureB: Fixture, _: Int ->
return@register PolygonContact(fixtureA, fixtureB) return@register PolygonContact(fixtureA, fixtureB)
} }
register(IShape.Type.POLYGON, IShape.Type.CIRCLE) { fixtureA: IFixture, _: Int, fixtureB: IFixture, _: Int -> register(IShape.Type.POLYGON, IShape.Type.CIRCLE) { fixtureA: Fixture, _: Int, fixtureB: Fixture, _: Int ->
return@register PolygonCircleContact(fixtureA, fixtureB) return@register PolygonCircleContact(fixtureA, fixtureB)
} }
register(IShape.Type.CIRCLE, IShape.Type.CIRCLE) { fixtureA: IFixture, _: Int, fixtureB: IFixture, _: Int -> register(IShape.Type.CIRCLE, IShape.Type.CIRCLE) { fixtureA: Fixture, _: Int, fixtureB: Fixture, _: Int ->
return@register CircleContact(fixtureA, fixtureB) return@register CircleContact(fixtureA, fixtureB)
} }
register(IShape.Type.EDGE, IShape.Type.CIRCLE) { fixtureA: IFixture, _: Int, fixtureB: IFixture, _: Int -> register(IShape.Type.EDGE, IShape.Type.CIRCLE) { fixtureA: Fixture, _: Int, fixtureB: Fixture, _: Int ->
return@register EdgeCircleContact(fixtureA, fixtureB) return@register EdgeCircleContact(fixtureA, fixtureB)
} }
register(IShape.Type.EDGE, IShape.Type.POLYGON) { fixtureA: IFixture, _: Int, fixtureB: IFixture, _: Int -> register(IShape.Type.EDGE, IShape.Type.POLYGON) { fixtureA: Fixture, _: Int, fixtureB: Fixture, _: Int ->
return@register EdgePolygonContact(fixtureA, fixtureB) return@register EdgePolygonContact(fixtureA, fixtureB)
} }
register(IShape.Type.CHAIN, IShape.Type.POLYGON) { fixtureA: IFixture, indexA: Int, fixtureB: IFixture, indexB: Int -> register(IShape.Type.CHAIN, IShape.Type.POLYGON) { fixtureA: Fixture, indexA: Int, fixtureB: Fixture, indexB: Int ->
return@register ChainPolygonContact(fixtureA, indexA, fixtureB, indexB) return@register ChainPolygonContact(fixtureA, indexA, fixtureB, indexB)
} }
register(IShape.Type.CHAIN, IShape.Type.CIRCLE) { fixtureA: IFixture, indexA: Int, fixtureB: IFixture, indexB: Int -> register(IShape.Type.CHAIN, IShape.Type.CIRCLE) { fixtureA: Fixture, indexA: Int, fixtureB: Fixture, indexB: Int ->
return@register ChainCircleContact(fixtureA, indexA, fixtureB, indexB) return@register ChainCircleContact(fixtureA, indexA, fixtureB, indexB)
} }
} }

View File

@ -1,17 +1,17 @@
package ru.dbotthepony.kbox2d.dynamics.contact package ru.dbotthepony.kbox2d.dynamics.contact
import ru.dbotthepony.kbox2d.api.IFixture
import ru.dbotthepony.kbox2d.api.IShape import ru.dbotthepony.kbox2d.api.IShape
import ru.dbotthepony.kbox2d.api.Manifold import ru.dbotthepony.kbox2d.api.Manifold
import ru.dbotthepony.kbox2d.api.Transform import ru.dbotthepony.kbox2d.api.Transform
import ru.dbotthepony.kbox2d.collision.handler.b2CollideEdgeAndCircle import ru.dbotthepony.kbox2d.collision.handler.b2CollideEdgeAndCircle
import ru.dbotthepony.kbox2d.collision.shapes.ChainShape import ru.dbotthepony.kbox2d.collision.shapes.ChainShape
import ru.dbotthepony.kbox2d.collision.shapes.CircleShape import ru.dbotthepony.kbox2d.collision.shapes.CircleShape
import ru.dbotthepony.kbox2d.dynamics.Fixture
class ChainCircleContact( class ChainCircleContact(
fixtureA: IFixture, fixtureA: Fixture,
childIndexA: Int, childIndexA: Int,
fixtureB: IFixture, fixtureB: Fixture,
childIndexB: Int, childIndexB: Int,
) : AbstractContact(fixtureA, childIndexA, fixtureB, childIndexB) { ) : AbstractContact(fixtureA, childIndexA, fixtureB, childIndexB) {
init { init {

View File

@ -1,17 +1,17 @@
package ru.dbotthepony.kbox2d.dynamics.contact package ru.dbotthepony.kbox2d.dynamics.contact
import ru.dbotthepony.kbox2d.api.IFixture
import ru.dbotthepony.kbox2d.api.IShape import ru.dbotthepony.kbox2d.api.IShape
import ru.dbotthepony.kbox2d.api.Manifold import ru.dbotthepony.kbox2d.api.Manifold
import ru.dbotthepony.kbox2d.api.Transform import ru.dbotthepony.kbox2d.api.Transform
import ru.dbotthepony.kbox2d.collision.handler.b2CollideEdgeAndPolygon import ru.dbotthepony.kbox2d.collision.handler.b2CollideEdgeAndPolygon
import ru.dbotthepony.kbox2d.collision.shapes.ChainShape import ru.dbotthepony.kbox2d.collision.shapes.ChainShape
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
import ru.dbotthepony.kbox2d.dynamics.Fixture
class ChainPolygonContact( class ChainPolygonContact(
fixtureA: IFixture, fixtureA: Fixture,
childIndexA: Int, childIndexA: Int,
fixtureB: IFixture, fixtureB: Fixture,
childIndexB: Int, childIndexB: Int,
) : AbstractContact(fixtureA, childIndexA, fixtureB, childIndexB) { ) : AbstractContact(fixtureA, childIndexA, fixtureB, childIndexB) {
init { init {

View File

@ -1,15 +1,15 @@
package ru.dbotthepony.kbox2d.dynamics.contact package ru.dbotthepony.kbox2d.dynamics.contact
import ru.dbotthepony.kbox2d.api.IFixture
import ru.dbotthepony.kbox2d.api.IShape import ru.dbotthepony.kbox2d.api.IShape
import ru.dbotthepony.kbox2d.api.Manifold import ru.dbotthepony.kbox2d.api.Manifold
import ru.dbotthepony.kbox2d.api.Transform import ru.dbotthepony.kbox2d.api.Transform
import ru.dbotthepony.kbox2d.collision.handler.b2CollideCircles import ru.dbotthepony.kbox2d.collision.handler.b2CollideCircles
import ru.dbotthepony.kbox2d.collision.shapes.CircleShape import ru.dbotthepony.kbox2d.collision.shapes.CircleShape
import ru.dbotthepony.kbox2d.dynamics.Fixture
class CircleContact( class CircleContact(
fixtureA: IFixture, fixtureA: Fixture,
fixtureB: IFixture, fixtureB: Fixture,
) : AbstractContact(fixtureA, 0, fixtureB, 0) { ) : AbstractContact(fixtureA, 0, fixtureB, 0) {
init { init {
require(fixtureA.type == IShape.Type.CIRCLE) { "Fixture A is of type ${fixtureA.type}" } require(fixtureA.type == IShape.Type.CIRCLE) { "Fixture A is of type ${fixtureA.type}" }

View File

@ -1,16 +1,16 @@
package ru.dbotthepony.kbox2d.dynamics.contact package ru.dbotthepony.kbox2d.dynamics.contact
import ru.dbotthepony.kbox2d.api.IFixture
import ru.dbotthepony.kbox2d.api.IShape import ru.dbotthepony.kbox2d.api.IShape
import ru.dbotthepony.kbox2d.api.Manifold import ru.dbotthepony.kbox2d.api.Manifold
import ru.dbotthepony.kbox2d.api.Transform import ru.dbotthepony.kbox2d.api.Transform
import ru.dbotthepony.kbox2d.collision.handler.b2CollideEdgeAndCircle import ru.dbotthepony.kbox2d.collision.handler.b2CollideEdgeAndCircle
import ru.dbotthepony.kbox2d.collision.shapes.CircleShape import ru.dbotthepony.kbox2d.collision.shapes.CircleShape
import ru.dbotthepony.kbox2d.collision.shapes.EdgeShape import ru.dbotthepony.kbox2d.collision.shapes.EdgeShape
import ru.dbotthepony.kbox2d.dynamics.Fixture
class EdgeCircleContact( class EdgeCircleContact(
fixtureA: IFixture, fixtureA: Fixture,
fixtureB: IFixture, fixtureB: Fixture,
) : AbstractContact(fixtureA, 0, fixtureB, 0) { ) : AbstractContact(fixtureA, 0, fixtureB, 0) {
init { init {
require(fixtureA.type == IShape.Type.EDGE) { "Fixture A is of type ${fixtureA.type}, expected EDGE" } require(fixtureA.type == IShape.Type.EDGE) { "Fixture A is of type ${fixtureA.type}, expected EDGE" }

View File

@ -1,16 +1,16 @@
package ru.dbotthepony.kbox2d.dynamics.contact package ru.dbotthepony.kbox2d.dynamics.contact
import ru.dbotthepony.kbox2d.api.IFixture
import ru.dbotthepony.kbox2d.api.IShape import ru.dbotthepony.kbox2d.api.IShape
import ru.dbotthepony.kbox2d.api.Manifold import ru.dbotthepony.kbox2d.api.Manifold
import ru.dbotthepony.kbox2d.api.Transform import ru.dbotthepony.kbox2d.api.Transform
import ru.dbotthepony.kbox2d.collision.handler.b2CollideEdgeAndPolygon import ru.dbotthepony.kbox2d.collision.handler.b2CollideEdgeAndPolygon
import ru.dbotthepony.kbox2d.collision.shapes.EdgeShape import ru.dbotthepony.kbox2d.collision.shapes.EdgeShape
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
import ru.dbotthepony.kbox2d.dynamics.Fixture
class EdgePolygonContact( class EdgePolygonContact(
fixtureA: IFixture, fixtureA: Fixture,
fixtureB: IFixture, fixtureB: Fixture,
) : AbstractContact(fixtureA, 0, fixtureB, 0) { ) : AbstractContact(fixtureA, 0, fixtureB, 0) {
init { init {
require(fixtureA.type == IShape.Type.EDGE) { "Fixture A is of type ${fixtureA.type}, expected EDGE" } require(fixtureA.type == IShape.Type.EDGE) { "Fixture A is of type ${fixtureA.type}, expected EDGE" }

View File

@ -1,16 +1,16 @@
package ru.dbotthepony.kbox2d.dynamics.contact package ru.dbotthepony.kbox2d.dynamics.contact
import ru.dbotthepony.kbox2d.api.IFixture
import ru.dbotthepony.kbox2d.api.IShape import ru.dbotthepony.kbox2d.api.IShape
import ru.dbotthepony.kbox2d.api.Manifold import ru.dbotthepony.kbox2d.api.Manifold
import ru.dbotthepony.kbox2d.api.Transform import ru.dbotthepony.kbox2d.api.Transform
import ru.dbotthepony.kbox2d.collision.handler.b2CollidePolygonAndCircle import ru.dbotthepony.kbox2d.collision.handler.b2CollidePolygonAndCircle
import ru.dbotthepony.kbox2d.collision.shapes.CircleShape import ru.dbotthepony.kbox2d.collision.shapes.CircleShape
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
import ru.dbotthepony.kbox2d.dynamics.Fixture
class PolygonCircleContact( class PolygonCircleContact(
fixtureA: IFixture, fixtureA: Fixture,
fixtureB: IFixture, fixtureB: Fixture,
) : AbstractContact(fixtureA, 0, fixtureB, 0) { ) : AbstractContact(fixtureA, 0, fixtureB, 0) {
init { init {
require(fixtureA.type == IShape.Type.POLYGON) { "Fixture A is of type ${fixtureA.type}, expected POLYGON" } require(fixtureA.type == IShape.Type.POLYGON) { "Fixture A is of type ${fixtureA.type}, expected POLYGON" }

View File

@ -1,15 +1,15 @@
package ru.dbotthepony.kbox2d.dynamics.contact package ru.dbotthepony.kbox2d.dynamics.contact
import ru.dbotthepony.kbox2d.api.IFixture
import ru.dbotthepony.kbox2d.api.IShape import ru.dbotthepony.kbox2d.api.IShape
import ru.dbotthepony.kbox2d.api.Manifold import ru.dbotthepony.kbox2d.api.Manifold
import ru.dbotthepony.kbox2d.api.Transform import ru.dbotthepony.kbox2d.api.Transform
import ru.dbotthepony.kbox2d.collision.handler.b2CollidePolygons import ru.dbotthepony.kbox2d.collision.handler.b2CollidePolygons
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
import ru.dbotthepony.kbox2d.dynamics.Fixture
class PolygonContact( class PolygonContact(
fixtureA: IFixture, fixtureA: Fixture,
fixtureB: IFixture, fixtureB: Fixture,
) : AbstractContact(fixtureA, 0, fixtureB, 0) { ) : AbstractContact(fixtureA, 0, fixtureB, 0) {
init { init {
require(fixtureA.type == IShape.Type.POLYGON) { "Fixture A has type of ${fixtureA.type}" } require(fixtureA.type == IShape.Type.POLYGON) { "Fixture A has type of ${fixtureA.type}" }

View File

@ -9,7 +9,7 @@ fun interface JointFactory {
fun factorize(jointDef: IJointDef): AbstractJoint fun factorize(jointDef: IJointDef): AbstractJoint
} }
sealed class AbstractJoint(def: IJointDef) : IJoint { sealed class AbstractJoint(def: IJointDef) : IMovable {
init { init {
require(def.bodyA != def.bodyB) { "Tried to create join on same body" } require(def.bodyA != def.bodyB) { "Tried to create join on same body" }
} }
@ -18,9 +18,28 @@ sealed class AbstractJoint(def: IJointDef) : IJoint {
protected var index: Int = 0 protected var index: Int = 0
override val collideConnected: Boolean = def.collideConnected /**
override val type: JointType = def.type * Get collide connected.
override var userData: Any? = def.userData * Note: modifying the collide connect flag won't work correctly because
* the flag is only checked when fixture AABBs begin to overlap.
*/
val collideConnected: Boolean = def.collideConnected
/**
* Dump this joint to the log file.
*/
open fun dump() { }
/**
* Debug draw this joint
*/
open fun draw(draw: IDebugDraw) { }
/**
* Get the type of the concrete joint.
*/
val type: JointType = def.type
var userData: Any? = def.userData
// KBox2D: In original code, nothing expects bodies to be null // KBox2D: In original code, nothing expects bodies to be null
// but certain joints (notably, mouse joint) have no meaningful // but certain joints (notably, mouse joint) have no meaningful
@ -31,8 +50,35 @@ sealed class AbstractJoint(def: IJointDef) : IJoint {
protected var _bodyA: Body? = def.bodyA as Body? protected var _bodyA: Body? = def.bodyA as Body?
protected var _bodyB: Body? = def.bodyB as Body? protected var _bodyB: Body? = def.bodyB as Body?
final override val bodyA: Body get() = checkNotNull(_bodyA) { "Body A is not present" } /**
final override val bodyB: Body get() = checkNotNull(_bodyB) { "Body B is not present" } * Get the first body attached to this joint.
*/
val bodyA: Body get() = checkNotNull(_bodyA) { "Body A is not present" }
/**
* Get the second body attached to this joint.
*/
val bodyB: Body get() = checkNotNull(_bodyB) { "Body B is not present" }
/**
* Get the anchor point on bodyA in world coordinates.
*/
abstract val anchorA: Vector2d
/**
* Get the anchor point on bodyB in world coordinates.
*/
abstract val anchorB: Vector2d
/**
* Get the reaction force on bodyB at the joint anchor in Newtons.
*/
abstract fun getReactionForce(inv_dt: Double): Vector2d
/**
* Get the reaction torque on bodyB in N*m.
*/
abstract fun getReactionTorque(inv_dt: Double): Double
val hasBodyA: Boolean get() = _bodyA != null val hasBodyA: Boolean get() = _bodyA != null
val hasBodyB: Boolean get() = _bodyB != null val hasBodyB: Boolean get() = _bodyB != null
@ -58,11 +104,26 @@ sealed class AbstractJoint(def: IJointDef) : IJoint {
_bodyB?.jointList = edgeB _bodyB?.jointList = edgeB
} }
final override var next: IJoint? = null /**
* Get the next joint the world joint list.
*
* Kotlin: This is not a list, but, as in C++ impl, a custom
* linked list.
*/
var next: AbstractJoint? = null
internal set internal set
final override var prev: IJoint? = null var prev: AbstractJoint? = null
internal set internal set
fun destroy() {
bodyA.world.destroyJoint(this)
}
/**
* Short-cut function to determine if either body is enabled.
*/
val isEnabled: Boolean get() = bodyA.isEnabled || bodyB.isEnabled
/** /**
* Signals that this joint was destroyed, invalidate stuff * Signals that this joint was destroyed, invalidate stuff
* to fail-fast this object * to fail-fast this object

View File

@ -8,6 +8,7 @@ import ru.dbotthepony.kbox2d.collision.shapes.ChainShape
import ru.dbotthepony.kbox2d.collision.shapes.CircleShape import ru.dbotthepony.kbox2d.collision.shapes.CircleShape
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
import ru.dbotthepony.kbox2d.dynamics.B2World import ru.dbotthepony.kbox2d.dynamics.B2World
import ru.dbotthepony.kbox2d.dynamics.Body
import ru.dbotthepony.kbox2d.dynamics.joint.MouseJoint import ru.dbotthepony.kbox2d.dynamics.joint.MouseJoint
import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.world.Chunk import ru.dbotthepony.kstarbound.world.Chunk
@ -53,7 +54,7 @@ fun main() {
ground.createFixture(groundPoly, 0.0) ground.createFixture(groundPoly, 0.0)
val boxes = ArrayList<ru.dbotthepony.kbox2d.api.IBody>() val boxes = ArrayList<Body>()
/*run { /*run {
val movingDef = BodyDef( val movingDef = BodyDef(