diff --git a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/Body.kt b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/Body.kt index a0446169..24a17738 100644 --- a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/Body.kt +++ b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/Body.kt @@ -148,353 +148,3 @@ enum class BodyFlags(val bitmask: Int) { 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 get() { - return object : Iterator { - 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 get() { - return object : Iterator { - 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 get() { - return object : Iterator { - 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() -} diff --git a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/BroadPhase.kt b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/BroadPhase.kt deleted file mode 100644 index 479dc765..00000000 --- a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/BroadPhase.kt +++ /dev/null @@ -1,47 +0,0 @@ -package ru.dbotthepony.kbox2d.api - -typealias b2Pair = Pair - -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 -} diff --git a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/Contact.kt b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/Contact.kt index a57ccd69..5b675a13 100644 --- a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/Contact.kt +++ b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/Contact.kt @@ -1,5 +1,7 @@ package ru.dbotthepony.kbox2d.api +import ru.dbotthepony.kbox2d.dynamics.Body +import ru.dbotthepony.kbox2d.dynamics.contact.AbstractContact import kotlin.math.sqrt /// 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. */ data class ContactEdge( - val other: ru.dbotthepony.kbox2d.api.IBody, ///< provides quick access to the other body attached. - val contact: IContact, ///< the contact + val other: Body, ///< provides quick access to the other body attached. + val contact: AbstractContact, ///< the contact 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 ) @@ -68,79 +70,3 @@ enum class ContactFlags(val bitmask: Int) { 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 -} diff --git a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/ContactManager.kt b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/ContactManager.kt deleted file mode 100644 index 594f02c5..00000000 --- a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/ContactManager.kt +++ /dev/null @@ -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 get() { - return object : Iterator { - 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? -} diff --git a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/Distance.kt b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/Distance.kt index 8d10b461..d895a08e 100644 --- a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/Distance.kt +++ b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/Distance.kt @@ -1,26 +1,8 @@ package ru.dbotthepony.kbox2d.api +import ru.dbotthepony.kbox2d.collision.DistanceProxy 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 - 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. * Set count to zero on first call. @@ -38,8 +20,8 @@ data class SimplexCache( * in the computation. Even */ data class DistanceInput( - var proxyA: IDistanceProxy, - var proxyB: IDistanceProxy, + var proxyA: DistanceProxy, + var proxyB: DistanceProxy, var transformA: Transform = Transform(), var transformB: Transform = Transform(), var useRadii: Boolean = false @@ -57,8 +39,8 @@ data class DistanceOutput( ) data class ShapeCastInput( - var proxyA: IDistanceProxy, - var proxyB: IDistanceProxy, + var proxyA: DistanceProxy, + var proxyB: DistanceProxy, var transformA: Transform, var transformB: Transform, var translationB: Vector2d, diff --git a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/DynamicTree.kt b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/DynamicTree.kt deleted file mode 100644 index e8b4d620..00000000 --- a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/DynamicTree.kt +++ /dev/null @@ -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() -} diff --git a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/Fixture.kt b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/Fixture.kt index 48fd87f9..faec56a4 100644 --- a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/Fixture.kt +++ b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/Fixture.kt @@ -1,5 +1,7 @@ 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.vector.ndouble.Vector2d @@ -40,26 +42,38 @@ data class ImmutableFilter( ) : IFilter 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 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, - /// 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, - /// The density, usually in kg/m^2. + /** + * The density, usually in kg/m^2. + */ 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, - /// Use this to store application specific fixture data. + /** + * Use this to store application specific fixture data. + */ var userData: Any? = null, val filter: Filter = Filter() @@ -67,7 +81,7 @@ data class FixtureDef( data class FixtureProxy( var aabb: AABB, - val fixture: IFixture, + val fixture: Fixture, val childIndex: Int, ) { 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)") } } - -/** - * 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 -} diff --git a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/IProxieable.kt b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/IProxieable.kt index 8f24c9aa..1af78907 100644 --- a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/IProxieable.kt +++ b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/IProxieable.kt @@ -2,6 +2,8 @@ package ru.dbotthepony.kbox2d.api import ru.dbotthepony.kvector.util2d.AABB import ru.dbotthepony.kvector.vector.ndouble.Vector2d +import ru.dbotthepony.kbox2d.collision.DynamicTree +import ru.dbotthepony.kbox2d.collision.BroadPhase fun interface ProxyQueryCallback { fun invoke(nodeId: Int, userData: Any?): Boolean @@ -11,33 +13,33 @@ fun interface ProxyRayCastCallback { 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 * UpdatePairs is called. * - * For [IDynamicTree]: + * For [DynamicTree]: * Create a proxy. Provide a tight fitting AABB and a userData pointer. */ fun createProxy(aabb: AABB, userData: Any?): Int /** - * For [IBroadPhase]: + * For [BroadPhase]: * 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. */ fun destroyProxy(proxyID: Int) /** - * For [IBroadPhase]: + * For [BroadPhase]: * Call MoveProxy as many times as you like, then when you are done * call UpdatePairs to finalized the proxy pairs (for your time step). * @return true * - * For [IDynamicTree]: + * For [DynamicTree]: * 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 * the function returns immediately. diff --git a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/Joint.kt b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/Joint.kt index ea12faad..18e8dd28 100644 --- a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/Joint.kt +++ b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/Joint.kt @@ -1,5 +1,6 @@ package ru.dbotthepony.kbox2d.api +import ru.dbotthepony.kbox2d.dynamics.Body import ru.dbotthepony.kbox2d.dynamics.joint.AbstractJoint import ru.dbotthepony.kvector.vector.ndouble.Vector2d import kotlin.math.PI @@ -12,8 +13,8 @@ data class StiffnessResult(val stiffness: Double, val damping: Double) fun b2LinearStiffness( frequencyHertz: Double, dampingRatio: Double, - bodyA: IBody?, - bodyB: IBody?, + bodyA: Body?, + bodyB: Body?, ): StiffnessResult { val massA = bodyA?.mass ?: 0.0 val massB = bodyB?.mass ?: 0.0 @@ -41,8 +42,8 @@ fun b2LinearStiffness( fun b2AngularStiffness( frequencyHertz: Double, dampingRatio: Double, - bodyA: IBody?, - bodyB: IBody?, + bodyA: Body?, + bodyB: Body?, ): StiffnessResult { val inertiaA = bodyA?.inertia ?: 0.0 val inertiaB = bodyB?.inertia ?: 0.0 @@ -91,13 +92,13 @@ data class Jacobian( * nodes, one for each attached body. */ class JointEdge( - other: IBody?, ///< provides quick access to the other body attached. - val joint: IJoint, ///< the joint + other: Body?, ///< provides quick access to the other body attached. + val joint: AbstractJoint, ///< the joint 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 ) { - val otherNullable: IBody? = other - val other: IBody get() = checkNotNull(otherNullable) { "Other body is not present" } + val otherNullable: Body? = other + val other: Body get() = checkNotNull(otherNullable) { "Other body is not present" } } sealed interface IJointDef { @@ -109,12 +110,12 @@ sealed interface IJointDef { /** * The first attached body. */ - val bodyA: IBody? + val bodyA: Body? /** * The second attached body. */ - val bodyB: IBody? + val bodyB: Body? /** * Set this flag to true if the attached bodies should collide. @@ -128,8 +129,8 @@ sealed interface IJointDef { } class DistanceJointDef( - b1: IBody, - b2: IBody, + b1: Body, + b2: Body, anchor1: Vector2d, anchor2: Vector2d ) : IJointDef { @@ -160,8 +161,8 @@ class DistanceJointDef( */ val damping: Double = 0.0 - override var bodyA: IBody = b1 - override var bodyB: IBody = b2 + override var bodyA: Body = b1 + override var bodyB: Body = b2 override var collideConnected: Boolean = false override var userData: Any? = null @@ -185,13 +186,13 @@ class DistanceJointDef( } class RevoluteJointDef( - b1: IBody, - b2: IBody, + b1: Body, + b2: Body, anchor: Vector2d, ) : IJointDef { override val type: JointType = JointType.REVOLUTE - override var bodyA: IBody = b1 - override var bodyB: IBody = b2 + override var bodyA: Body = b1 + override var bodyB: Body = b2 override var collideConnected: Boolean = false override var userData: Any? = null @@ -244,14 +245,14 @@ class RevoluteJointDef( } class PrismaticJointDef( - b1: IBody, - b2: IBody, + b1: Body, + b2: Body, anchor: Vector2d, axis: Vector2d, ) : IJointDef { override val type: JointType = JointType.PRISMATIC - override var bodyA: IBody = b1 - override var bodyB: IBody = b2 + override var bodyA: Body = b1 + override var bodyB: Body = b2 override var collideConnected: Boolean = false override var userData: Any? = null @@ -311,8 +312,8 @@ class PrismaticJointDef( * two dynamic body anchor points, and a pulley ratio. */ class PulleyJointDef( - b1: IBody, - b2: IBody, + b1: Body, + b2: Body, /** * The first ground anchor in world coordinates. This point never moves. @@ -329,8 +330,8 @@ class PulleyJointDef( ratio: Double, ) : IJointDef { override val type: JointType = JointType.PULLEY - override var bodyA: IBody = b1 - override var bodyB: IBody = b2 + override var bodyA: Body = b1 + override var bodyB: Body = b2 override var collideConnected: Boolean = false override var userData: Any? = null @@ -375,8 +376,8 @@ class PulleyJointDef( * @warning bodyB on the input joints must both be dynamic */ class GearJointDef( - override var bodyA: IBody, - override var bodyB: IBody, + override var bodyA: Body, + override var bodyB: Body, /** * The first revolute/prismatic joint attached to the gear joint. @@ -416,8 +417,8 @@ class MouseJointDef( */ var target: Vector2d, - override var bodyB: IBody, - override val bodyA: IBody? = null, + override var bodyB: Body, + override val bodyA: Body? = null, /** * The maximum constraint force that can be exerted @@ -436,8 +437,8 @@ class MouseJointDef( fun linearStiffness( frequencyHertz: Double, dampingRatio: Double, - bodyA: IBody?, - bodyB: IBody? + bodyA: Body?, + bodyB: Body? ): MouseJointDef { val (stiffness, damping) = b2LinearStiffness(frequencyHertz, dampingRatio, bodyA, bodyB) this.stiffness = stiffness @@ -448,8 +449,8 @@ class MouseJointDef( fun angularStiffness( frequencyHertz: Double, dampingRatio: Double, - bodyA: IBody?, - bodyB: IBody? + bodyA: Body?, + bodyB: Body? ): MouseJointDef { val (stiffness, damping) = b2AngularStiffness(frequencyHertz, dampingRatio, bodyA, bodyB) this.stiffness = stiffness @@ -467,8 +468,8 @@ class MouseJointDef( * anchors and a local axis helps when saving and loading a game. */ class WheelJointDef( - override var bodyA: IBody, - override var bodyB: IBody, + override var bodyA: Body, + override var bodyB: Body, anchor: Vector2d, axis: Vector2d, @@ -534,8 +535,8 @@ class WheelJointDef( fun linearStiffness( frequencyHertz: Double, dampingRatio: Double, - bodyA: IBody = this.bodyA, - bodyB: IBody = this.bodyB + bodyA: Body = this.bodyA, + bodyB: Body = this.bodyB ): WheelJointDef { val (stiffness, damping) = b2LinearStiffness(frequencyHertz, dampingRatio, bodyA, bodyB) this.stiffness = stiffness @@ -546,8 +547,8 @@ class WheelJointDef( fun angularStiffness( frequencyHertz: Double, dampingRatio: Double, - bodyA: IBody = this.bodyA, - bodyB: IBody = this.bodyB + bodyA: Body = this.bodyA, + bodyB: Body = this.bodyB ): WheelJointDef { val (stiffness, damping) = b2AngularStiffness(frequencyHertz, dampingRatio, bodyA, bodyB) this.stiffness = stiffness @@ -562,8 +563,8 @@ class WheelJointDef( * of the anchor points is important for computing the reaction torque. */ class WeldJointDef( - override var bodyA: IBody, - override var bodyB: IBody, + override var bodyA: Body, + override var bodyB: Body, anchor: Vector2d, /** @@ -598,8 +599,8 @@ class WeldJointDef( fun linearStiffness( frequencyHertz: Double, dampingRatio: Double, - bodyA: IBody = this.bodyA, - bodyB: IBody = this.bodyB + bodyA: Body = this.bodyA, + bodyB: Body = this.bodyB ): WeldJointDef { val (stiffness, damping) = b2LinearStiffness(frequencyHertz, dampingRatio, bodyA, bodyB) this.stiffness = stiffness @@ -610,8 +611,8 @@ class WeldJointDef( fun angularStiffness( frequencyHertz: Double, dampingRatio: Double, - bodyA: IBody = this.bodyA, - bodyB: IBody = this.bodyB + bodyA: Body = this.bodyA, + bodyB: Body = this.bodyB ): WeldJointDef { val (stiffness, damping) = b2AngularStiffness(frequencyHertz, dampingRatio, bodyA, bodyB) this.stiffness = stiffness @@ -624,8 +625,8 @@ class WeldJointDef( * Friction joint definition. */ class FrictionJointDef( - override var bodyA: IBody, - override var bodyB: IBody, + override var bodyA: Body, + override var bodyB: Body, anchor: Vector2d, /** @@ -655,8 +656,8 @@ class FrictionJointDef( } class MotorJointDef( - override var bodyA: IBody, - override var bodyB: IBody, + override var bodyA: Body, + override var bodyB: Body, /** * The maximum motor force in N. @@ -692,77 +693,3 @@ class MotorJointDef( 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? -} diff --git a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/Shape.kt b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/Shape.kt index 97ca3c83..40a8a479 100644 --- a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/Shape.kt +++ b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/Shape.kt @@ -9,9 +9,12 @@ data class MassData( 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> { enum class Type { CIRCLE, @@ -20,41 +23,58 @@ interface IShape> { CHAIN, } - /// Clone the concrete shape. + /** + * Clone the concrete shape. + */ 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 - /// Get the number of child primitives. + /** + * Get the number of child primitives. + */ 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 } - /// Cast a ray against a child shape. - /// @param output the ray-cast results. - /// @param input the ray-cast input parameters. - /// @param transform the transform to be applied to the shape. - /// @param childIndex the child shape index + /** + * Cast a ray against a child shape. + * @param output the ray-cast results. + * @param input the ray-cast input parameters. + * @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 - /// Given a transform, compute the associated axis aligned bounding box for a child shape. - /// @param aabb returns the axis aligned box. - /// @param xf the world transform of the shape. - /// @param childIndex the child shape + /** + * Given a transform, compute the associated axis aligned bounding box for a child shape. + * @param aabb returns the axis aligned box. + * @param xf the world transform of the shape. + * @param childIndex the child shape + */ 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. - /// @param massData returns the mass data for this shape. - /// @param density the density in kilograms per meter squared. + /** + * Compute the mass properties of this shape using its dimensions and density. + * The inertia tensor is computed about the local origin. + * @param massData returns the mass data for this shape. + * @param density the density in kilograms per meter squared. + */ 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 } diff --git a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/ShapeDefs.kt b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/ShapeDefs.kt deleted file mode 100644 index 5fe626c7..00000000 --- a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/ShapeDefs.kt +++ /dev/null @@ -1,2 +0,0 @@ -package ru.dbotthepony.kbox2d.api - diff --git a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/TimeOfImpact.kt b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/TimeOfImpact.kt index c22a065f..354b5844 100644 --- a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/TimeOfImpact.kt +++ b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/TimeOfImpact.kt @@ -1,11 +1,13 @@ package ru.dbotthepony.kbox2d.api +import ru.dbotthepony.kbox2d.collision.DistanceProxy + /** * Input parameters for b2TimeOfImpact */ data class TOIInput( - var proxyA: IDistanceProxy, - var proxyB: IDistanceProxy, + var proxyA: DistanceProxy, + var proxyB: DistanceProxy, var sweepA: Sweep, var sweepB: Sweep, var tMax: Double, // defines sweep interval [0, tMax] diff --git a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/World.kt b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/World.kt deleted file mode 100644 index a4354ae5..00000000 --- a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/World.kt +++ /dev/null @@ -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 get() { - return object : Iterator { - 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 get() { - return object : Iterator { - 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 get() { - return object : Iterator { - 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() -} diff --git a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/WorldCallbacks.kt b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/WorldCallbacks.kt index cc243454..14434025 100644 --- a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/WorldCallbacks.kt +++ b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/api/WorldCallbacks.kt @@ -1,5 +1,8 @@ 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 /** @@ -11,7 +14,7 @@ interface IContactFilter { * 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. */ - 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 * 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 * 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. */ - fun beginContact(contact: IContact) + fun beginContact(contact: AbstractContact) /** * 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 @@ -84,7 +87,7 @@ interface IContactListener { * get an EndContact callback. However, you may get a BeginContact callback * 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 @@ -96,7 +99,7 @@ interface IContactListener { * * 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. * @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 * 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 } diff --git a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/collision/BroadPhase.kt b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/collision/BroadPhase.kt index 5cffc7e3..9c32e883 100644 --- a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/collision/BroadPhase.kt +++ b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/collision/BroadPhase.kt @@ -4,6 +4,10 @@ import ru.dbotthepony.kbox2d.api.* import ru.dbotthepony.kvector.util2d.AABB import ru.dbotthepony.kvector.vector.ndouble.Vector2d +typealias b2Pair = Pair + +const val e_nullProxy = -1 + private class IntArrayList { private var buffer = IntArray(16) 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 pairBuffer = ArrayList() private var moveCount = 0 private val tree = DynamicTree() - override var proxyCount: Int = 0 + /** + * Get the number of proxies. + */ + var proxyCount: Int = 0 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) } @@ -107,16 +122,28 @@ class BroadPhase : IBroadPhase { return tree.getFatAABB(proxyID) } - override val treeHeight: Int + /** + * Get the height of the embedded tree. + */ + val treeHeight: Int get() = tree.height - override val treeBalance: Int + /** + * Get the balance of the embedded tree. + */ + val treeBalance: Int get() = tree.maxBalance - override val treeQuality: Double + /** + * Get the quality metric of the embedded tree. + */ + val treeQuality: Double 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)) } @@ -143,7 +170,10 @@ class BroadPhase : IBroadPhase { 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() for (i in 0 until moveCount) { diff --git a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/collision/Distance.kt b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/collision/Distance.kt index 06f7a1b3..612b49de 100644 --- a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/collision/Distance.kt +++ b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/collision/Distance.kt @@ -17,9 +17,13 @@ var b2_gjkIters = 0 var b2_gjkMaxIters = 0 private set -class DistanceProxy : IDistanceProxy { - override val vertices: List - override val radius: Double +/** + * A distance proxy is used by the GJK algorithm. + * It encapsulates any shape. + */ +class DistanceProxy { + val vertices: List + val radius: Double constructor(shape: IShape<*>, index: Int) { when (shape.type) { @@ -63,7 +67,10 @@ class DistanceProxy : IDistanceProxy { 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 bestValue = b2Dot(vertices[0], d) @@ -79,7 +86,10 @@ class DistanceProxy : IDistanceProxy { 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)] } } @@ -116,9 +126,9 @@ class Simplex() { constructor( cache: SimplexCache, - proxyA: IDistanceProxy, + proxyA: DistanceProxy, transformA: Transform, - proxyB: IDistanceProxy, + proxyB: DistanceProxy, transformB: Transform, ) : this() { check(cache.count <= 3) @@ -413,8 +423,8 @@ private const val k_maxIters = 20 */ fun b2Distance( cache: SimplexCache, - proxyA: IDistanceProxy, - proxyB: IDistanceProxy, + proxyA: DistanceProxy, + proxyB: DistanceProxy, transformA: Transform, transformB: Transform, useRadii: Boolean = false @@ -553,8 +563,8 @@ private const val gjk_tolerance = 0.5 * b2_linearSlop */ fun b2ShapeCast( output: ShapeCastOutput, - proxyA: IDistanceProxy, - proxyB: IDistanceProxy, + proxyA: DistanceProxy, + proxyB: DistanceProxy, xfA: Transform, xfB: Transform, r: Vector2d, diff --git a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/collision/DynamicTree.kt b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/collision/DynamicTree.kt index ad3bad09..bd135257 100644 --- a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/collision/DynamicTree.kt +++ b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/collision/DynamicTree.kt @@ -5,9 +5,36 @@ import ru.dbotthepony.kvector.util2d.AABB import ru.dbotthepony.kvector.vector.ndouble.Vector2d 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 -class DynamicTree : IDynamicTree { +class DynamicTree : IProxieable, IMovable { private var root: Int = b2_nullNode private val nodeCapacity get() = nodes.size @@ -15,7 +42,7 @@ class DynamicTree : IDynamicTree { private var freeList = 0 private var insertionCount = 0 - internal var nodes = Array(16) { TreeNode(this) } + private var nodes = Array(16) { TreeNode(this) } init { // Build a linked list for the free list. @@ -162,11 +189,11 @@ class DynamicTree : IDynamicTree { return nodes[proxyID].userData } - override fun wasMoved(proxyID: Int): Boolean { + fun wasMoved(proxyID: Int): Boolean { return nodes[proxyID].moved } - override fun clearMoved(proxyID: Int) { + fun clearMoved(proxyID: Int) { nodes[proxyID].moved = false } @@ -476,9 +503,16 @@ class DynamicTree : IDynamicTree { 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) return 0.0 @@ -562,7 +596,10 @@ class DynamicTree : IDynamicTree { validateMetrics(child2) } - override fun validate() { + /** + * Validate this tree. For testing. + */ + fun validate() { validateStructure(root) validateMetrics(root) @@ -578,7 +615,11 @@ class DynamicTree : IDynamicTree { 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 for (node in nodes) { @@ -596,7 +637,10 @@ class DynamicTree : IDynamicTree { return maxBalance } - override fun rebuildBottomUp() { + /** + * Build an optimal tree. Very expensive. For testing. + */ + fun rebuildBottomUp() { TODO("Not Yet Implemented") } diff --git a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/collision/TimeOfImpact.kt b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/collision/TimeOfImpact.kt index ab2f10ef..9c075bb8 100644 --- a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/collision/TimeOfImpact.kt +++ b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/collision/TimeOfImpact.kt @@ -34,8 +34,8 @@ private data class MinSeparationResult( private class SeparationFunction( cache: SimplexCache, - val proxyA: IDistanceProxy, - val proxyB: IDistanceProxy, + val proxyA: DistanceProxy, + val proxyB: DistanceProxy, val sweepA: Sweep, val sweepB: Sweep, 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. */ fun b2TimeOfImpact( - proxyA: IDistanceProxy, - proxyB: IDistanceProxy, + proxyA: DistanceProxy, + proxyB: DistanceProxy, _sweepA: Sweep, _sweepB: Sweep, tMax: Double, // defines sweep interval [0, tMax] diff --git a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/B2World.kt b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/B2World.kt index 89d8fe4f..4ae417ee 100644 --- a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/B2World.kt +++ b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/B2World.kt @@ -1,3 +1,6 @@ + +@file:Suppress("unused") + package ru.dbotthepony.kbox2d.dynamics 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.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 - override var jointCount: Int = 0 + var jointCount: Int = 0 private set - override val contactManager: IContactManager = ContactManager() + val contactManager = ContactManager() - override var destructionListener: IDestructionListener? = null - override var contactFilter: IContactFilter? by contactManager::contactFilter - override var contactListener: IContactListener? by contactManager::contactListener + /** + * 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: AbstractContact? get() = contactManager.contactList + val contactListIterator: Iterator 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 - override var jointList: IJoint? = null + /** + * 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? 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 - override var warmStarting: Boolean = true - override var continuousPhysics: Boolean = true - override var enableSubStepping: Boolean = false + /** + * 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. + */ + var jointList: AbstractJoint? = null + private set - override var allowAutoSleep: Boolean = true + val bodyListIterator: Iterator get() { + return object : Iterator { + 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 get() { + return object : Iterator { + 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) { if (value == field) 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 - 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 newContacts = false private val profile = ProfileData() - override fun notifyNewContacts() { + fun notifyNewContacts() { 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) throw ConcurrentModificationException() val body = Body(bodyDef, this) body.next = bodyList - (bodyList as Body?)?.prev = body + bodyList?.prev = body bodyList = body bodyCount++ 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) throw ConcurrentModificationException() 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. 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. for (fixture in body.fixtureIterator) { destructionListener?.sayGoodbye(fixture) - (fixture as Fixture).destroyProxies(contactManager.broadPhase) + fixture.destroyProxies(contactManager.broadPhase) } // Remove world body list. - val prev = body.prev as Body? - val next = body.next as Body? + val prev = body.prev + val next = body.next prev?.next = next next?.prev = prev @@ -116,10 +247,15 @@ class B2World(override var gravity: Vector2d) : IB2World { } 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) throw ConcurrentModificationException() @@ -127,7 +263,7 @@ class B2World(override var gravity: Vector2d) : IB2World { // Connect to the world list. joint.next = jointList - (jointList as AbstractJoint?)?.prev = joint + jointList?.prev = joint jointList = joint jointCount++ @@ -150,12 +286,15 @@ class B2World(override var gravity: Vector2d) : IB2World { 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) throw ConcurrentModificationException() 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" } if (!joint.isValid) { @@ -164,8 +303,8 @@ class B2World(override var gravity: Vector2d) : IB2World { // Remove from the doubly linked list. this.run { - val prev = joint.prev as AbstractJoint? - val next = joint.next as AbstractJoint? + val prev = joint.prev + val next = joint.next prev?.next = next next?.prev = prev @@ -241,25 +380,20 @@ class B2World(override var gravity: Vector2d) : IB2World { // Clear all the island flags. for (body in bodyListIterator) { - body as Body body.isOnIsland = false } for (contact in contactListIterator) { - contact as AbstractContact contact.isOnIsland = false } for (joint in jointListIterator) { - joint as AbstractJoint check(joint.isValid) { "$joint is no longer valid, but present in linked list" } joint.isOnIsland = false } // Build and simulate all awake islands. for (seed in bodyListIterator) { - seed as Body - if (seed.type == BodyType.STATIC || !seed.isAwake || !seed.isEnabled || seed.isOnIsland) { continue } @@ -287,7 +421,7 @@ class B2World(override var gravity: Vector2d) : IB2World { // Search all contacts connected to this body. for (ce in body.contactEdgeIterator) { - val contact = ce.contact as AbstractContact + val contact = ce.contact // Has this contact already been added to an island? if (contact.isOnIsland) @@ -304,7 +438,7 @@ class B2World(override var gravity: Vector2d) : IB2World { island.add(contact) contact.isOnIsland = true - val other = ce.other as Body + val other = ce.other // Was the other body already added to this island? if (!other.isOnIsland) { @@ -315,13 +449,13 @@ class B2World(override var gravity: Vector2d) : IB2World { // Search all joints connect to this body. 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" } if (joint.isOnIsland) continue - val other = je.otherNullable as Body? + val other = je.otherNullable // Don't simulate joints connected to disabled bodies. if (other != null && !other.isEnabled) @@ -357,8 +491,6 @@ class B2World(override var gravity: Vector2d) : IB2World { // Synchronize fixtures, check for out of range bodies. for (body in bodyListIterator) { - body as Body - // If a body was not in an island then it did not move. if (!body.isOnIsland || body.type == BodyType.STATIC) { continue @@ -381,14 +513,12 @@ class B2World(override var gravity: Vector2d) : IB2World { if (stepComplete) { for (body in bodyListIterator) { - body as Body body.isOnIsland = false body.sweep.alpha0 = 0.0 } for (c in contactManager.contactListIterator) { // Invalidate TOI - c as AbstractContact c.isOnIsland = false c.toiFlag = false c.toiCount = 0 @@ -406,13 +536,11 @@ class B2World(override var gravity: Vector2d) : IB2World { continue } - c as AbstractContact - if (c.toiCount > b2_maxSubSteps) { continue } - var alpha = 1.0 + var alpha: Double if (c.toiFlag) { // This contact has a valid cached TOI. @@ -550,7 +678,7 @@ class B2World(override var gravity: Vector2d) : IB2World { if (body.type == BodyType.DYNAMIC) { for (ce in body.contactEdgeIterator) { - val contact = ce.contact as AbstractContact + val contact = ce.contact // Has this contact already been added to the island? if (contact.isOnIsland) { @@ -569,8 +697,6 @@ class B2World(override var gravity: Vector2d) : IB2World { continue } - other as Body - // Tentatively advance the body to the TOI. val backup = other.sweep if (!other.isOnIsland) { @@ -639,7 +765,7 @@ class B2World(override var gravity: Vector2d) : IB2World { // Invalidate all contact TOIs on this displaced body. for (ce in body.contactEdgeIterator) { - val contact = ce.contact as AbstractContact + val contact = ce.contact contact.toiFlag = false contact.isOnIsland = false } @@ -658,8 +784,15 @@ class B2World(override var gravity: Vector2d) : IB2World { 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 (newContacts) { @@ -724,19 +857,41 @@ class B2World(override var gravity: Vector2d) : IB2World { 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) { - body as Body body.force = Vector2d.ZERO 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) } } - 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) 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) { IShape.Type.CIRCLE -> { 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 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) { if (isLocked) throw ConcurrentModificationException() @@ -891,7 +1038,6 @@ class B2World(override var gravity: Vector2d) : IB2World { try { for (body in bodyListIterator) { - body as Body body.transform.p -= newOrigin body.sweep.c0 -= 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() - 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") } + + 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) + } } diff --git a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/Body.kt b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/Body.kt index d83e7abe..0aaf3c35 100644 --- a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/Body.kt +++ b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/Body.kt @@ -3,10 +3,13 @@ package ru.dbotthepony.kbox2d.dynamics import ru.dbotthepony.kbox2d.api.* import ru.dbotthepony.kvector.vector.ndouble.Vector2d -open class Body(def: BodyDef, world: IB2World) : IBody { - private var _world: IB2World? = world +class Body(def: BodyDef, world: B2World) { + 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") internal var flags: Int = 0 @@ -43,21 +46,37 @@ open class Body(def: BodyDef, world: IB2World) : IBody { 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 - 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 - override val angle: Double + + /** + * Get the angle in radians. + * @return the current world rotation angle in radians. + */ + val angle: Double get() = sweep.a - override val userData: Any? = def.userData + val userData: Any? = def.userData internal var sleepTime: Double = 0.0 internal var torque: Double = 0.0 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) { if (type == BodyType.STATIC) return @@ -68,7 +87,10 @@ open class Body(def: BodyDef, world: IB2World) : IBody { field = value } - override var angularVelocity: Double = def.angularVelocity + /** + * The angular velocity of the body. + */ + var angularVelocity: Double = def.angularVelocity set(value) { if (type == BodyType.STATIC) return @@ -79,27 +101,95 @@ open class Body(def: BodyDef, world: IB2World) : IBody { field = value } - override var fixtureList: Fixture? = null + var fixtureList: Fixture? = null protected set - protected var fixtureCount: Int = 0 + var fixtureCount: Int = 0 + private set - override var jointList: JointEdge? = null - internal set - override var contactEdge: ContactEdge? = null + /** + * 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. + */ + var jointList: JointEdge? = null internal set - override var next: IBody? = null - internal set - override var prev: IBody? = null + /** + * 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. + */ + var contactEdge: ContactEdge? = null internal set - override var linearDamping: Double = def.linearDamping - override var angularDamping: Double = def.angularDamping - override var gravityScale: Double = def.gravityScale + val contactEdgeIterator: Iterator get() { + return object : Iterator { + private var node = contactEdge - override var mass: Double = 0.0 - protected set + override fun hasNext(): Boolean { + return node != null + } + + override fun next(): ContactEdge { + val old = node!! + node = old.next + return old + } + } + } + + val jointIterator: Iterator get() { + return object : Iterator { + 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 get() { + return object : Iterator { + 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 @@ -116,11 +206,19 @@ open class Body(def: BodyDef, world: IB2World) : IBody { internal var I by this::rotInertia 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) 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) set(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) set(value) { 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) set(value) { if (value == isFixedRotation) @@ -190,7 +311,11 @@ open class Body(def: BodyDef, world: IB2World) : IBody { 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) set(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) { if (world.isLocked) throw ConcurrentModificationException() @@ -234,7 +362,7 @@ open class Body(def: BodyDef, world: IB2World) : IBody { contactEdge = null val broadPhase = world.contactManager.broadPhase - var f: IFixture? = fixtureList + var f: Fixture? = fixtureList while (f != null) { 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) throw ConcurrentModificationException() @@ -271,21 +407,48 @@ open class Body(def: BodyDef, world: IB2World) : IBody { 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) throw ConcurrentModificationException() 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" } - var node: IFixture? = fixtureList + var node: Fixture? = fixtureList var found = false - var previous: IFixture? = null + var previous: Fixture? = null while (node != null) { if (node == fixture) { // TODO: Это должно работать - (previous as Fixture?)?.next = node.next + previous?.next = node.next found = true break } 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. mass = 0.0 invMass = 0.0 @@ -387,7 +555,16 @@ open class Body(def: BodyDef, world: IB2World) : IBody { 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( mass = mass, inertia = inertia, @@ -428,7 +605,7 @@ open class Body(def: BodyDef, world: IB2World) : IBody { 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. if (type != BodyType.DYNAMIC && other.type != BodyType.DYNAMIC) return false @@ -454,7 +631,14 @@ open class Body(def: BodyDef, world: IB2World) : IBody { 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) throw ConcurrentModificationException() @@ -477,40 +661,88 @@ open class Body(def: BodyDef, world: IB2World) : IBody { 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) - override val localCenter: Vector2d + /** + * Get the local position of the center of mass. + */ + val localCenter: Vector2d get() = sweep.localCenter - override val worldCenter: Vector2d + /** + * Get the world position of the center of mass. + */ + val worldCenter: Vector2d 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) } - 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) } - 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) } - 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) } - 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) } - 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)) } - 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) 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) 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) 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) 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) 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) return @@ -616,7 +877,7 @@ open class Body(def: BodyDef, world: IB2World) : IBody { } } - override fun dump() { + fun dump() { TODO("Not yet implemented") } } diff --git a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/ContactManager.kt b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/ContactManager.kt index 9476f018..8c7970c1 100644 --- a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/ContactManager.kt +++ b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/ContactManager.kt @@ -4,18 +4,35 @@ import ru.dbotthepony.kbox2d.api.* import ru.dbotthepony.kbox2d.collision.BroadPhase import ru.dbotthepony.kbox2d.dynamics.contact.AbstractContact -class ContactManager : IContactManager { - override val broadPhase: IBroadPhase = BroadPhase() - override var contactList: IContact? = null - override var contactCount: Int = 0 +/** + * Delegate of b2World. + */ +class ContactManager { + val broadPhase = BroadPhase() + var contactList: AbstractContact? = null + var contactCount: Int = 0 private set - override var contactFilter: IContactFilter? = null - override var contactListener: IContactListener? = null + var contactFilter: IContactFilter? = null + var contactListener: IContactListener? = null - override fun destroy(contact: IContact) { - contact as AbstractContact + val contactListIterator: Iterator get() { + return object : Iterator { + 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 fixtureB = contact.fixtureB val bodyA = fixtureA.body as Body @@ -72,7 +89,7 @@ class ContactManager : IContactManager { * all the narrow phase collision is processed for the world * contact list. */ - override fun collide() { + fun collide() { // Update awake contacts. for (c in contactListIterator) { @@ -123,11 +140,11 @@ class ContactManager : IContactManager { } } - override fun findNewContacts() { + fun findNewContacts() { broadPhase.updatePairs(this::addPair) } - override fun addPair(proxyUserDataA: Any?, proxyUserDataB: Any?) { + fun addPair(proxyUserDataA: Any?, proxyUserDataB: Any?) { val proxyA = proxyUserDataA as FixtureProxy val proxyB = proxyUserDataB as FixtureProxy diff --git a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/Fixture.kt b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/Fixture.kt index 44896678..47d476b1 100644 --- a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/Fixture.kt +++ b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/Fixture.kt @@ -1,28 +1,77 @@ package ru.dbotthepony.kbox2d.dynamics import ru.dbotthepony.kbox2d.api.* +import ru.dbotthepony.kbox2d.collision.BroadPhase import ru.dbotthepony.kbox2d.collision.shapes.ChainShape import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape +import ru.dbotthepony.kvector.util2d.AABB +import ru.dbotthepony.kvector.vector.ndouble.Vector2d import java.util.* 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 -) : 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 - override var restitution: Double = def.restitution - override var restitutionThreshold: Double = def.restitutionThreshold + /** + * Get the user data that was assigned in the fixture definition. Use this to + * store your application specific data. + */ + 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) { if (field != value) { 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 { if (shape is PolygonShape) { @@ -42,10 +96,15 @@ open class Fixture( } } - protected val internalProxies = ArrayList(shape.childCount) - final override val proxies: List = Collections.unmodifiableList(internalProxies) + private val internalProxies = ArrayList(shape.childCount) + val proxies: List = 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) { require(value.isFinite()) { "Infinite density" } require(!value.isNaN()) { "NaN density" } @@ -53,7 +112,12 @@ open class Fixture( 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) { if (value is ImmutableFilter) field = value @@ -76,7 +140,7 @@ open class Fixture( /** * 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(internalProxies.isEmpty()) { "Already having proxies" } @@ -97,7 +161,7 @@ open class Fixture( /** * These support body activation/deactivation. */ - internal fun destroyProxies(broadPhase: IBroadPhase) { + internal fun destroyProxies(broadPhase: BroadPhase) { check(body != null) { "Already destroyed" } // Destroy proxies in the broad-phase. @@ -108,7 +172,7 @@ open class Fixture( 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" } 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 check(body != null) { "Already destroyed" } var edge = body.contactEdge @@ -144,4 +211,45 @@ open class Fixture( 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) { } } diff --git a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/contact/AbstractContact.kt b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/contact/AbstractContact.kt index ac4c7382..08208904 100644 --- a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/contact/AbstractContact.kt +++ b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/contact/AbstractContact.kt @@ -4,30 +4,61 @@ import ru.dbotthepony.kbox2d.api.* import ru.dbotthepony.kbox2d.collision.WorldManifold import ru.dbotthepony.kbox2d.collision.b2TestOverlap import ru.dbotthepony.kbox2d.dynamics.Body +import ru.dbotthepony.kbox2d.dynamics.Fixture import java.util.* import kotlin.collections.HashMap 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( - final override val fixtureA: IFixture, - final override val childIndexA: Int, - final override val fixtureB: IFixture, - final override val childIndexB: Int, -) : IContact { + /** + * Get fixture A in this contact. + */ + val fixtureA: Fixture, + + /** + * 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 isOnIsland = 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 - override var next: IContact? = null + /** + * Get the next contact in the world's contact list. + */ + var next: AbstractContact? = null internal set - override var prev: IContact? = null + /** + * Get the previous contact in the world's contact list. + */ + var prev: AbstractContact? = null internal set internal var isFlaggedForFiltering: Boolean @@ -51,15 +82,44 @@ sealed class AbstractContact( bodyB.contactEdge = nodeB } - override var friction: Double = b2MixFriction(fixtureA.friction, fixtureB.friction) - override var restitution: Double = b2MixRestitution(fixtureA.restitution, fixtureB.restitution) - override var restitutionThreshold: Double = b2MixRestitutionThreshold(fixtureA.restitutionThreshold, fixtureB.restitutionThreshold) - override var tangentSpeed: Double = 0.0 + /** + * 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 = 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 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 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) } - override var isTouching: Boolean + /** + * Is this contact touching? + */ + var isTouching: Boolean get() = (flags and ContactFlags.TOUCHING.bitmask) == ContactFlags.TOUCHING.bitmask 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 set(value) { flags = ContactFlags.ENABLED.update(flags, value) } - override fun flagForFiltering() { + fun flagForFiltering() { 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 { private val registry = EnumMap>(IShape.Type::class.java) @@ -168,9 +257,9 @@ sealed class AbstractContact( } internal fun create( - fixtureA: IFixture, + fixtureA: Fixture, indexA: Int, - fixtureB: IFixture, + fixtureB: Fixture, indexB: Int, ): AbstractContact { val type1 = fixtureA.type @@ -182,31 +271,31 @@ sealed class AbstractContact( } 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) } - 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) } - 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) } - 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) } - 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) } - 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) } - 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) } } diff --git a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/contact/ChainCircleContact.kt b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/contact/ChainCircleContact.kt index c20c20c8..4423c369 100644 --- a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/contact/ChainCircleContact.kt +++ b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/contact/ChainCircleContact.kt @@ -1,17 +1,17 @@ package ru.dbotthepony.kbox2d.dynamics.contact -import ru.dbotthepony.kbox2d.api.IFixture import ru.dbotthepony.kbox2d.api.IShape import ru.dbotthepony.kbox2d.api.Manifold import ru.dbotthepony.kbox2d.api.Transform import ru.dbotthepony.kbox2d.collision.handler.b2CollideEdgeAndCircle import ru.dbotthepony.kbox2d.collision.shapes.ChainShape import ru.dbotthepony.kbox2d.collision.shapes.CircleShape +import ru.dbotthepony.kbox2d.dynamics.Fixture class ChainCircleContact( - fixtureA: IFixture, + fixtureA: Fixture, childIndexA: Int, - fixtureB: IFixture, + fixtureB: Fixture, childIndexB: Int, ) : AbstractContact(fixtureA, childIndexA, fixtureB, childIndexB) { init { diff --git a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/contact/ChainPolygonContact.kt b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/contact/ChainPolygonContact.kt index 0c27a4a4..f13a6e91 100644 --- a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/contact/ChainPolygonContact.kt +++ b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/contact/ChainPolygonContact.kt @@ -1,17 +1,17 @@ package ru.dbotthepony.kbox2d.dynamics.contact -import ru.dbotthepony.kbox2d.api.IFixture import ru.dbotthepony.kbox2d.api.IShape import ru.dbotthepony.kbox2d.api.Manifold import ru.dbotthepony.kbox2d.api.Transform import ru.dbotthepony.kbox2d.collision.handler.b2CollideEdgeAndPolygon import ru.dbotthepony.kbox2d.collision.shapes.ChainShape import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape +import ru.dbotthepony.kbox2d.dynamics.Fixture class ChainPolygonContact( - fixtureA: IFixture, + fixtureA: Fixture, childIndexA: Int, - fixtureB: IFixture, + fixtureB: Fixture, childIndexB: Int, ) : AbstractContact(fixtureA, childIndexA, fixtureB, childIndexB) { init { diff --git a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/contact/CircleContact.kt b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/contact/CircleContact.kt index 1e7b5fb9..b859be7f 100644 --- a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/contact/CircleContact.kt +++ b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/contact/CircleContact.kt @@ -1,15 +1,15 @@ package ru.dbotthepony.kbox2d.dynamics.contact -import ru.dbotthepony.kbox2d.api.IFixture import ru.dbotthepony.kbox2d.api.IShape import ru.dbotthepony.kbox2d.api.Manifold import ru.dbotthepony.kbox2d.api.Transform import ru.dbotthepony.kbox2d.collision.handler.b2CollideCircles import ru.dbotthepony.kbox2d.collision.shapes.CircleShape +import ru.dbotthepony.kbox2d.dynamics.Fixture class CircleContact( - fixtureA: IFixture, - fixtureB: IFixture, + fixtureA: Fixture, + fixtureB: Fixture, ) : AbstractContact(fixtureA, 0, fixtureB, 0) { init { require(fixtureA.type == IShape.Type.CIRCLE) { "Fixture A is of type ${fixtureA.type}" } diff --git a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/contact/EdgeCircleContact.kt b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/contact/EdgeCircleContact.kt index a64164d2..6b8f4b79 100644 --- a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/contact/EdgeCircleContact.kt +++ b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/contact/EdgeCircleContact.kt @@ -1,16 +1,16 @@ package ru.dbotthepony.kbox2d.dynamics.contact -import ru.dbotthepony.kbox2d.api.IFixture import ru.dbotthepony.kbox2d.api.IShape import ru.dbotthepony.kbox2d.api.Manifold import ru.dbotthepony.kbox2d.api.Transform import ru.dbotthepony.kbox2d.collision.handler.b2CollideEdgeAndCircle import ru.dbotthepony.kbox2d.collision.shapes.CircleShape import ru.dbotthepony.kbox2d.collision.shapes.EdgeShape +import ru.dbotthepony.kbox2d.dynamics.Fixture class EdgeCircleContact( - fixtureA: IFixture, - fixtureB: IFixture, + fixtureA: Fixture, + fixtureB: Fixture, ) : AbstractContact(fixtureA, 0, fixtureB, 0) { init { require(fixtureA.type == IShape.Type.EDGE) { "Fixture A is of type ${fixtureA.type}, expected EDGE" } diff --git a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/contact/EdgePolygonContact.kt b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/contact/EdgePolygonContact.kt index 1dc03ecd..4dbc4803 100644 --- a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/contact/EdgePolygonContact.kt +++ b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/contact/EdgePolygonContact.kt @@ -1,16 +1,16 @@ package ru.dbotthepony.kbox2d.dynamics.contact -import ru.dbotthepony.kbox2d.api.IFixture import ru.dbotthepony.kbox2d.api.IShape import ru.dbotthepony.kbox2d.api.Manifold import ru.dbotthepony.kbox2d.api.Transform import ru.dbotthepony.kbox2d.collision.handler.b2CollideEdgeAndPolygon import ru.dbotthepony.kbox2d.collision.shapes.EdgeShape import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape +import ru.dbotthepony.kbox2d.dynamics.Fixture class EdgePolygonContact( - fixtureA: IFixture, - fixtureB: IFixture, + fixtureA: Fixture, + fixtureB: Fixture, ) : AbstractContact(fixtureA, 0, fixtureB, 0) { init { require(fixtureA.type == IShape.Type.EDGE) { "Fixture A is of type ${fixtureA.type}, expected EDGE" } diff --git a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/contact/PolygonCircleContact.kt b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/contact/PolygonCircleContact.kt index 41598f90..7eae5684 100644 --- a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/contact/PolygonCircleContact.kt +++ b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/contact/PolygonCircleContact.kt @@ -1,16 +1,16 @@ package ru.dbotthepony.kbox2d.dynamics.contact -import ru.dbotthepony.kbox2d.api.IFixture import ru.dbotthepony.kbox2d.api.IShape import ru.dbotthepony.kbox2d.api.Manifold import ru.dbotthepony.kbox2d.api.Transform import ru.dbotthepony.kbox2d.collision.handler.b2CollidePolygonAndCircle import ru.dbotthepony.kbox2d.collision.shapes.CircleShape import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape +import ru.dbotthepony.kbox2d.dynamics.Fixture class PolygonCircleContact( - fixtureA: IFixture, - fixtureB: IFixture, + fixtureA: Fixture, + fixtureB: Fixture, ) : AbstractContact(fixtureA, 0, fixtureB, 0) { init { require(fixtureA.type == IShape.Type.POLYGON) { "Fixture A is of type ${fixtureA.type}, expected POLYGON" } diff --git a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/contact/PolygonContact.kt b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/contact/PolygonContact.kt index 4636b0b0..12d46eaa 100644 --- a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/contact/PolygonContact.kt +++ b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/contact/PolygonContact.kt @@ -1,15 +1,15 @@ package ru.dbotthepony.kbox2d.dynamics.contact -import ru.dbotthepony.kbox2d.api.IFixture import ru.dbotthepony.kbox2d.api.IShape import ru.dbotthepony.kbox2d.api.Manifold import ru.dbotthepony.kbox2d.api.Transform import ru.dbotthepony.kbox2d.collision.handler.b2CollidePolygons import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape +import ru.dbotthepony.kbox2d.dynamics.Fixture class PolygonContact( - fixtureA: IFixture, - fixtureB: IFixture, + fixtureA: Fixture, + fixtureB: Fixture, ) : AbstractContact(fixtureA, 0, fixtureB, 0) { init { require(fixtureA.type == IShape.Type.POLYGON) { "Fixture A has type of ${fixtureA.type}" } diff --git a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/joint/AbstractJoint.kt b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/joint/AbstractJoint.kt index 2fe27f92..2e899a86 100644 --- a/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/joint/AbstractJoint.kt +++ b/src/kbox2d/kotlin/ru/dbotthepony/kbox2d/dynamics/joint/AbstractJoint.kt @@ -9,7 +9,7 @@ fun interface JointFactory { fun factorize(jointDef: IJointDef): AbstractJoint } -sealed class AbstractJoint(def: IJointDef) : IJoint { +sealed class AbstractJoint(def: IJointDef) : IMovable { init { 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 - override val collideConnected: Boolean = def.collideConnected - override val type: JointType = def.type - override var userData: Any? = def.userData + /** + * 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 = 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 // 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 _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 hasBodyB: Boolean get() = _bodyB != null @@ -58,11 +104,26 @@ sealed class AbstractJoint(def: IJointDef) : IJoint { _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 - final override var prev: IJoint? = null + var prev: AbstractJoint? = null 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 * to fail-fast this object diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt index c6779cb9..c4e42667 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt @@ -8,6 +8,7 @@ import ru.dbotthepony.kbox2d.collision.shapes.ChainShape import ru.dbotthepony.kbox2d.collision.shapes.CircleShape import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape import ru.dbotthepony.kbox2d.dynamics.B2World +import ru.dbotthepony.kbox2d.dynamics.Body import ru.dbotthepony.kbox2d.dynamics.joint.MouseJoint import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kstarbound.world.Chunk @@ -53,7 +54,7 @@ fun main() { ground.createFixture(groundPoly, 0.0) - val boxes = ArrayList() + val boxes = ArrayList() /*run { val movingDef = BodyDef(