Box2d integration test
This commit is contained in:
parent
c82c89dfec
commit
4a02a0e0de
@ -2,10 +2,12 @@ package ru.dbotthepony.kbox2d.api
|
||||
|
||||
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
|
||||
|
||||
/// The body type.
|
||||
/// static: zero mass, zero velocity, may be manually moved
|
||||
/// kinematic: zero mass, non-zero velocity set by user, moved by solver
|
||||
/// dynamic: positive mass, non-zero velocity determined by forces, moved by solver
|
||||
/**
|
||||
* The body type.
|
||||
* - static: zero mass, zero velocity, may be manually moved
|
||||
* - kinematic: zero mass, non-zero velocity set by user, moved by solver
|
||||
* - dynamic: positive mass, non-zero velocity determined by forces, moved by solver
|
||||
*/
|
||||
enum class BodyType {
|
||||
STATIC,
|
||||
KINEMATIC,
|
||||
|
@ -58,12 +58,19 @@ interface IShape<S : IShape<S>> {
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
* **KBox2D extension.**
|
||||
*
|
||||
* Computes full AABB in local coordinate space with zero rotation.
|
||||
* @param childIndex the child shape
|
||||
*/
|
||||
fun computeAABB(childIndex: Int): AABB
|
||||
|
||||
/**
|
||||
* Compute the mass properties of this shape using its dimensions and density.
|
||||
* The inertia tensor is computed about the local origin.
|
||||
|
@ -172,6 +172,27 @@ class ChainShape : IShape<ChainShape> {
|
||||
)
|
||||
}
|
||||
|
||||
override fun computeAABB(childIndex: Int): AABB {
|
||||
val i1 = childIndex
|
||||
var i2 = childIndex + 1
|
||||
|
||||
if (i2 == vertices.size) {
|
||||
i2 = 0
|
||||
}
|
||||
|
||||
val v1 = vertices[i1]
|
||||
val v2 = vertices[i2]
|
||||
val lower = b2Min(v1, v2)
|
||||
val upper = b2Max(v1, v2)
|
||||
|
||||
val r = Vector2d(radius, radius)
|
||||
|
||||
return AABB(
|
||||
mins = lower - r,
|
||||
maxs = upper + r
|
||||
)
|
||||
}
|
||||
|
||||
override fun computeMass(density: Double): MassData {
|
||||
return MASS
|
||||
}
|
||||
|
@ -67,6 +67,13 @@ class CircleShape(
|
||||
)
|
||||
}
|
||||
|
||||
override fun computeAABB(childIndex: Int): AABB {
|
||||
return AABB(
|
||||
mins = Vector2d(p.x - radius, p.y - radius),
|
||||
maxs = Vector2d(p.x + radius, p.y + radius)
|
||||
)
|
||||
}
|
||||
|
||||
override fun computeMass(density: Double): MassData {
|
||||
val mass = density * PI * radius * radius
|
||||
|
||||
|
@ -105,6 +105,21 @@ class EdgeShape : IShape<EdgeShape> {
|
||||
)
|
||||
}
|
||||
|
||||
override fun computeAABB(childIndex: Int): AABB {
|
||||
val v1 = vertex1
|
||||
val v2 = vertex2
|
||||
|
||||
val lower = b2Min(v1, v2)
|
||||
val upper = b2Max(v1, v2)
|
||||
|
||||
val r = Vector2d(radius, radius)
|
||||
|
||||
return AABB(
|
||||
mins = lower - r,
|
||||
maxs = upper + r
|
||||
)
|
||||
}
|
||||
|
||||
override fun computeMass(density: Double): MassData {
|
||||
return MassData(
|
||||
center = 0.5 * (vertex1 + vertex2),
|
||||
|
@ -306,6 +306,24 @@ class PolygonShape : IShape<PolygonShape> {
|
||||
)
|
||||
}
|
||||
|
||||
override fun computeAABB(childIndex: Int): AABB {
|
||||
var lower = vertices[0]
|
||||
var upper = lower
|
||||
|
||||
for (i in 1 until vertices.size) {
|
||||
val v = vertices[i]
|
||||
lower = b2Min(lower, v)
|
||||
upper = b2Max(upper, v)
|
||||
}
|
||||
|
||||
val r = Vector2d(radius, radius)
|
||||
|
||||
return AABB(
|
||||
mins = lower - r,
|
||||
maxs = upper + r
|
||||
)
|
||||
}
|
||||
|
||||
override fun computeMass(density: Double): MassData {
|
||||
// Polygon mass, centroid, and inertia.
|
||||
// Let rho be the polygon density in mass per unit area.
|
||||
|
@ -4,6 +4,7 @@
|
||||
package ru.dbotthepony.kbox2d.dynamics
|
||||
|
||||
import ru.dbotthepony.kbox2d.api.*
|
||||
import ru.dbotthepony.kvector.util2d.AABB
|
||||
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
|
||||
|
||||
class B2Body(def: BodyDef, world: B2World) {
|
||||
@ -395,6 +396,7 @@ class B2Body(def: BodyDef, world: B2World) {
|
||||
}
|
||||
|
||||
fixture.next = fixtureList
|
||||
fixtureList?.prev = fixture
|
||||
fixtureList = fixture
|
||||
fixtureCount++
|
||||
|
||||
@ -444,25 +446,17 @@ class B2Body(def: BodyDef, world: B2World) {
|
||||
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: B2Fixture? = fixtureList
|
||||
var found = false
|
||||
var previous: B2Fixture? = null
|
||||
val prev = fixture.prev
|
||||
val next = fixture.next
|
||||
|
||||
while (node != null) {
|
||||
if (node == fixture) {
|
||||
// TODO: Это должно работать
|
||||
previous?.next = node.next
|
||||
found = true
|
||||
break
|
||||
} else {
|
||||
previous = node
|
||||
node = node.next
|
||||
}
|
||||
prev?.next = next
|
||||
next?.prev = prev
|
||||
|
||||
if (fixture == fixtureList) {
|
||||
fixtureList = next ?: prev
|
||||
}
|
||||
|
||||
check(found) { "Can't find $fixture in linked list of fixtures" }
|
||||
|
||||
val density = node!!.density
|
||||
val density = fixture.density
|
||||
|
||||
// Destroy any contacts associated with the fixture.
|
||||
var edge = contactEdge
|
||||
@ -556,6 +550,42 @@ class B2Body(def: BodyDef, world: B2World) {
|
||||
linearVelocity += b2Cross(angularVelocity, sweep.c - oldCenter)
|
||||
}
|
||||
|
||||
/**
|
||||
* **KBox2D extension.**
|
||||
*
|
||||
* Computes full local space AABB of all fixtures attached, with zero rotation.
|
||||
* If no fixtures are attached, returns zero sized AABB.
|
||||
*/
|
||||
val localSpaceAABB: AABB get() {
|
||||
var combined = AABB.ZERO
|
||||
|
||||
for (fixture in fixtureIterator) {
|
||||
for (i in 0 until fixture.shape.childCount) {
|
||||
combined = combined.combine(fixture.shape.computeAABB(i))
|
||||
}
|
||||
}
|
||||
|
||||
return combined
|
||||
}
|
||||
|
||||
/**
|
||||
* **KBox2D extension.**
|
||||
*
|
||||
* Computes full world space AABB of all fixtures attached, with zero rotation.
|
||||
* If no fixtures are attached, returns zero sized AABB positioned at [position].
|
||||
*/
|
||||
val worldSpaceAABB: AABB get() {
|
||||
var combined = AABB(position, position)
|
||||
|
||||
for (fixture in fixtureIterator) {
|
||||
for (i in 0 until fixture.shape.childCount) {
|
||||
combined = combined.combine(fixture.shape.computeAABB(xf, i))
|
||||
}
|
||||
}
|
||||
|
||||
return combined
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mass data of the body.
|
||||
* @return a struct containing the mass, inertia and center of the body.
|
||||
@ -743,7 +773,7 @@ class B2Body(def: BodyDef, world: B2World) {
|
||||
* @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) {
|
||||
fun applyForce(force: Vector2d, point: Vector2d, wake: Boolean = true) {
|
||||
if (type != BodyType.DYNAMIC)
|
||||
return
|
||||
|
||||
@ -762,7 +792,7 @@ class B2Body(def: BodyDef, world: B2World) {
|
||||
* @param force the world force vector, usually in Newtons (N).
|
||||
* @param wake also wake up the body
|
||||
*/
|
||||
fun applyForceToCenter(force: Vector2d, wake: Boolean) {
|
||||
fun applyForceToCenter(force: Vector2d, wake: Boolean = true) {
|
||||
if (type != BodyType.DYNAMIC)
|
||||
return
|
||||
|
||||
@ -781,7 +811,7 @@ class B2Body(def: BodyDef, world: B2World) {
|
||||
* @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) {
|
||||
fun applyTorque(torque: Double, wake: Boolean = true) {
|
||||
if (type != BodyType.DYNAMIC)
|
||||
return
|
||||
|
||||
@ -801,7 +831,7 @@ class B2Body(def: BodyDef, world: B2World) {
|
||||
* @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) {
|
||||
fun applyLinearImpulse(impulse: Vector2d, point: Vector2d, wake: Boolean = true) {
|
||||
if (type != BodyType.DYNAMIC)
|
||||
return
|
||||
|
||||
@ -819,7 +849,7 @@ class B2Body(def: BodyDef, world: B2World) {
|
||||
* @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) {
|
||||
fun applyLinearImpulseToCenter(impulse: Vector2d, wake: Boolean = true) {
|
||||
if (type != BodyType.DYNAMIC)
|
||||
return
|
||||
|
||||
@ -836,7 +866,7 @@ class B2Body(def: BodyDef, world: B2World) {
|
||||
* @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) {
|
||||
fun applyAngularImpulse(impulse: Double, wake: Boolean = true) {
|
||||
if (type != BodyType.DYNAMIC)
|
||||
return
|
||||
|
||||
|
@ -31,6 +31,14 @@ class B2Fixture(
|
||||
* @return the next shape.
|
||||
*/
|
||||
var next: B2Fixture? = null
|
||||
internal set
|
||||
|
||||
/**
|
||||
* Get the previous fixture in the parent body's fixture list.
|
||||
* @return the next shape.
|
||||
*/
|
||||
var prev: B2Fixture? = null
|
||||
internal set
|
||||
|
||||
/**
|
||||
* Get the user data that was assigned in the fixture definition. Use this to
|
||||
@ -126,6 +134,11 @@ class B2Fixture(
|
||||
refilter()
|
||||
}
|
||||
|
||||
/**
|
||||
* **KBox2D extension**.
|
||||
*
|
||||
* Convenience function for quickly destroying this fixture.
|
||||
*/
|
||||
fun destroy() {
|
||||
checkNotNull(body) { "Already destroyed" }.destroyFixture(this)
|
||||
}
|
||||
|
@ -191,7 +191,7 @@ internal class Island(
|
||||
body.sweep.c0 = body.sweep.c
|
||||
body.sweep.a0 = body.sweep.a
|
||||
|
||||
if (body.type == ru.dbotthepony.kbox2d.api.BodyType.DYNAMIC) {
|
||||
if (body.type == BodyType.DYNAMIC) {
|
||||
// Integrate velocities.
|
||||
v += (gravity * body.gravityScale * body.mass + body.force) * body.invMass * h
|
||||
w += h * body.rotInertiaInv * body.torque
|
||||
|
@ -368,6 +368,8 @@ data class AABB(val mins: Vector2d, val maxs: Vector2d) {
|
||||
Vector2d(x + width / 2.0, y + height / 2.0),
|
||||
)
|
||||
}
|
||||
|
||||
@JvmField val ZERO = AABB(Vector2d.ZERO, Vector2d.ZERO)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,20 +4,19 @@ import org.apache.logging.log4j.LogManager
|
||||
import org.lwjgl.Version
|
||||
import org.lwjgl.glfw.GLFW.glfwSetWindowShouldClose
|
||||
import ru.dbotthepony.kbox2d.api.*
|
||||
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.B2Body
|
||||
import ru.dbotthepony.kbox2d.dynamics.joint.MouseJoint
|
||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||
import ru.dbotthepony.kstarbound.defs.TileDefinition
|
||||
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_FF
|
||||
import ru.dbotthepony.kstarbound.world.Chunk
|
||||
import ru.dbotthepony.kstarbound.world.ChunkPos
|
||||
import ru.dbotthepony.kstarbound.world.entities.Move
|
||||
import ru.dbotthepony.kstarbound.world.entities.PlayerEntity
|
||||
import ru.dbotthepony.kstarbound.world.entities.Projectile
|
||||
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
|
||||
@ -32,380 +31,14 @@ fun main() {
|
||||
//return
|
||||
}
|
||||
|
||||
val world = B2World(Vector2d(y = -10.0))
|
||||
|
||||
val groundDef = BodyDef(
|
||||
position = Vector2d(y = 0.0)
|
||||
)
|
||||
|
||||
val ground = world.createBody(groundDef)
|
||||
|
||||
//val groundPoly = PolygonShape()
|
||||
//groundPoly.setAsBox(50.0, 10.0)
|
||||
|
||||
val groundPoly = ChainShape()
|
||||
groundPoly.createLoop(listOf(
|
||||
Vector2d(-30.0, 10.0),
|
||||
Vector2d(-25.0, 0.0),
|
||||
Vector2d(25.0, 0.0),
|
||||
Vector2d(30.0, 10.0),
|
||||
Vector2d(30.0, -2.0),
|
||||
Vector2d(-30.0, -2.0)).asReversed())
|
||||
|
||||
ground.createFixture(groundPoly, 0.0)
|
||||
|
||||
val boxes = ArrayList<B2Body>()
|
||||
|
||||
/*run {
|
||||
val movingDef = BodyDef(
|
||||
type = BodyType.DYNAMIC,
|
||||
position = Vector2d(y = 4.0),
|
||||
angle = PI / 4.0
|
||||
)
|
||||
val movingBody = world.createBody(movingDef)
|
||||
|
||||
val dynamicBox = PolygonShape()
|
||||
dynamicBox.setAsBox(1.0, 1.0)
|
||||
|
||||
val fixtureDef = FixtureDef(
|
||||
shape = dynamicBox,
|
||||
density = 1.0,
|
||||
friction = 0.3
|
||||
)
|
||||
|
||||
movingBody.createFixture(fixtureDef)
|
||||
boxes.add(movingBody)
|
||||
}*/
|
||||
|
||||
run {
|
||||
if (true)
|
||||
return@run
|
||||
val movingDef = BodyDef(
|
||||
type = BodyType.DYNAMIC,
|
||||
position = Vector2d(x = -1.0, y = 6.0),
|
||||
)
|
||||
|
||||
val movingBody = world.createBody(movingDef)
|
||||
|
||||
val dynamicBox = PolygonShape()
|
||||
dynamicBox.setAsBox(1.0, 1.0)
|
||||
|
||||
val fixtureDef = FixtureDef(
|
||||
shape = dynamicBox,
|
||||
density = 1.0,
|
||||
friction = 0.3
|
||||
)
|
||||
|
||||
movingBody.createFixture(fixtureDef)
|
||||
boxes.add(movingBody)
|
||||
}
|
||||
|
||||
val rand = Random()
|
||||
|
||||
val mouseJoints = ArrayList<MouseJoint>()
|
||||
|
||||
run {
|
||||
val stripes = 4
|
||||
|
||||
for (stripe in 0 until stripes) {
|
||||
for (x in 0 .. (stripes - stripe)) {
|
||||
val movingBody = world.createBody(BodyDef(
|
||||
type = BodyType.DYNAMIC,
|
||||
position = Vector2d(x = (-stripes + stripe) * 1.0 + x * 2.1, y = 8.0 + stripe * 2.1),
|
||||
gravityScale = 1.1
|
||||
))
|
||||
|
||||
val dynamicBox: IShape<*>
|
||||
|
||||
if (false) {
|
||||
dynamicBox = PolygonShape()
|
||||
dynamicBox.setAsBox(1.0, 1.0)
|
||||
} else {
|
||||
dynamicBox = CircleShape(1.0)
|
||||
}
|
||||
|
||||
movingBody.createFixture(FixtureDef(
|
||||
shape = dynamicBox,
|
||||
density = 1.0,
|
||||
friction = 0.3
|
||||
))
|
||||
|
||||
/*for (otherBody in boxes) {
|
||||
val def = DistanceJointDef(movingBody, otherBody, movingBody.position, otherBody.position)
|
||||
world.createJoint(def)
|
||||
}*/
|
||||
|
||||
/*val mouse = world.createJoint(MouseJointDef(
|
||||
Vector2d(y = 10.0, x = 10.0),
|
||||
bodyB = movingBody,
|
||||
maxForce = 1000.0
|
||||
).linearStiffness(50.0, 0.7, bodyB = movingBody, bodyA = null))
|
||||
|
||||
mouseJoints.add(mouse as MouseJoint)*/
|
||||
|
||||
boxes.add(movingBody)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
run {
|
||||
if (true)
|
||||
return@run
|
||||
val movingDef1 = BodyDef(
|
||||
type = BodyType.DYNAMIC,
|
||||
position = Vector2d(x = -4.0, y = 4.0),
|
||||
gravityScale = 1.1
|
||||
)
|
||||
|
||||
val movingBody1 = world.createBody(movingDef1)
|
||||
|
||||
val dynamicBox = PolygonShape()
|
||||
dynamicBox.setAsBox(1.0, 1.0)
|
||||
|
||||
val fixtureDef1 = FixtureDef(
|
||||
shape = dynamicBox,
|
||||
density = 1.0,
|
||||
friction = 0.3
|
||||
)
|
||||
|
||||
movingBody1.createFixture(fixtureDef1)
|
||||
|
||||
boxes.add(movingBody1)
|
||||
|
||||
val movingDef2 = BodyDef(
|
||||
type = BodyType.DYNAMIC,
|
||||
position = Vector2d(x = 4.0, y = 6.0),
|
||||
gravityScale = 1.1
|
||||
)
|
||||
|
||||
val movingBody2 = world.createBody(movingDef2)
|
||||
|
||||
val fixtureDef2 = FixtureDef(
|
||||
shape = dynamicBox,
|
||||
density = 1.0,
|
||||
friction = 0.3
|
||||
)
|
||||
|
||||
movingBody2.createFixture(fixtureDef2)
|
||||
boxes.add(movingBody2)
|
||||
|
||||
val groundAnchor1 = movingBody1.position + Vector2d(y = 10.0)
|
||||
val groundAnchor2 = movingBody2.position + Vector2d(y = 12.0)
|
||||
val def = PulleyJointDef(movingBody1, movingBody2, groundAnchor1, groundAnchor2, movingBody1.position, movingBody2.position, 1.0)
|
||||
|
||||
world.createJoint(def)
|
||||
}
|
||||
|
||||
run {
|
||||
val base = world.createBody(BodyDef(
|
||||
type = BodyType.DYNAMIC,
|
||||
position = Vector2d(x = 0.0, y = 16.0),
|
||||
))
|
||||
|
||||
val wheel1 = world.createBody(BodyDef(
|
||||
type = BodyType.DYNAMIC,
|
||||
position = Vector2d(x = -2.0, y = 15.0),
|
||||
))
|
||||
|
||||
val wheel2 = world.createBody(BodyDef(
|
||||
type = BodyType.DYNAMIC,
|
||||
position = Vector2d(x = 2.0, y = 15.0),
|
||||
))
|
||||
|
||||
base.createFixture(FixtureDef(
|
||||
shape = PolygonShape().also { it.setAsBox(2.0, 0.5) },
|
||||
density = 1.0
|
||||
))
|
||||
|
||||
wheel1.createFixture(FixtureDef(
|
||||
shape = CircleShape(0.5),
|
||||
density = 1.0,
|
||||
friction = 1.5
|
||||
))
|
||||
|
||||
wheel2.createFixture(FixtureDef(
|
||||
shape = CircleShape(0.5),
|
||||
density = 1.0,
|
||||
friction = 1.5
|
||||
))
|
||||
|
||||
world.createJoint(WheelJointDef(
|
||||
bodyA = base,
|
||||
bodyB = wheel1,
|
||||
anchor = Vector2d(x = -2.0, y = 15.0),
|
||||
axis = Vector2d.POSITIVE_Y,
|
||||
enableLimit = true,
|
||||
upperTranslation = 0.25,
|
||||
lowerTranslation = -0.25,
|
||||
))
|
||||
|
||||
world.createJoint(WheelJointDef(
|
||||
bodyA = base,
|
||||
bodyB = wheel2,
|
||||
anchor = Vector2d(x = 2.0, y = 15.0),
|
||||
axis = Vector2d.POSITIVE_Y,
|
||||
enableLimit = true,
|
||||
upperTranslation = 0.25,
|
||||
lowerTranslation = -0.25,
|
||||
))
|
||||
|
||||
base.setTransform(base.position, 0.56)
|
||||
}
|
||||
|
||||
run {
|
||||
val circleNail = world.createBody(BodyDef(
|
||||
type = BodyType.STATIC,
|
||||
position = Vector2d(x = -12.0, y = 4.0),
|
||||
))
|
||||
|
||||
val circleBody = world.createBody(BodyDef(
|
||||
type = BodyType.DYNAMIC,
|
||||
position = Vector2d(x = -12.0, y = 4.0),
|
||||
))
|
||||
|
||||
circleBody.createFixture(FixtureDef(
|
||||
shape = CircleShape(2.0),
|
||||
density = 1.0,
|
||||
friction = 0.3
|
||||
))
|
||||
|
||||
val circleNailJoint = world.createJoint(RevoluteJointDef(
|
||||
circleNail,
|
||||
circleBody,
|
||||
Vector2d(x = -12.0, y = 4.0)
|
||||
))
|
||||
|
||||
val boxNail = world.createBody(BodyDef(
|
||||
type = BodyType.STATIC,
|
||||
position = Vector2d(x = 2.0, y = 4.0),
|
||||
))
|
||||
|
||||
val boxBody = world.createBody(BodyDef(
|
||||
type = BodyType.DYNAMIC,
|
||||
position = Vector2d(x = 2.0, y = 4.0),
|
||||
))
|
||||
|
||||
boxBody.createFixture(FixtureDef(
|
||||
shape = PolygonShape().also { it.setAsBox(3.5, 0.5) },
|
||||
density = 1.0,
|
||||
friction = 0.3
|
||||
))
|
||||
|
||||
val boxNailJoint = world.createJoint(RevoluteJointDef(
|
||||
boxNail,
|
||||
boxBody,
|
||||
Vector2d(x = 2.0, y = 4.0)
|
||||
))
|
||||
|
||||
val gearJoint = world.createJoint(GearJointDef(
|
||||
circleBody,
|
||||
boxBody,
|
||||
circleNailJoint,
|
||||
boxNailJoint,
|
||||
1.0
|
||||
))
|
||||
}
|
||||
|
||||
run {
|
||||
val boxA = world.createBody(BodyDef(
|
||||
type = BodyType.DYNAMIC,
|
||||
position = Vector2d(x = -2.0, y = 6.0),
|
||||
))
|
||||
|
||||
val boxB = world.createBody(BodyDef(
|
||||
type = BodyType.DYNAMIC,
|
||||
position = Vector2d(x = 0.0, y = 6.0),
|
||||
))
|
||||
|
||||
val boxC = world.createBody(BodyDef(
|
||||
type = BodyType.DYNAMIC,
|
||||
position = Vector2d(x = 2.0, y = 6.0),
|
||||
))
|
||||
|
||||
val dynamicBox = PolygonShape()
|
||||
dynamicBox.setAsBox(1.0, 1.0)
|
||||
|
||||
val fixtureDef = FixtureDef(
|
||||
shape = dynamicBox,
|
||||
density = 1.0,
|
||||
friction = 0.3
|
||||
)
|
||||
|
||||
boxA.createFixture(fixtureDef)
|
||||
boxB.createFixture(fixtureDef)
|
||||
boxC.createFixture(fixtureDef)
|
||||
|
||||
world.createJoint(WeldJointDef(
|
||||
bodyA = boxA, bodyB = boxB, (boxA.position + boxB.position) * 0.5
|
||||
).linearStiffness(5.0, 0.5, boxA, boxB))
|
||||
|
||||
world.createJoint(WeldJointDef(
|
||||
bodyA = boxB, bodyB = boxC, (boxB.position + boxC.position) * 0.5
|
||||
).linearStiffness(5.0, 0.5, boxB, boxC))
|
||||
}
|
||||
|
||||
run {
|
||||
val boxA = world.createBody(BodyDef(
|
||||
type = BodyType.DYNAMIC,
|
||||
position = Vector2d(x = -2.0, y = 8.0),
|
||||
))
|
||||
|
||||
val dynamicBox = PolygonShape()
|
||||
dynamicBox.setAsBox(1.0, 1.0)
|
||||
|
||||
val fixtureDef = FixtureDef(
|
||||
shape = dynamicBox,
|
||||
density = 1.0,
|
||||
friction = 0.3
|
||||
)
|
||||
|
||||
boxA.createFixture(fixtureDef)
|
||||
|
||||
world.createJoint(FrictionJointDef(
|
||||
bodyA = boxA,
|
||||
bodyB = ground,
|
||||
Vector2d(x = -2.0, y = 8.0),
|
||||
maxForce = 1.0,
|
||||
maxTorque = 10.0,
|
||||
collideConnected = true
|
||||
))
|
||||
}
|
||||
|
||||
run {
|
||||
val boxA = world.createBody(BodyDef(
|
||||
type = BodyType.DYNAMIC,
|
||||
position = Vector2d(x = -2.0, y = 8.0),
|
||||
))
|
||||
|
||||
val dynamicBox = PolygonShape()
|
||||
dynamicBox.setAsBox(1.0, 1.0)
|
||||
|
||||
val fixtureDef = FixtureDef(
|
||||
shape = dynamicBox,
|
||||
density = 1.0,
|
||||
friction = 0.3
|
||||
)
|
||||
|
||||
boxA.createFixture(fixtureDef)
|
||||
|
||||
world.createJoint(MotorJointDef(
|
||||
bodyA = boxA,
|
||||
bodyB = ground,
|
||||
maxForce = 1000.0,
|
||||
collideConnected = true,
|
||||
))
|
||||
}
|
||||
|
||||
val timeStep = 1.0 / 144.0
|
||||
|
||||
val client = StarboundClient()
|
||||
|
||||
//Starbound.addFilePath(File("./unpacked_assets/"))
|
||||
Starbound.addPakPath(File("J:\\SteamLibrary\\steamapps\\common\\Starbound\\assets\\packed.pak"))
|
||||
|
||||
/*Starbound.initializeGame { finished, replaceStatus, status ->
|
||||
Starbound.initializeGame { finished, replaceStatus, status ->
|
||||
client.putDebugLog(status, replaceStatus)
|
||||
}*/
|
||||
}
|
||||
|
||||
client.onTermination {
|
||||
Starbound.terminateLoading = true
|
||||
@ -416,14 +49,14 @@ fun main() {
|
||||
val ent = PlayerEntity(client.world!!)
|
||||
|
||||
Starbound.onInitialize {
|
||||
/*chunkA = client.world!!.computeIfAbsent(ChunkPos(0, 0)).chunk
|
||||
chunkA = client.world!!.computeIfAbsent(ChunkPos(0, 0)).chunk
|
||||
val chunkB = client.world!!.computeIfAbsent(ChunkPos(-1, 0)).chunk
|
||||
val chunkC = client.world!!.computeIfAbsent(ChunkPos(-2, 0)).chunk
|
||||
|
||||
val tile = Starbound.getTileDefinition("alienrock")
|
||||
|
||||
for (x in -48 .. 48) {
|
||||
for (y in 0 .. 20) {
|
||||
for (x in -6 .. 6) {
|
||||
for (y in 0 .. 4) {
|
||||
val chnk = client.world!!.computeIfAbsent(ChunkPos(x, y))
|
||||
|
||||
if (y == 0) {
|
||||
@ -486,19 +119,48 @@ fun main() {
|
||||
chunkA!!.foreground[rand.nextInt(0, CHUNK_SIZE_FF), rand.nextInt(0, CHUNK_SIZE_FF)] = tile
|
||||
}*/
|
||||
|
||||
ent.movement.dropToFloor()
|
||||
// ent.movement.dropToFloor()
|
||||
|
||||
for ((i, proj) in Starbound.projectilesAccess.values.withIndex()) {
|
||||
val projEnt = Projectile(client.world!!, proj)
|
||||
projEnt.pos = Vector2d(i * 2.0, 10.0)
|
||||
projEnt.position = Vector2d(i * 2.0, 10.0)
|
||||
projEnt.spawn()
|
||||
}*/
|
||||
}
|
||||
|
||||
run {
|
||||
val stripes = 4
|
||||
|
||||
for (stripe in 0 until stripes) {
|
||||
for (x in 0 .. (stripes - stripe)) {
|
||||
val movingBody = client.world!!.physics.createBody(BodyDef(
|
||||
type = BodyType.DYNAMIC,
|
||||
position = Vector2d(x = (-stripes + stripe) * 1.0 + x * 2.1, y = 8.0 + stripe * 2.1),
|
||||
gravityScale = 1.1
|
||||
))
|
||||
|
||||
val dynamicBox: IShape<*>
|
||||
|
||||
if (false) {
|
||||
dynamicBox = PolygonShape()
|
||||
dynamicBox.setAsBox(1.0, 1.0)
|
||||
} else {
|
||||
dynamicBox = CircleShape(1.0)
|
||||
}
|
||||
|
||||
movingBody.createFixture(FixtureDef(
|
||||
shape = dynamicBox,
|
||||
density = 1.0,
|
||||
friction = 0.3
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ent.pos += Vector2d(y = 36.0, x = -10.0)
|
||||
ent.position += Vector2d(y = 36.0, x = -10.0)
|
||||
|
||||
client.onDrawGUI {
|
||||
client.gl.font.render("${ent.pos}", y = 100f, scale = 0.25f)
|
||||
client.gl.font.render("${ent.position}", y = 100f, scale = 0.25f)
|
||||
client.gl.font.render("${ent.movement.velocity}", y = 120f, scale = 0.25f)
|
||||
}
|
||||
|
||||
@ -509,65 +171,13 @@ fun main() {
|
||||
|
||||
client.camera.pos.y = 10f
|
||||
|
||||
world.debugDraw = client.gl.box2dRenderer
|
||||
|
||||
client.gl.box2dRenderer.drawShapes = true
|
||||
client.gl.box2dRenderer.drawPairs = false
|
||||
client.gl.box2dRenderer.drawAABB = false
|
||||
client.gl.box2dRenderer.drawJoints = false
|
||||
|
||||
client.onPostDrawWorld {
|
||||
world.debugDraw()
|
||||
|
||||
client.gl.quadWireframe {
|
||||
var pos: Vector2d
|
||||
|
||||
/*for (box in boxes) {
|
||||
pos = box.position
|
||||
|
||||
it.quadRotated(
|
||||
-1f,
|
||||
-1f,
|
||||
1f,
|
||||
1f,
|
||||
pos.x.toFloat(),
|
||||
pos.y.toFloat(),
|
||||
box.angle,
|
||||
)
|
||||
}*/
|
||||
|
||||
/*for (box in boxes) {
|
||||
val broad = world.contactManager.broadPhase
|
||||
val f = box.fixtureList!!
|
||||
val proxy = f.proxies[0].proxyId
|
||||
val aabb = broad.getFatAABB(proxy)
|
||||
|
||||
it.quad(
|
||||
aabb,
|
||||
)
|
||||
}*/
|
||||
|
||||
/*pos = ground.position
|
||||
|
||||
it.quad(
|
||||
(pos.x - 50f).toFloat(),
|
||||
(pos.y - 10f).toFloat(),
|
||||
(pos.x + 50f).toFloat(),
|
||||
(pos.y + 10f).toFloat(),
|
||||
)*/
|
||||
}
|
||||
|
||||
for (joint in mouseJoints) {
|
||||
joint.targetA = Vector2d(rand.nextDouble() * 20.0, rand.nextDouble() * 20.0)
|
||||
|
||||
if (rand.nextDouble() < 0.01) {
|
||||
world.destroyJoint(joint)
|
||||
mouseJoints.remove(joint)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
world.step(timeStep, 6, 4)
|
||||
}
|
||||
|
||||
ent.spawn()
|
||||
@ -576,8 +186,8 @@ fun main() {
|
||||
Starbound.pollCallbacks()
|
||||
|
||||
//ent.think(client.frameRenderTime)
|
||||
//client.camera.pos.x = ent.pos.x.toFloat()
|
||||
//client.camera.pos.y = ent.pos.y.toFloat()
|
||||
client.camera.pos.x = ent.position.x.toFloat()
|
||||
client.camera.pos.y = ent.position.y.toFloat()
|
||||
|
||||
//println(client.camera.velocity.toDoubleVector() * client.frameRenderTime * 0.1)
|
||||
|
||||
|
@ -6,6 +6,10 @@ import ru.dbotthepony.kstarbound.world.*
|
||||
import ru.dbotthepony.kvector.util2d.AABB
|
||||
|
||||
class ClientWorld(val client: StarboundClient, seed: Long = 0L) : World<ClientWorld, ClientChunk>(seed) {
|
||||
init {
|
||||
physics.debugDraw = client.gl.box2dRenderer
|
||||
}
|
||||
|
||||
override fun chunkFactory(pos: ChunkPos): ClientChunk {
|
||||
return ClientChunk(
|
||||
world = this,
|
||||
@ -33,6 +37,8 @@ class ClientWorld(val client: StarboundClient, seed: Long = 0L) : World<ClientWo
|
||||
|
||||
renderLayeredList(client.gl.matrixStack, determineRenderers)
|
||||
|
||||
physics.debugDraw()
|
||||
|
||||
for (renderer in determineRenderers) {
|
||||
renderer.renderDebug()
|
||||
}
|
||||
|
@ -25,7 +25,6 @@ class StarboundClient : AutoCloseable {
|
||||
val window: Long
|
||||
val camera = Camera(this)
|
||||
val input = UserInput()
|
||||
var world: ClientWorld? = ClientWorld(this, 0L)
|
||||
|
||||
var gameTerminated = false
|
||||
private set
|
||||
@ -126,6 +125,8 @@ class StarboundClient : AutoCloseable {
|
||||
|
||||
val gl = GLStateTracker()
|
||||
|
||||
var world: ClientWorld? = ClientWorld(this, 0L)
|
||||
|
||||
fun ensureSameThread() = gl.ensureSameThread()
|
||||
|
||||
init {
|
||||
|
@ -16,7 +16,7 @@ import java.io.Closeable
|
||||
* Считается, что процесс отрисовки ограничен лишь одним слоем (т.е. отрисовка происходит в один проход)
|
||||
*/
|
||||
open class EntityRenderer(val state: GLStateTracker, val entity: Entity, open var chunk: ClientChunk?) : Closeable {
|
||||
open val renderPos: Vector2d get() = entity.pos
|
||||
open val renderPos: Vector2d get() = entity.position
|
||||
|
||||
open fun render(stack: Matrix4fStack) {
|
||||
|
||||
|
@ -1,5 +1,9 @@
|
||||
package ru.dbotthepony.kstarbound.world
|
||||
|
||||
import ru.dbotthepony.kbox2d.api.BodyDef
|
||||
import ru.dbotthepony.kbox2d.api.FixtureDef
|
||||
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
|
||||
import ru.dbotthepony.kbox2d.dynamics.B2Fixture
|
||||
import ru.dbotthepony.kstarbound.defs.TileDefinition
|
||||
import ru.dbotthepony.kstarbound.math.*
|
||||
import ru.dbotthepony.kstarbound.world.entities.Entity
|
||||
@ -355,6 +359,20 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
||||
|
||||
val aabb = aabbBase + Vector2d(pos.x * CHUNK_SIZE.toDouble(), pos.y * CHUNK_SIZE.toDouble())
|
||||
|
||||
var isPhysicsDirty = false
|
||||
|
||||
fun markPhysicsDirty() {
|
||||
if (isPhysicsDirty)
|
||||
return
|
||||
|
||||
isPhysicsDirty = true
|
||||
world.dirtyPhysicsChunks.add(this as This)
|
||||
}
|
||||
|
||||
fun bakeCollisions() {
|
||||
foreground.bakeCollisions()
|
||||
}
|
||||
|
||||
inner class TileLayer : IMutableTileChunk {
|
||||
/**
|
||||
* Возвращает счётчик изменений этого слоя
|
||||
@ -371,12 +389,25 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
||||
private val collisionCacheView = Collections.unmodifiableCollection(collisionCache)
|
||||
private var collisionChangeset = -1
|
||||
|
||||
// максимально грубое комбинирование тайлов в бруски для коллизии
|
||||
// TODO: https://ru.wikipedia.org/wiki/R-дерево_(структура_данных)
|
||||
private fun bakeCollisions() {
|
||||
private val body = world.physics.createBody(BodyDef(
|
||||
position = pos.firstBlock.toDoubleVector(),
|
||||
))
|
||||
|
||||
private val collisionBoxes = ArrayList<B2Fixture>()
|
||||
|
||||
fun bakeCollisions() {
|
||||
if (collisionChangeset == changeset)
|
||||
return
|
||||
|
||||
collisionChangeset = changeset
|
||||
collisionCache.clear()
|
||||
|
||||
for (box in collisionBoxes) {
|
||||
body.destroyFixture(box)
|
||||
}
|
||||
|
||||
collisionBoxes.clear()
|
||||
|
||||
val xAdd = pos.x * CHUNK_SIZEd
|
||||
val yAdd = pos.y * CHUNK_SIZEd
|
||||
|
||||
@ -393,12 +424,17 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
||||
last = x
|
||||
} else {
|
||||
if (first != null) {
|
||||
collisionCache.add(
|
||||
AABB(
|
||||
val aabb = AABB(
|
||||
Vector2d(x = xAdd + first.toDouble(), y = y.toDouble() + yAdd),
|
||||
Vector2d(x = xAdd + last.toDouble() + 1.0, y = y.toDouble() + 1.0 + yAdd),
|
||||
)
|
||||
)
|
||||
|
||||
collisionCache.add(aabb)
|
||||
|
||||
body.createFixture(FixtureDef(
|
||||
shape = PolygonShape().also { it.setAsBox(aabb.width / 2.0, aabb.height / 2.0, aabb.centre - pos.firstBlock.toDoubleVector(), 0.0) },
|
||||
friction = 0.4,
|
||||
))
|
||||
|
||||
first = null
|
||||
}
|
||||
@ -406,12 +442,17 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
||||
}
|
||||
|
||||
if (first != null) {
|
||||
collisionCache.add(
|
||||
AABB(
|
||||
val aabb = AABB(
|
||||
Vector2d(x = first.toDouble() + xAdd, y = y.toDouble() + yAdd),
|
||||
Vector2d(x = last.toDouble() + 1.0 + xAdd, y = y.toDouble() + 1.0 + yAdd),
|
||||
)
|
||||
)
|
||||
|
||||
collisionCache.add(aabb)
|
||||
|
||||
body.createFixture(FixtureDef(
|
||||
shape = PolygonShape().also { it.setAsBox(aabb.width / 2.0, aabb.height / 2.0, aabb.centre - pos.firstBlock.toDoubleVector(), 0.0) },
|
||||
friction = 0.4,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -451,6 +492,7 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
||||
|
||||
changeset++
|
||||
tiles[x or (y shl CHUNK_SHIFT)] = tile
|
||||
markPhysicsDirty()
|
||||
}
|
||||
|
||||
override operator fun set(x: Int, y: Int, tile: TileDefinition?): ChunkTile? {
|
||||
@ -460,6 +502,7 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
||||
val chunkTile = if (tile != null) ChunkTile(this, tile) else null
|
||||
this[x, y] = chunkTile
|
||||
changeset++
|
||||
markPhysicsDirty()
|
||||
return chunkTile
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package ru.dbotthepony.kstarbound.world
|
||||
|
||||
import ru.dbotthepony.kbox2d.dynamics.B2World
|
||||
import ru.dbotthepony.kstarbound.METRES_IN_STARBOUND_UNIT
|
||||
import ru.dbotthepony.kstarbound.defs.TileDefinition
|
||||
import ru.dbotthepony.kstarbound.math.*
|
||||
@ -98,8 +99,16 @@ private const val EPSILON = 0.00001
|
||||
|
||||
abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, ChunkType>>(val seed: Long = 0L) {
|
||||
protected val chunkMap = HashMap<ChunkPos, IMutableWorldChunkTuple<This, ChunkType>>()
|
||||
|
||||
/**
|
||||
* Chunks, which have their collision mesh changed
|
||||
*/
|
||||
val dirtyPhysicsChunks = HashSet<ChunkType>()
|
||||
|
||||
protected var lastAccessedChunk: IMutableWorldChunkTuple<This, ChunkType>? = null
|
||||
|
||||
val physics = B2World(Vector2d(0.0, -EARTH_FREEFALL_ACCELERATION))
|
||||
|
||||
/**
|
||||
* Таймер этого мира, в секундах.
|
||||
*
|
||||
@ -124,6 +133,14 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
||||
fun think(delta: Double) {
|
||||
require(delta > 0.0) { "Tried to update $this by $delta seconds" }
|
||||
|
||||
for (chunk in dirtyPhysicsChunks) {
|
||||
chunk.bakeCollisions()
|
||||
}
|
||||
|
||||
dirtyPhysicsChunks.clear()
|
||||
|
||||
physics.step(delta, 6, 4)
|
||||
|
||||
timer += delta
|
||||
thinkInner(delta)
|
||||
}
|
||||
@ -145,7 +162,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
||||
*
|
||||
* При Vector2d.ZERO = невесомость
|
||||
*/
|
||||
var gravity = Vector2d(0.0, -EARTH_FREEFALL_ACCELERATION)
|
||||
var gravity by physics::gravity
|
||||
|
||||
protected abstract fun chunkFactory(
|
||||
pos: ChunkPos,
|
||||
@ -200,7 +217,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
||||
val orphanedInThisChunk = ArrayList<Entity>()
|
||||
|
||||
for (ent in orphanedEntities) {
|
||||
val cPos = ChunkPos.fromTilePosition(ent.pos)
|
||||
val cPos = ChunkPos.fromTilePosition(ent.position)
|
||||
|
||||
if (cPos == pos) {
|
||||
orphanedInThisChunk.add(ent)
|
||||
|
@ -14,16 +14,6 @@ enum class Move {
|
||||
}
|
||||
|
||||
interface IWalkableEntity : IEntity {
|
||||
/**
|
||||
* AABB сущности, которая стоит
|
||||
*/
|
||||
val standingAABB: AABB
|
||||
|
||||
/**
|
||||
* AABB сущности, которая присела
|
||||
*/
|
||||
val duckingAABB: AABB
|
||||
|
||||
/**
|
||||
* Максимальная скорость передвижения этого AliveMovementController в Starbound Units/секунда
|
||||
*
|
||||
@ -75,54 +65,48 @@ interface IWalkableEntity : IEntity {
|
||||
* Базовый абстрактный класс, реализующий сущность, которая ходит по земле
|
||||
*/
|
||||
abstract class WalkableMovementController<T : IWalkableEntity>(entity: T) : MovementController<T>(entity) {
|
||||
init {
|
||||
body.isFixedRotation = true
|
||||
}
|
||||
|
||||
protected abstract val moveDirection: Move
|
||||
override val collisionResolution = CollisionResolution.SLIDE
|
||||
|
||||
var wantsToDuck = false
|
||||
var isDucked = false
|
||||
open var isDucked = false
|
||||
protected set
|
||||
|
||||
override val currentAABB: AABB
|
||||
get() {
|
||||
if (isDucked) {
|
||||
return entity.duckingAABB
|
||||
}
|
||||
|
||||
return entity.standingAABB
|
||||
}
|
||||
|
||||
override fun thinkPhysics(delta: Double) {
|
||||
super.thinkPhysics(delta)
|
||||
thinkMovement(delta)
|
||||
}
|
||||
|
||||
/**
|
||||
* Смотрим [IWalkableEntity.topSpeed]
|
||||
* See [IWalkableEntity.topSpeed]
|
||||
*/
|
||||
open val topSpeed by entity::topSpeed
|
||||
|
||||
/**
|
||||
* Смотрим [IWalkableEntity.moveSpeed]
|
||||
* See [IWalkableEntity.moveSpeed]
|
||||
*/
|
||||
open val moveSpeed by entity::moveSpeed
|
||||
|
||||
/**
|
||||
* Смотрим [IWalkableEntity.freeFallMoveSpeed]
|
||||
* See [IWalkableEntity.freeFallMoveSpeed]
|
||||
*/
|
||||
open val freeFallMoveSpeed by entity::freeFallMoveSpeed
|
||||
|
||||
/**
|
||||
* Смотрим [IWalkableEntity.brakeForce]
|
||||
* See [IWalkableEntity.brakeForce]
|
||||
*/
|
||||
open val brakeForce by entity::brakeForce
|
||||
|
||||
/**
|
||||
* Смотрим [IWalkableEntity.stepSize]
|
||||
* See [IWalkableEntity.stepSize]
|
||||
*/
|
||||
open val stepSize by entity::stepSize
|
||||
|
||||
/**
|
||||
* Смотрим [IWalkableEntity.jumpForce]
|
||||
* See [IWalkableEntity.jumpForce]
|
||||
*/
|
||||
open val jumpForce by entity::jumpForce
|
||||
|
||||
@ -144,115 +128,64 @@ abstract class WalkableMovementController<T : IWalkableEntity>(entity: T) : Move
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract fun canUnDuck(): Boolean
|
||||
|
||||
protected open fun thinkMovement(delta: Double) {
|
||||
if (onGround || !affectedByGravity) {
|
||||
var add = Vector2d.ZERO
|
||||
if (onGround && !isDucked) {
|
||||
when (moveDirection) {
|
||||
Move.STAND_STILL -> {
|
||||
body.linearVelocity += Vector2d(x = -body.linearVelocity.x * delta * brakeForce)
|
||||
}
|
||||
|
||||
if (isDucked) {
|
||||
thinkFriction(delta * brakeForce)
|
||||
} else if (velocity.y.absoluteValue < 1 && !jumpRequested) {
|
||||
when (moveDirection) {
|
||||
Move.STAND_STILL -> {
|
||||
thinkFriction(delta * brakeForce)
|
||||
add = Vector2d.ZERO
|
||||
Move.MOVE_LEFT -> {
|
||||
if (body.linearVelocity.x > 0.0) {
|
||||
body.linearVelocity += Vector2d(x = -body.linearVelocity.x * delta * brakeForce)
|
||||
}
|
||||
|
||||
Move.MOVE_LEFT -> {
|
||||
if (velocity.x > 0.0) {
|
||||
thinkFriction(delta * brakeForce)
|
||||
}
|
||||
|
||||
add = Vector2d(x = -delta * moveSpeed)
|
||||
}
|
||||
|
||||
Move.MOVE_RIGHT -> {
|
||||
if (velocity.x < 0.0) {
|
||||
thinkFriction(delta * brakeForce)
|
||||
}
|
||||
|
||||
add = Vector2d(x = delta * moveSpeed)
|
||||
if (body.linearVelocity.x > -topSpeed) {
|
||||
body.linearVelocity += Vector2d(x = -moveSpeed * delta)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (add != Vector2d.ZERO) {
|
||||
if (isSpaceOpen(add, delta)) {
|
||||
velocity += add
|
||||
|
||||
// спускание с "лестницы"
|
||||
val sweep = sweepAbsolute(pos + velocity * delta * 4.0, Vector2d(y = -stepSize), delta)
|
||||
|
||||
if (sweep.hitAnything && sweep.hitPosition.y < -0.1) {
|
||||
dropToFloor()
|
||||
thinkFriction(delta * 6.0)
|
||||
Move.MOVE_RIGHT -> {
|
||||
if (body.linearVelocity.x < 0.0) {
|
||||
body.linearVelocity += Vector2d(x = -body.linearVelocity.x * delta * brakeForce)
|
||||
}
|
||||
|
||||
if (world is ClientWorld && world.client.settings.debugCollisions) {
|
||||
world.client.onPostDrawWorldOnce {
|
||||
world.client.gl.quadWireframe(worldAABB + velocity * delta * 4.0 + sweep.hitPosition, Color.RED)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// подъем по "лестнице"
|
||||
val sweep = sweepRelative(Vector2d(y = stepSize), delta)
|
||||
|
||||
if (!sweep.hitAnything) {
|
||||
val sweep2 = sweepAbsolute(pos + sweep.hitPosition, Vector2d(x = -0.1 + add.x), delta)
|
||||
|
||||
if (!sweep2.hitAnything) {
|
||||
pos += sweep.hitPosition + sweep2.hitPosition
|
||||
thinkFriction(delta * 64.0)
|
||||
dropToFloor()
|
||||
}
|
||||
|
||||
if (world is ClientWorld && world.client.settings.debugCollisions) {
|
||||
world.client.onPostDrawWorldOnce {
|
||||
world.client.gl.quadWireframe(worldAABB + sweep.hitPosition + sweep2.hitPosition, Color.GREEN)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (world is ClientWorld && world.client.settings.debugCollisions) {
|
||||
world.client.onPostDrawWorldOnce {
|
||||
world.client.gl.quadWireframe(worldAABB + sweep.hitPosition, Color.BLUE)
|
||||
}
|
||||
if (body.linearVelocity.x < topSpeed) {
|
||||
body.linearVelocity += Vector2d(x = moveSpeed * delta)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (velocity.length > topSpeed) {
|
||||
thinkFriction(delta * (velocity.length - topSpeed))
|
||||
}
|
||||
|
||||
if (jumpRequested) {
|
||||
jumpRequested = false
|
||||
velocity += groundNormal * jumpForce
|
||||
onGround = false
|
||||
nextJump = world.timer + 0.1
|
||||
|
||||
body.linearVelocity += Vector2d(y = jumpForce)
|
||||
}
|
||||
} else if (!onGround && affectedByGravity) {
|
||||
} else if (!onGround && !isDucked && freeFallMoveSpeed != 0.0) {
|
||||
when (moveDirection) {
|
||||
Move.STAND_STILL -> {}
|
||||
Move.STAND_STILL -> {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
Move.MOVE_LEFT -> {
|
||||
val add = Vector2d(x = -delta * freeFallMoveSpeed)
|
||||
|
||||
if (isSpaceOpen(add, delta))
|
||||
velocity += add
|
||||
body.linearVelocity += Vector2d(x = -freeFallMoveSpeed * delta)
|
||||
}
|
||||
|
||||
Move.MOVE_RIGHT -> {
|
||||
val add = Vector2d(x = delta * freeFallMoveSpeed)
|
||||
|
||||
if (isSpaceOpen(add, delta))
|
||||
velocity += add
|
||||
body.linearVelocity += Vector2d(x = freeFallMoveSpeed * delta)
|
||||
}
|
||||
}
|
||||
} else if (onGround && isDucked) {
|
||||
body.linearVelocity += Vector2d(x = -body.linearVelocity.x * delta * brakeForce)
|
||||
}
|
||||
|
||||
if (wantsToDuck && onGround) {
|
||||
isDucked = true
|
||||
} else if (isDucked) {
|
||||
if (world.isSpaceEmptyFromTiles(entity.standingAABB + pos)) {
|
||||
if (canUnDuck()) {
|
||||
isDucked = false
|
||||
}
|
||||
}
|
||||
@ -270,7 +203,7 @@ abstract class AliveWalkingEntity(world: World<*, *>) : AliveEntity(world), IWal
|
||||
override val topSpeed = 20.0
|
||||
override val moveSpeed = 64.0
|
||||
override val freeFallMoveSpeed = 8.0
|
||||
override val brakeForce = 32.0
|
||||
override val brakeForce = 16.0
|
||||
override val jumpForce = 20.0
|
||||
override val stepSize = 1.1
|
||||
|
||||
|
@ -9,12 +9,57 @@ import ru.dbotthepony.kvector.vector.ndouble.Vector2d
|
||||
* Интерфейс служит лишь для убирания жёсткой зависимости от класса Entity
|
||||
*/
|
||||
interface IEntity {
|
||||
/**
|
||||
* The world this entity in, never changes.
|
||||
*/
|
||||
val world: World<*, *>
|
||||
|
||||
/**
|
||||
* The chunk this entity currently in, it is automatically updated on each change of [position]
|
||||
*/
|
||||
var chunk: Chunk<*, *>?
|
||||
var pos: Vector2d
|
||||
var rotation: Double
|
||||
|
||||
/**
|
||||
* Current entity position. If entity is logical, this can only be changed
|
||||
* by external means, otherwise it always stands where it is.
|
||||
*
|
||||
* If entity is physical, then movement controller will update this on each physics step
|
||||
* to match position of physical body.
|
||||
*
|
||||
* Setting this value will update [chunk] immediately, if [isSpawned] is true and [isRemoved] is false.
|
||||
*/
|
||||
var position: Vector2d
|
||||
|
||||
/**
|
||||
* This entity's angle in radians.
|
||||
*
|
||||
* Logical entities never rotate.
|
||||
*
|
||||
* Alive entities usually don't rotate.
|
||||
*
|
||||
* If entity is physical, this value is updated by movement controller on
|
||||
* each physics step to match angle of physical body.
|
||||
*/
|
||||
var angle: Double
|
||||
|
||||
/**
|
||||
* This entity's movement controller. Even logical entities have one, but they have
|
||||
* dummy movement controller, which does nothing.
|
||||
*
|
||||
* If entity is physical, this controller handle interaction with Box2D world, update angles and
|
||||
* position and other stuff.
|
||||
*/
|
||||
val movement: MovementController<*>
|
||||
|
||||
/**
|
||||
* Whenever is this entity spawned in world ([spawn] called).
|
||||
* Doesn't mean entity still exists in world, check it with [isRemoved]
|
||||
*/
|
||||
val isSpawned: Boolean
|
||||
|
||||
/**
|
||||
* Whenever is this entity was removed from world ([remove] called).
|
||||
*/
|
||||
val isRemoved: Boolean
|
||||
|
||||
fun spawn()
|
||||
@ -33,11 +78,15 @@ abstract class Entity(override val world: World<*, *>) : IEntity {
|
||||
throw IllegalStateException("Trying to set chunk this entity belong to before spawning in world")
|
||||
}
|
||||
|
||||
if (isRemoved) {
|
||||
throw IllegalStateException("This entity was removed")
|
||||
}
|
||||
|
||||
if (value == field) {
|
||||
return
|
||||
}
|
||||
|
||||
val chunkPos = ChunkPos.fromTilePosition(pos)
|
||||
val chunkPos = ChunkPos.fromTilePosition(position)
|
||||
|
||||
if (value != null && chunkPos != value.pos) {
|
||||
throw IllegalStateException("Set proper position before setting chunk this Entity belongs to")
|
||||
@ -57,7 +106,7 @@ abstract class Entity(override val world: World<*, *>) : IEntity {
|
||||
}
|
||||
}
|
||||
|
||||
override var pos = Vector2d()
|
||||
override var position = Vector2d()
|
||||
set(value) {
|
||||
if (field == value)
|
||||
return
|
||||
@ -65,7 +114,9 @@ abstract class Entity(override val world: World<*, *>) : IEntity {
|
||||
val old = field
|
||||
field = value
|
||||
|
||||
if (isSpawned) {
|
||||
movement.notifyPositionChanged()
|
||||
|
||||
if (isSpawned && !isRemoved) {
|
||||
val oldChunkPos = ChunkPos.fromTilePosition(old)
|
||||
val newChunkPos = ChunkPos.fromTilePosition(value)
|
||||
|
||||
@ -75,7 +126,7 @@ abstract class Entity(override val world: World<*, *>) : IEntity {
|
||||
}
|
||||
}
|
||||
|
||||
override var rotation: Double = 0.0
|
||||
override var angle: Double = 0.0
|
||||
|
||||
final override var isSpawned = false
|
||||
private set
|
||||
@ -88,7 +139,7 @@ abstract class Entity(override val world: World<*, *>) : IEntity {
|
||||
|
||||
isSpawned = true
|
||||
world.entities.add(this)
|
||||
chunk = world.getChunk(ChunkPos.fromTilePosition(pos))?.chunk
|
||||
chunk = world.getChunk(ChunkPos.fromTilePosition(position))?.chunk
|
||||
|
||||
if (chunk == null) {
|
||||
world.orphanedEntities.add(this)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package ru.dbotthepony.kstarbound.world.entities
|
||||
|
||||
import ru.dbotthepony.kstarbound.math.lerp
|
||||
import ru.dbotthepony.kbox2d.api.BodyDef
|
||||
import ru.dbotthepony.kbox2d.api.BodyType
|
||||
import ru.dbotthepony.kvector.util2d.AABB
|
||||
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
|
||||
|
||||
@ -13,129 +14,76 @@ enum class CollisionResolution {
|
||||
|
||||
abstract class MovementController<T : IEntity>(val entity: T) {
|
||||
val world = entity.world
|
||||
var pos by entity::pos
|
||||
var rotation by entity::rotation
|
||||
open var position by entity::position
|
||||
open var angle by entity::angle
|
||||
|
||||
open val mass = 1.0
|
||||
|
||||
// наследуемые свойства
|
||||
open val affectedByGravity = true
|
||||
open val collisionResolution = CollisionResolution.STOP
|
||||
|
||||
var velocity = Vector2d()
|
||||
|
||||
/**
|
||||
* Касается ли AABB сущности земли
|
||||
*
|
||||
* Данный флаг выставляется при обработке скорости, если данный флаг не будет выставлен
|
||||
* правильно, то сущность будет иметь очень плохое движение в стороны
|
||||
*
|
||||
* Так же от него зависит то, может ли сущность двигаться, если она не парит
|
||||
*
|
||||
* Если сущность касается земли, то на неё не действует гравитация
|
||||
*/
|
||||
var onGround = false
|
||||
protected set(value) {
|
||||
field = value
|
||||
nextOnGroundUpdate = world.timer + 0.1
|
||||
}
|
||||
|
||||
/**
|
||||
* Текущий AABB этого Movement Controller
|
||||
*
|
||||
* Это может быть как и статичное значение (для данного типа сущности), так и динамичное
|
||||
* (к примеру, присевший игрок)
|
||||
*
|
||||
* Данное значение, хоть и является val, НЕ ЯВЛЯЕТСЯ КОНСТАНТОЙ!
|
||||
*/
|
||||
abstract val currentAABB: AABB
|
||||
|
||||
/**
|
||||
* Текущий AABB в отображении на мировые координаты
|
||||
*/
|
||||
val worldAABB: AABB get() = currentAABB + pos
|
||||
|
||||
protected var nextOnGroundUpdate = 0.0
|
||||
|
||||
protected fun sweepRelative(velocity: Vector2d, delta: Double, collisionResolution: CollisionResolution = this.collisionResolution) = world.sweep(worldAABB, velocity, collisionResolution, delta)
|
||||
protected fun sweepAbsolute(from: Vector2d, velocity: Vector2d, delta: Double, collisionResolution: CollisionResolution = this.collisionResolution) = world.sweep(currentAABB + from, velocity, collisionResolution, delta)
|
||||
protected fun isSpaceOpen(relative: Vector2d, delta: Double) = !sweepRelative(relative, delta).hitAnything
|
||||
|
||||
fun dropToFloor() {
|
||||
val sweep = sweepRelative(DROP_TO_FLOOR, 1.0, CollisionResolution.STOP)
|
||||
|
||||
if (!sweep.hitAnything)
|
||||
return
|
||||
|
||||
pos += sweep.hitPosition
|
||||
protected val body by lazy {
|
||||
world.physics.createBody(BodyDef(
|
||||
position = position,
|
||||
angle = angle,
|
||||
type = BodyType.DYNAMIC
|
||||
))
|
||||
}
|
||||
|
||||
var groundNormal = Vector2d.ZERO
|
||||
protected set
|
||||
open val velocity get() = body.linearVelocity
|
||||
|
||||
protected open fun propagateVelocity(delta: Double) {
|
||||
if (velocity.length == 0.0)
|
||||
return
|
||||
|
||||
val sweep = sweepRelative(velocity * delta, delta)
|
||||
this.velocity = sweep.hitPosition / delta
|
||||
this.pos += this.velocity * delta
|
||||
|
||||
if (nextOnGroundUpdate <= world.timer || !onGround) {
|
||||
onGround = sweep.hitNormal.dot(world.gravity.normalized) <= -0.98
|
||||
groundNormal = sweep.hitNormal
|
||||
|
||||
if (!onGround) {
|
||||
val sweepGround = sweepRelative(world.gravity * delta, delta)
|
||||
onGround = sweepGround.hitAnything && sweepGround.hitNormal.dot(world.gravity.normalized) <= -0.98
|
||||
groundNormal = sweepGround.hitNormal
|
||||
/**
|
||||
* Returns whenever are we contacting something below us
|
||||
*/
|
||||
open val onGround: Boolean get() {
|
||||
for (contact in body.contactEdgeIterator) {
|
||||
if (contact.contact.manifold.localNormal.dot(world.gravity.normalized) >= 0.97) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
protected open fun thinkGravity(delta: Double) {
|
||||
velocity += world.gravity * delta
|
||||
}
|
||||
|
||||
protected open fun thinkFriction(delta: Double) {
|
||||
velocity *= Vector2d(lerp(delta, 1.0, 0.01), 1.0)
|
||||
/**
|
||||
* World space AABB, by default returning combined AABB of physical body.
|
||||
*/
|
||||
open val worldAABB: AABB get() {
|
||||
return body.worldSpaceAABB
|
||||
}
|
||||
|
||||
open fun thinkPhysics(delta: Double) {
|
||||
if (!onGround && affectedByGravity)
|
||||
thinkGravity(delta)
|
||||
|
||||
propagateVelocity(delta)
|
||||
|
||||
if (affectedByGravity && onGround)
|
||||
thinkFriction(delta)
|
||||
mutePositionChanged = true
|
||||
position = body.position
|
||||
angle = body.angle
|
||||
mutePositionChanged = false
|
||||
}
|
||||
|
||||
protected open fun onTouchSurface(velocity: Vector2d, normal: Vector2d) {
|
||||
entity.onTouchSurface(velocity, normal)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DROP_TO_FLOOR = Vector2d(y = -1_000.0)
|
||||
protected var mutePositionChanged = false
|
||||
|
||||
open fun notifyPositionChanged() {
|
||||
if (mutePositionChanged) {
|
||||
return
|
||||
}
|
||||
|
||||
body.setTransform(entity.position, entity.angle)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* MovementController который ничего не делает (прям совсем)
|
||||
* Movement controller for logical entities, which does nothing.
|
||||
*/
|
||||
class DummyMovementController(entity: Entity) : MovementController<Entity>(entity) {
|
||||
override val currentAABB = DUMMY_AABB
|
||||
override val affectedByGravity = false
|
||||
class LogicalMovementController(entity: Entity) : MovementController<Entity>(entity) {
|
||||
override val worldAABB: AABB get() = AABB(position, position)
|
||||
|
||||
override fun propagateVelocity(delta: Double) {
|
||||
// Dummies never touch anything, since they don't have Box2D body
|
||||
override val onGround: Boolean = false
|
||||
override val velocity: Vector2d = Vector2d.ZERO
|
||||
|
||||
override fun thinkPhysics(delta: Double) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
override fun thinkGravity(delta: Double) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
override fun thinkFriction(delta: Double) {
|
||||
override fun notifyPositionChanged() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
|
@ -1,19 +1,66 @@
|
||||
package ru.dbotthepony.kstarbound.world.entities
|
||||
|
||||
import ru.dbotthepony.kbox2d.api.FixtureDef
|
||||
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
|
||||
import ru.dbotthepony.kbox2d.dynamics.B2Fixture
|
||||
import ru.dbotthepony.kstarbound.world.World
|
||||
import ru.dbotthepony.kvector.util2d.AABB
|
||||
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
|
||||
|
||||
class PlayerMovementController(entity: PlayerEntity) : WalkableMovementController<PlayerEntity>(entity) {
|
||||
public override var moveDirection = Move.STAND_STILL
|
||||
private var bodyFixture: B2Fixture
|
||||
|
||||
override var isDucked: Boolean = false
|
||||
set(value) {
|
||||
if (value == field)
|
||||
return
|
||||
|
||||
field = value
|
||||
|
||||
bodyFixture.destroy()
|
||||
|
||||
if (value) {
|
||||
bodyFixture = body.createFixture(FixtureDef(
|
||||
shape = DUCKING,
|
||||
friction = 0.4,
|
||||
density = 1.9,
|
||||
))
|
||||
} else {
|
||||
bodyFixture = body.createFixture(FixtureDef(
|
||||
shape = STANDING,
|
||||
friction = 0.4,
|
||||
density = 1.9,
|
||||
))
|
||||
}
|
||||
|
||||
body.isAwake = true
|
||||
}
|
||||
|
||||
override fun canUnDuck(): Boolean {
|
||||
return world.isSpaceEmptyFromTiles(STANDING_AABB + position)
|
||||
}
|
||||
|
||||
init {
|
||||
bodyFixture = body.createFixture(FixtureDef(
|
||||
shape = STANDING,
|
||||
friction = 0.4,
|
||||
density = 1.9,
|
||||
))
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val STANDING = PolygonShape().also { it.setAsBox(0.9, 1.8) }
|
||||
private val STANDING_AABB = STANDING.computeAABB(0)
|
||||
private val DUCKING = PolygonShape().also { it.setAsBox(0.9, 0.9, Vector2d(y = -0.9), 0.0) }
|
||||
private val DUCKING_AABB = DUCKING.computeAABB(0)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Физический аватар игрока в мире
|
||||
*/
|
||||
open class PlayerEntity(world: World<*, *>) : AliveWalkingEntity(world) {
|
||||
override val standingAABB = AABB.rectangle(Vector2d.ZERO, 1.8, 3.7)
|
||||
override val duckingAABB = AABB.rectangle(Vector2d.ZERO, 1.8, 1.8) + Vector2d(y = -0.9)
|
||||
override val movement = PlayerMovementController(this)
|
||||
|
||||
override fun thinkAI(delta: Double) {
|
||||
|
@ -4,7 +4,7 @@ import ru.dbotthepony.kstarbound.defs.projectile.ConfiguredProjectile
|
||||
import ru.dbotthepony.kstarbound.world.World
|
||||
|
||||
class Projectile(world: World<*, *>, val def: ConfiguredProjectile) : Entity(world) {
|
||||
override val movement: MovementController<*> = DummyMovementController(this)
|
||||
override val movement: MovementController<*> = LogicalMovementController(this)
|
||||
|
||||
override fun thinkAI(delta: Double) {
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user