SAT testcode
This commit is contained in:
parent
2280882247
commit
b9975657d4
@ -1,6 +1,8 @@
|
||||
package ru.dbotthepony.kstarbound
|
||||
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import org.lwjgl.Version
|
||||
import org.lwjgl.glfw.GLFW.glfwSetWindowShouldClose
|
||||
@ -13,20 +15,21 @@ import ru.dbotthepony.kstarbound.player.QuestInstance
|
||||
import ru.dbotthepony.kstarbound.util.JVMTimeSource
|
||||
import ru.dbotthepony.kstarbound.world.api.IChunkCell
|
||||
import ru.dbotthepony.kstarbound.world.entities.ItemEntity
|
||||
import ru.dbotthepony.kstarbound.world.entities.PlayerEntity
|
||||
import ru.dbotthepony.kstarbound.io.json.VersionedJson
|
||||
import ru.dbotthepony.kstarbound.io.readVarInt
|
||||
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||
import ru.dbotthepony.kstarbound.world.entities.Move
|
||||
import ru.dbotthepony.kstarbound.world.entities.WorldObject
|
||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||
import ru.dbotthepony.kvector.vector.Vector2d
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.DataInputStream
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.zip.Inflater
|
||||
import java.util.zip.InflaterInputStream
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
|
||||
@ -64,8 +67,6 @@ fun main() {
|
||||
Starbound.terminateLoading = true
|
||||
}
|
||||
|
||||
val ent = PlayerEntity(client.world!!)
|
||||
|
||||
Starbound.onInitialize {
|
||||
//for (chunkX in 17 .. 18) {
|
||||
//for (chunkX in 14 .. 24) {
|
||||
@ -124,10 +125,10 @@ fun main() {
|
||||
for (i in 0 .. 0) {
|
||||
val item = ItemEntity(client.world!!, item.value)
|
||||
|
||||
item.position = Vector2d(7.0 + i, 685.0)
|
||||
item.position = Vector2d(225.0 + i, 685.0)
|
||||
item.spawn()
|
||||
item.velocity = Vector2d(0.01, -0.08)
|
||||
//item.movement.applyVelocity(Vector2d(rand.nextDouble() * 1000.0 - 500.0, rand.nextDouble() * 1000.0 - 500.0))
|
||||
item.movement.applyVelocity(Vector2d(-1.0, 0.0))
|
||||
}
|
||||
|
||||
// println(Starbound.statusEffects["firecharge"])
|
||||
@ -154,21 +155,55 @@ fun main() {
|
||||
}
|
||||
|
||||
//ent.position += Vector2d(y = 14.0, x = -10.0)
|
||||
ent.position = Vector2d(238.0, 685.0)
|
||||
client.camera.pos = Vector2d(238.0, 685.0)
|
||||
//client.camera.pos = Vector2f(0f, 0f)
|
||||
|
||||
client.onDrawGUI {
|
||||
client.font.render("${ent.position}", y = 100f, scale = 0.25f)
|
||||
client.font.render("${ent.movement.velocity}", y = 120f, scale = 0.25f)
|
||||
client.font.render("Camera: ${client.camera.pos} ${client.settings.zoom}", y = 140f, scale = 0.25f)
|
||||
client.font.render("Cursor: ${client.mouseCoordinates} -> ${client.screenToWorld(client.mouseCoordinates)}", y = 160f, scale = 0.25f)
|
||||
client.font.render("World chunk: ${client.world!!.chunkFromCell(client.camera.pos)}", y = 180f, scale = 0.25f)
|
||||
}
|
||||
|
||||
client.onPreDrawWorld {
|
||||
var p = Poly(Starbound.gson.fromJson<List<Vector2d>>("""[ [1.75, 2.55], [2.25, 2.05], [2.75, -3.55], [2.25, -3.95], [-2.25, -3.95], [-2.75, -3.55], [-2.25, 2.05], [-1.75, 2.55] ]""", TypeToken.getParameterized(ImmutableList::class.java, Vector2d::class.java).type))
|
||||
val polies = ArrayList<Poly>()
|
||||
|
||||
val box = Poly(listOf(Vector2d(-0.5, -0.5), Vector2d(-0.5, 0.5), Vector2d(0.5, 0.5), Vector2d(0.5, -0.5)))
|
||||
p = box
|
||||
val triangle = Poly(listOf(Vector2d(-0.5, -0.5), Vector2d(0.5, 0.5), Vector2d(0.5, -0.5)))
|
||||
|
||||
//polies.add(triangle + Vector2d(0.0, -1.5))
|
||||
polies.add(box)
|
||||
polies.add(box + Vector2d(1.0))
|
||||
//polies.add(box + Vector2d(1.0, -1.0))
|
||||
//polies.add(box + Vector2d(1.0, -2.0))
|
||||
//polies.add(box + Vector2d(1.0, -3.0))
|
||||
|
||||
//client.foregroundExecutor.scheduleAtFixedRate({ p.intersect(p2)?.let { p += it; println(it) } }, 1, 1, TimeUnit.SECONDS)
|
||||
|
||||
p += client.camera.pos
|
||||
|
||||
for (i in polies.indices)
|
||||
polies[i] = polies[i] + client.camera.pos
|
||||
|
||||
client.onPostDrawWorld {
|
||||
//client.camera.pos.x = ent.pos.x.toFloat()
|
||||
//client.camera.pos.y = ent.pos.y.toFloat()
|
||||
|
||||
p += client.screenToWorld(client.mouseCoordinates) - p.aabb.centre
|
||||
|
||||
for (i in 0 until 10) {
|
||||
val intersects = ArrayList<Poly.Penetration>()
|
||||
|
||||
polies.forEach { p.intersect(it)?.let { intersects.add(it) } }
|
||||
|
||||
if (intersects.isEmpty())
|
||||
break
|
||||
else
|
||||
p += intersects.max()
|
||||
}
|
||||
|
||||
p.render()
|
||||
polies.forEach { it.render() }
|
||||
}
|
||||
|
||||
client.box2dRenderer.drawShapes = false
|
||||
@ -200,23 +235,6 @@ fun main() {
|
||||
|
||||
//println(client.camera.velocity.toDoubleVector() * client.frameRenderTime * 0.1)
|
||||
|
||||
//if (ent.onGround)
|
||||
//ent.velocity += client.camera.velocity.toDoubleVector() * client.frameRenderTime * 0.1
|
||||
|
||||
if (client.input.KEY_LEFT_DOWN) {
|
||||
ent.movement.moveDirection = Move.MOVE_LEFT
|
||||
} else if (client.input.KEY_RIGHT_DOWN) {
|
||||
ent.movement.moveDirection = Move.MOVE_RIGHT
|
||||
} else {
|
||||
ent.movement.moveDirection = Move.STAND_STILL
|
||||
}
|
||||
|
||||
if (client.input.KEY_SPACE_PRESSED && ent.movement.onGround) {
|
||||
ent.movement.requestJump()
|
||||
} else if (client.input.KEY_SPACE_RELEASED) {
|
||||
ent.movement.recallJump()
|
||||
}
|
||||
|
||||
if (client.input.KEY_ESCAPE_PRESSED) {
|
||||
glfwSetWindowShouldClose(client.window, true)
|
||||
}
|
||||
|
@ -74,7 +74,6 @@ import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.time.Duration
|
||||
import java.util.*
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.ForkJoinPool
|
||||
import java.util.concurrent.ForkJoinWorkerThread
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
@ -326,23 +325,23 @@ class StarboundClient : Closeable {
|
||||
private fun executeQueuedTasks() {
|
||||
foregroundExecutor.executeQueuedTasks()
|
||||
|
||||
var next3 = openglCleanQueue.poll() as CleanRef?
|
||||
var next = openglCleanQueue.poll() as CleanRef?
|
||||
|
||||
while (next3 != null) {
|
||||
while (next != null) {
|
||||
openglObjectsCleaned++
|
||||
next3.fn.accept(next3.value)
|
||||
next.fn.accept(next.value)
|
||||
checkForGLError("Removing unreachable OpenGL object")
|
||||
|
||||
val head = openglCleanQueueHead
|
||||
|
||||
if (next3 === head) {
|
||||
if (next === head) {
|
||||
openglCleanQueueHead = head.next
|
||||
} else {
|
||||
next3.prev?.next = next3.next
|
||||
next3.next?.prev = next3.prev
|
||||
next.prev?.next = next.next
|
||||
next.next?.prev = next.prev
|
||||
}
|
||||
|
||||
next3 = openglCleanQueue.poll() as CleanRef?
|
||||
next = openglCleanQueue.poll() as CleanRef?
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ import org.lwjgl.opengl.GL45.GL_UNSIGNED_INT
|
||||
import org.lwjgl.opengl.GL45.GL_UNSIGNED_SHORT
|
||||
import org.lwjgl.opengl.GL45.GL_UNSIGNED_BYTE
|
||||
import ru.dbotthepony.kstarbound.client.gl.BufferObject
|
||||
import ru.dbotthepony.kvector.api.IStruct2d
|
||||
import ru.dbotthepony.kvector.api.IStruct2f
|
||||
import ru.dbotthepony.kvector.api.IStruct4f
|
||||
import ru.dbotthepony.kvector.arrays.Matrix3f
|
||||
@ -279,12 +280,36 @@ class VertexBuilder(
|
||||
return this
|
||||
}
|
||||
|
||||
fun vertex(value: IStruct2f): VertexBuilder {
|
||||
vertex()
|
||||
position(value.component1(), value.component2())
|
||||
return this
|
||||
}
|
||||
|
||||
fun vertex(value: IStruct2d): VertexBuilder {
|
||||
vertex()
|
||||
position(value.component1().toFloat(), value.component2().toFloat())
|
||||
return this
|
||||
}
|
||||
|
||||
fun vertex(transform: Matrix3f, x: Float, y: Float): VertexBuilder {
|
||||
vertex()
|
||||
position(transform, x, y)
|
||||
return this
|
||||
}
|
||||
|
||||
fun vertex(transform: Matrix3f, value: IStruct2f): VertexBuilder {
|
||||
vertex()
|
||||
position(transform, value)
|
||||
return this
|
||||
}
|
||||
|
||||
fun vertex(transform: Matrix3f, value: IStruct2d): VertexBuilder {
|
||||
vertex()
|
||||
position(transform, value)
|
||||
return this
|
||||
}
|
||||
|
||||
private fun pushFloat(attr: VertexAttributes.Attribute, x: Float): VertexBuilder {
|
||||
vertexMemory.position(vertexCount * attributes.vertexStride + attr.relativeOffset)
|
||||
vertexMemory.putFloat(x)
|
||||
@ -352,6 +377,7 @@ class VertexBuilder(
|
||||
}
|
||||
|
||||
fun position(transform: Matrix3f, value: IStruct2f) = position(transform, value.component1(), value.component2())
|
||||
fun position(transform: Matrix3f, value: IStruct2d) = position(transform, value.component1().toFloat(), value.component2().toFloat())
|
||||
|
||||
fun uv(x: Float, y: Float): VertexBuilder {
|
||||
return pushFloat2(requireNotNull(uv) { "Vertex format does not have texture UV attribute" }, x, y)
|
||||
|
@ -56,8 +56,8 @@ class Box2DRenderer : IDebugDraw {
|
||||
for (i in vertices.indices) {
|
||||
val current = vertices[i]
|
||||
val next = vertices[(i + 1) % vertices.size]
|
||||
lines.builder.vertex(state.stack.last(), current.x.toFloat(), current.y.toFloat()).color(color)
|
||||
lines.builder.vertex(state.stack.last(), next.x.toFloat(), next.y.toFloat()).color(color)
|
||||
lines.builder.vertex(state.stack.last(), current).color(color)
|
||||
lines.builder.vertex(state.stack.last(), next).color(color)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,8 +7,16 @@ import ru.dbotthepony.kvector.arrays.Matrix4fStack
|
||||
|
||||
class ItemRenderer(client: StarboundClient, entity: ItemEntity, chunk: ClientChunk?) : EntityRenderer(client, entity, chunk) {
|
||||
private val def = entity.def
|
||||
private val textures = def.inventoryIcon?.stream()?.map { it.image }?.toList() ?: listOf()
|
||||
|
||||
override fun render(stack: Matrix4fStack) {
|
||||
//client.programs.positionTexture.use()
|
||||
//client.programs.positionTexture.texture0 = 0
|
||||
|
||||
//for (texture in textures) {
|
||||
// client.textures2D[0] = texture.image?.texture ?: continue
|
||||
//}
|
||||
|
||||
entity.hitboxes.forEach { it.render(client) }
|
||||
}
|
||||
}
|
||||
|
@ -29,24 +29,4 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
|
||||
it.liquidIsDirty = true
|
||||
}
|
||||
}
|
||||
|
||||
private val entityRenderers = HashMap<Entity, EntityRenderer>()
|
||||
|
||||
override fun onEntityAdded(entity: Entity) {
|
||||
entityRenderers[entity] = EntityRenderer.getRender(world.client, entity, this)
|
||||
}
|
||||
|
||||
override fun onEntityTransferedToThis(entity: Entity, otherChunk: ClientChunk) {
|
||||
val renderer = otherChunk.entityRenderers[entity] ?: throw IllegalStateException("$otherChunk has no renderer for $entity!")
|
||||
entityRenderers[entity] = renderer
|
||||
renderer.chunk = this
|
||||
}
|
||||
|
||||
override fun onEntityTransferedFromThis(entity: Entity, otherChunk: ClientChunk) {
|
||||
entityRenderers.remove(entity)
|
||||
}
|
||||
|
||||
override fun onEntityRemoved(entity: Entity) {
|
||||
entityRenderers.remove(entity)
|
||||
}
|
||||
}
|
||||
|
@ -311,6 +311,14 @@ class ClientWorld(
|
||||
obj.addLights(client.viewportLighting, client.viewportCellX, client.viewportCellY)
|
||||
}
|
||||
}
|
||||
|
||||
for (ent in entities) {
|
||||
if (ent.position.x.toInt() in client.viewportCellX .. client.viewportCellX + client.viewportCellWidth && ent.position.y.toInt() in client.viewportCellY .. client.viewportCellY + client.viewportCellHeight) {
|
||||
layers.add(RenderLayer.Overlay.point()) {
|
||||
ent.render(client)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun thinkInner() {
|
||||
|
@ -324,10 +324,10 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
||||
|
||||
protected val entities = ReferenceOpenHashSet<Entity>()
|
||||
|
||||
protected abstract fun onEntityAdded(entity: Entity)
|
||||
protected abstract fun onEntityTransferedToThis(entity: Entity, otherChunk: This)
|
||||
protected abstract fun onEntityTransferedFromThis(entity: Entity, otherChunk: This)
|
||||
protected abstract fun onEntityRemoved(entity: Entity)
|
||||
protected open fun onEntityAdded(entity: Entity) { }
|
||||
protected open fun onEntityTransferedToThis(entity: Entity, otherChunk: This) { }
|
||||
protected open fun onEntityTransferedFromThis(entity: Entity, otherChunk: This) { }
|
||||
protected open fun onEntityRemoved(entity: Entity) { }
|
||||
|
||||
fun addEntity(entity: Entity) {
|
||||
if (!entities.add(entity)) {
|
||||
|
@ -4,9 +4,14 @@ import ru.dbotthepony.kstarbound.RegistryObject
|
||||
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||
import ru.dbotthepony.kvector.api.IStruct2i
|
||||
import ru.dbotthepony.kvector.util2d.AABB
|
||||
import ru.dbotthepony.kvector.vector.Vector2d
|
||||
import java.io.DataInputStream
|
||||
|
||||
private val rect = Poly(listOf(Vector2d.ZERO, Vector2d(0.0, 1.0), Vector2d(1.0, 1.0), Vector2d(1.0, 0.0)))
|
||||
|
||||
interface IChunkCell : IStruct2i {
|
||||
/**
|
||||
* absolute (in world)
|
||||
@ -26,6 +31,13 @@ interface IChunkCell : IStruct2i {
|
||||
return y
|
||||
}
|
||||
|
||||
val polies: Collection<Poly> get() {
|
||||
if (foreground.material.isMeta)
|
||||
return emptyList()
|
||||
|
||||
return listOf(rect + Vector2d(this.x.toDouble(), this.y.toDouble()))
|
||||
}
|
||||
|
||||
val foreground: ITileState
|
||||
val background: ITileState
|
||||
val liquid: ILiquidState
|
||||
|
@ -1,353 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.world.entities
|
||||
|
||||
import ru.dbotthepony.kbox2d.api.ContactEdge
|
||||
import ru.dbotthepony.kbox2d.api.FixtureDef
|
||||
import ru.dbotthepony.kbox2d.api.b2_polygonRadius
|
||||
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
|
||||
import ru.dbotthepony.kbox2d.dynamics.B2Fixture
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.world.World
|
||||
import ru.dbotthepony.kvector.util2d.AABB
|
||||
import ru.dbotthepony.kvector.vector.Vector2d
|
||||
import ru.dbotthepony.kvector.vector.times
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
enum class Move {
|
||||
STAND_STILL,
|
||||
MOVE_LEFT,
|
||||
MOVE_RIGHT
|
||||
}
|
||||
|
||||
/**
|
||||
* Базовый абстрактный класс, реализующий сущность, которая ходит по земле
|
||||
*/
|
||||
abstract class WalkableMovementController<T : AliveWalkingEntity>(entity: T) : MovementController<T>(entity) {
|
||||
init {
|
||||
body.isFixedRotation = true
|
||||
}
|
||||
|
||||
protected abstract val moveDirection: Move
|
||||
|
||||
protected var sensorA: B2Fixture? = null
|
||||
protected var sensorB: B2Fixture? = null
|
||||
protected var bodyFixture: B2Fixture? = null
|
||||
|
||||
protected abstract fun recreateBodyFixture()
|
||||
|
||||
protected open fun recreateSensors() {
|
||||
sensorA?.destroy()
|
||||
sensorB?.destroy()
|
||||
|
||||
val bodyFixture = bodyFixture ?: return
|
||||
val aabb = bodyFixture.shape.computeAABB(0)
|
||||
|
||||
val sensorheight = (aabb.height - stepSize) / 2.0
|
||||
|
||||
sensorA = body.createFixture(FixtureDef(
|
||||
shape = PolygonShape().also { it.setAsBox(0.2, sensorheight, Vector2d(-aabb.width / 2.0 - 0.2, aabb.height / 2.0 - sensorheight), 0.0) },
|
||||
isSensor = true,
|
||||
))
|
||||
|
||||
sensorB = body.createFixture(FixtureDef(
|
||||
shape = PolygonShape().also { it.setAsBox(0.2, sensorheight, Vector2d(aabb.width / 2.0 + 0.2, aabb.height / 2.0 - sensorheight), 0.0) },
|
||||
isSensor = true,
|
||||
))
|
||||
}
|
||||
|
||||
var wantsToDuck = false
|
||||
open var isDucked = false
|
||||
protected set
|
||||
|
||||
override fun think() {
|
||||
super.think()
|
||||
thinkMovement()
|
||||
}
|
||||
|
||||
/**
|
||||
* See [IWalkableEntity.topSpeed]
|
||||
*/
|
||||
open val topSpeed by entity::topSpeed
|
||||
|
||||
/**
|
||||
* See [IWalkableEntity.moveSpeed]
|
||||
*/
|
||||
open val moveSpeed by entity::moveSpeed
|
||||
|
||||
/**
|
||||
* See [IWalkableEntity.freeFallMoveSpeed]
|
||||
*/
|
||||
open val freeFallMoveSpeed by entity::freeFallMoveSpeed
|
||||
|
||||
/**
|
||||
* See [IWalkableEntity.brakeForce]
|
||||
*/
|
||||
open val brakeForce by entity::brakeForce
|
||||
|
||||
/**
|
||||
* See [IWalkableEntity.stepSize]
|
||||
*/
|
||||
open val stepSize by entity::stepSize
|
||||
|
||||
/**
|
||||
* See [IWalkableEntity.jumpForce]
|
||||
*/
|
||||
open val jumpForce by entity::jumpForce
|
||||
|
||||
protected var jumpRequested = false
|
||||
protected var nextJump = 0
|
||||
|
||||
open fun requestJump() {
|
||||
if (jumpRequested || nextJump > world.ticks)
|
||||
return
|
||||
|
||||
jumpRequested = true
|
||||
nextJump = world.ticks + 15
|
||||
}
|
||||
|
||||
open fun recallJump() {
|
||||
if (jumpRequested) {
|
||||
jumpRequested = false
|
||||
nextJump = 0
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract fun canUnDuck(): Boolean
|
||||
|
||||
protected var previousVelocity = Vector2d.ZERO
|
||||
|
||||
protected open fun thinkMovement() {
|
||||
if (onGround && !isDucked) {
|
||||
when (moveDirection) {
|
||||
Move.STAND_STILL -> {
|
||||
body.linearVelocity += Vector2d(x = -body.linearVelocity.x * Starbound.TICK_TIME_ADVANCE * brakeForce)
|
||||
}
|
||||
|
||||
Move.MOVE_LEFT -> {
|
||||
if (body.linearVelocity.x > 0.0) {
|
||||
body.linearVelocity += Vector2d(x = -body.linearVelocity.x * Starbound.TICK_TIME_ADVANCE * brakeForce)
|
||||
}
|
||||
|
||||
if (body.linearVelocity.x > -topSpeed) {
|
||||
body.linearVelocity += Vector2d(x = -moveSpeed * Starbound.TICK_TIME_ADVANCE)
|
||||
|
||||
// Ghost collision prevention
|
||||
if (body.linearVelocity.x.absoluteValue < 1) {
|
||||
body.linearVelocity += -Starbound.TICK_TIME_ADVANCE * world.gravity * 2.0
|
||||
}
|
||||
}
|
||||
|
||||
var wantToStepUp = false
|
||||
var foundContact: ContactEdge? = null
|
||||
|
||||
for (contact in body.contactEdgeIterator) {
|
||||
if (contact.contact.manifold.localNormal.dot(Vector2d.NEGATIVE_X) >= 0.95) {
|
||||
// we hit something to our left
|
||||
wantToStepUp = true
|
||||
foundContact = contact
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (wantToStepUp) {
|
||||
// make sure something we hit is actually a staircase of sort, and not geometry edges
|
||||
val aabbFound: AABB
|
||||
|
||||
if (foundContact!!.contact.fixtureB == sensorA) {
|
||||
aabbFound = foundContact.contact.fixtureA.getAABB(foundContact.contact.childIndexA)
|
||||
} else {
|
||||
aabbFound = foundContact.contact.fixtureB.getAABB(foundContact.contact.childIndexB)
|
||||
}
|
||||
|
||||
if (aabbFound.maxs.y - b2_polygonRadius * 2.0 <= body.worldSpaceAABB.mins.y) {
|
||||
// just a crack on sidewalk
|
||||
wantToStepUp = false
|
||||
}
|
||||
}
|
||||
|
||||
if (wantToStepUp) {
|
||||
var stepHeightClear = true
|
||||
|
||||
for (contact in body.contactEdgeIterator) {
|
||||
if (contact.contact.fixtureA == sensorA || contact.contact.fixtureB == sensorA) {
|
||||
if (contact.contact.isTouching) {
|
||||
stepHeightClear = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (stepHeightClear) {
|
||||
val velocity = if (previousVelocity.length > body.linearVelocity.length) previousVelocity else body.linearVelocity
|
||||
body.setTransform(body.position + Vector2d(x = -0.05, y = stepSize), body.angle)
|
||||
body.linearVelocity = velocity
|
||||
//body.linearVelocity += Vector2d(y = -Starbound.SECONDS_IN_FRAME * 18.0 * stepSize * world.gravity.y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Move.MOVE_RIGHT -> {
|
||||
if (body.linearVelocity.x < 0.0) {
|
||||
body.linearVelocity += Vector2d(x = -body.linearVelocity.x * Starbound.TICK_TIME_ADVANCE * brakeForce)
|
||||
}
|
||||
|
||||
if (body.linearVelocity.x < topSpeed) {
|
||||
body.linearVelocity += Vector2d(x = moveSpeed * Starbound.TICK_TIME_ADVANCE)
|
||||
|
||||
// Ghost collision prevention
|
||||
if (body.linearVelocity.x.absoluteValue < 1) {
|
||||
body.linearVelocity += -Starbound.TICK_TIME_ADVANCE * world.gravity * 2.0
|
||||
}
|
||||
}
|
||||
|
||||
var wantToStepUp = false
|
||||
var foundContact: ContactEdge? = null
|
||||
|
||||
for (contact in body.contactEdgeIterator) {
|
||||
if (contact.contact.manifold.localNormal.dot(Vector2d.POSITIVE_X) >= 0.95) {
|
||||
// we hit something to our right
|
||||
wantToStepUp = true
|
||||
foundContact = contact
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (wantToStepUp) {
|
||||
// make sure something we hit is actually a staircase of sort, and not geometry edges
|
||||
val aabbFound: AABB
|
||||
|
||||
if (foundContact!!.contact.fixtureB == sensorB) {
|
||||
aabbFound = foundContact.contact.fixtureA.getAABB(foundContact.contact.childIndexA)
|
||||
} else {
|
||||
aabbFound = foundContact.contact.fixtureB.getAABB(foundContact.contact.childIndexB)
|
||||
}
|
||||
|
||||
if (aabbFound.maxs.y - b2_polygonRadius * 2.0 <= body.worldSpaceAABB.mins.y) {
|
||||
// just a crack on sidewalk
|
||||
wantToStepUp = false
|
||||
}
|
||||
}
|
||||
|
||||
if (wantToStepUp) {
|
||||
var stepHeightClear = true
|
||||
|
||||
for (contact in body.contactEdgeIterator) {
|
||||
if (contact.contact.fixtureA == sensorB || contact.contact.fixtureB == sensorB) {
|
||||
if (contact.contact.isTouching) {
|
||||
stepHeightClear = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (stepHeightClear) {
|
||||
val velocity = if (previousVelocity.length > body.linearVelocity.length) previousVelocity else body.linearVelocity
|
||||
body.setTransform(body.position + Vector2d(x = 0.05, y = stepSize), body.angle)
|
||||
body.linearVelocity = velocity
|
||||
//body.linearVelocity += Vector2d(y = -Starbound.SECONDS_IN_FRAME * 18.0 * stepSize * world.gravity.y)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
previousVelocity = body.linearVelocity
|
||||
|
||||
if (jumpRequested) {
|
||||
jumpRequested = false
|
||||
nextJump = world.ticks + 15
|
||||
|
||||
body.linearVelocity += Vector2d(y = jumpForce)
|
||||
}
|
||||
} else if (!onGround && !isDucked && freeFallMoveSpeed != 0.0) {
|
||||
when (moveDirection) {
|
||||
Move.STAND_STILL -> {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
Move.MOVE_LEFT -> {
|
||||
body.linearVelocity += Vector2d(x = -freeFallMoveSpeed * Starbound.TICK_TIME_ADVANCE)
|
||||
}
|
||||
|
||||
Move.MOVE_RIGHT -> {
|
||||
body.linearVelocity += Vector2d(x = freeFallMoveSpeed * Starbound.TICK_TIME_ADVANCE)
|
||||
}
|
||||
}
|
||||
} else if (onGround && isDucked) {
|
||||
body.linearVelocity += Vector2d(x = -body.linearVelocity.x * Starbound.TICK_TIME_ADVANCE * brakeForce)
|
||||
}
|
||||
|
||||
if (wantsToDuck && onGround) {
|
||||
isDucked = true
|
||||
recreateBodyFixture()
|
||||
recreateSensors()
|
||||
body.isAwake = true
|
||||
} else if (isDucked) {
|
||||
if (canUnDuck()) {
|
||||
isDucked = false
|
||||
recreateBodyFixture()
|
||||
recreateSensors()
|
||||
body.isAwake = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AliveEntity(world: World<*, *>) : Entity(world) {
|
||||
open var maxHealth = 10.0
|
||||
open var health = 10.0
|
||||
}
|
||||
|
||||
abstract class AliveWalkingEntity(world: World<*, *>) : AliveEntity(world) {
|
||||
abstract override val movement: WalkableMovementController<*>
|
||||
|
||||
/**
|
||||
* Максимальная скорость передвижения этого AliveMovementController в Starbound Units/секунда
|
||||
*
|
||||
* Скорость передвижения: Это скорость вдоль земли (или в воздухе, если парит) при ходьбе.
|
||||
*
|
||||
* Если вектор скорости вдоль поверхности (или в воздухе, если парит) больше заданного значения,
|
||||
* то сущность быстро тормозит (учитывая силу трения)
|
||||
*/
|
||||
open val topSpeed: Double = 20.0
|
||||
|
||||
/**
|
||||
* Скорость ускорения сущности в Starbound Units/секунда^2
|
||||
*
|
||||
* Если сущность хочет двигаться вправо или влево,
|
||||
* то она разгоняется с данной скоростью.
|
||||
*/
|
||||
open val moveSpeed: Double = 64.0
|
||||
|
||||
/**
|
||||
* То, как сущность может влиять на свою скорость в Starbound Units/секунда^2
|
||||
* когда находится в свободном падении или в состоянии невесомости.
|
||||
*
|
||||
* Позволяет в т.ч. игрокам изменять свою траекторию полёта в стиле Quake.
|
||||
*/
|
||||
open val freeFallMoveSpeed: Double = 8.0
|
||||
|
||||
/**
|
||||
* "Сила", с которой сущность останавливается, если не хочет двигаться.
|
||||
*
|
||||
* Зависит от текущего трения, так как технически является множителем трения поверхности,
|
||||
* на которой стоит сущность.
|
||||
*/
|
||||
open val brakeForce: Double = 16.0
|
||||
|
||||
/**
|
||||
* Высота шага данной сущности. Данное значение отвечает за то, на сколько блоков
|
||||
* сможет подняться сущность просто двигаясь в одном направлении без необходимости прыгнуть.
|
||||
*/
|
||||
open val jumpForce: Double = 20.0
|
||||
|
||||
/**
|
||||
* Импульс прыжка данной сущности. Если сущность парит, то данное значение не несёт никакой
|
||||
* полезной нагрузки.
|
||||
*/
|
||||
open val stepSize: Double = 1.1
|
||||
|
||||
open var wantsToDuck
|
||||
get() = movement.wantsToDuck
|
||||
set(value) { movement.wantsToDuck = value }
|
||||
|
||||
open val isDucked get() = movement.isDucked
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
package ru.dbotthepony.kstarbound.world.entities
|
||||
|
||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||
import ru.dbotthepony.kstarbound.world.Chunk
|
||||
import ru.dbotthepony.kstarbound.world.World
|
||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||
import ru.dbotthepony.kvector.vector.Vector2d
|
||||
|
||||
abstract class Entity(val world: World<*, *>) {
|
||||
@ -56,8 +58,6 @@ abstract class Entity(val world: World<*, *>) {
|
||||
val old = field
|
||||
field = Vector2d(world.x.cell(value.x), world.y.cell(value.y))
|
||||
|
||||
movement.notifyPositionChanged()
|
||||
|
||||
if (isSpawned && !isRemoved) {
|
||||
val oldChunkPos = world.chunkFromCell(old)
|
||||
val newChunkPos = world.chunkFromCell(field)
|
||||
@ -85,9 +85,10 @@ abstract class Entity(val world: World<*, *>) {
|
||||
return
|
||||
|
||||
field = value
|
||||
movement.notifyPositionChanged()
|
||||
}
|
||||
|
||||
var velocity = Vector2d.ZERO
|
||||
val hitboxes = ArrayList<Poly>()
|
||||
|
||||
/**
|
||||
* Whenever is this entity spawned in world ([spawn] called).
|
||||
@ -113,8 +114,6 @@ abstract class Entity(val world: World<*, *>) {
|
||||
if (chunk == null) {
|
||||
world.orphanedEntities.add(this)
|
||||
}
|
||||
|
||||
movement.onSpawnedInWorld()
|
||||
}
|
||||
|
||||
open fun remove() {
|
||||
@ -127,24 +126,8 @@ abstract class Entity(val world: World<*, *>) {
|
||||
world.entities.remove(this)
|
||||
chunk?.removeEntity(this)
|
||||
}
|
||||
|
||||
movement.destroy()
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
abstract val movement: MovementController<*>
|
||||
|
||||
/**
|
||||
* Внутренний блок "раздумья" сущности, вызывается на каждом тике мира
|
||||
*/
|
||||
protected abstract fun thinkAI()
|
||||
|
||||
/**
|
||||
* Заставляет сущность "думать".
|
||||
*/
|
||||
@ -153,11 +136,57 @@ abstract class Entity(val world: World<*, *>) {
|
||||
throw IllegalStateException("Tried to think before spawning in world")
|
||||
}
|
||||
|
||||
movement.think()
|
||||
thinkAI()
|
||||
move()
|
||||
thinkInner()
|
||||
}
|
||||
|
||||
protected abstract fun thinkInner()
|
||||
|
||||
protected open fun move() {
|
||||
position += velocity
|
||||
|
||||
val polies = ArrayList<Poly>()
|
||||
|
||||
for (x in -1 .. 1) {
|
||||
for (y in -1 .. 1) {
|
||||
world.chunkMap.getCell(position.x.toInt() + x, position.y.toInt() + y)?.let {
|
||||
polies.addAll(it.polies)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i in 0 until 10) {
|
||||
val intersects = ArrayList<Poly.Penetration>()
|
||||
|
||||
this.hitboxes.forEach { hitbox0 ->
|
||||
val hitbox = hitbox0 + position
|
||||
polies.forEach { poly -> hitbox.intersect(poly)?.let { intersects.add(it) } }
|
||||
}
|
||||
|
||||
if (intersects.isEmpty())
|
||||
break
|
||||
else {
|
||||
val max = intersects.max()
|
||||
// resolve collision
|
||||
position += max.vector
|
||||
// collision response
|
||||
velocity -= max.axis * velocity.dot(max.axis * 1.1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open fun onTouchSurface(velocity: Vector2d, normal: Vector2d) {
|
||||
|
||||
}
|
||||
|
||||
open fun render(client: StarboundClient = StarboundClient.current()) {
|
||||
hitboxes.forEach { (it + position).render(client) }
|
||||
}
|
||||
|
||||
open var maxHealth = 0.0
|
||||
open var health = 0.0
|
||||
|
||||
open fun hurt(amount: Double): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -5,49 +5,19 @@ import ru.dbotthepony.kbox2d.api.FixtureDef
|
||||
import ru.dbotthepony.kbox2d.api.Manifold
|
||||
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
|
||||
import ru.dbotthepony.kbox2d.dynamics.contact.AbstractContact
|
||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||
import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition
|
||||
import ru.dbotthepony.kstarbound.world.World
|
||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||
import ru.dbotthepony.kvector.util2d.AABB
|
||||
import ru.dbotthepony.kvector.vector.Vector2d
|
||||
|
||||
class ItemEntity(world: World<*, *>, val def: IItemDefinition) : Entity(world) {
|
||||
override val movement = object : MovementController<ItemEntity>(this) {
|
||||
override fun beginContact(contact: AbstractContact) {
|
||||
// тут надо код подбора предмета игроком, если мы начинаем коллизию с окружностью подбора
|
||||
}
|
||||
|
||||
override fun endContact(contact: AbstractContact) {
|
||||
|
||||
}
|
||||
|
||||
override fun postSolve(contact: AbstractContact, impulse: ContactImpulse) {
|
||||
|
||||
}
|
||||
|
||||
override fun preSolve(contact: AbstractContact, oldManifold: Manifold) {
|
||||
if (contact.fixtureA.userData is ItemEntity && contact.fixtureB.userData is ItemEntity)
|
||||
contact.isEnabled = false
|
||||
}
|
||||
|
||||
override fun onSpawnedInWorld() {
|
||||
super.onSpawnedInWorld()
|
||||
|
||||
// все предметы в мире являются коробками
|
||||
val fixture = FixtureDef(
|
||||
shape = PolygonShape().also { it.setAsBox(0.75, 0.75) },
|
||||
restitution = 0.0,
|
||||
friction = 1.0,
|
||||
density = 0.3,
|
||||
)
|
||||
|
||||
fixture.userData = this@ItemEntity
|
||||
|
||||
body.createFixture(fixture)
|
||||
|
||||
// предметы не могут поворачиваться в мире, всегда падают плашмя
|
||||
body.isFixedRotation = true
|
||||
}
|
||||
init {
|
||||
hitboxes.add(Poly(AABB.rectangle(Vector2d.ZERO, 0.75, 0.75)))
|
||||
}
|
||||
|
||||
override fun thinkAI() {
|
||||
override fun thinkInner() {
|
||||
// TODO: деспавнинг?
|
||||
// просто, как бы, предметы не должны уж так сильно нагружать процессор
|
||||
}
|
||||
|
@ -1,155 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.world.entities
|
||||
|
||||
import ru.dbotthepony.kbox2d.api.*
|
||||
import ru.dbotthepony.kbox2d.dynamics.contact.AbstractContact
|
||||
import ru.dbotthepony.kvector.util2d.AABB
|
||||
import ru.dbotthepony.kvector.vector.Vector2d
|
||||
|
||||
enum class CollisionResolution {
|
||||
STOP,
|
||||
BOUNCE,
|
||||
PUSH,
|
||||
SLIDE,
|
||||
}
|
||||
|
||||
abstract class MovementController<T : Entity>(val entity: T) : IContactListener {
|
||||
val world = entity.world
|
||||
open var position by entity::position
|
||||
open var angle by entity::angle
|
||||
|
||||
/**
|
||||
* Уничтожает данный movement controller.
|
||||
*
|
||||
* Вызывается изнутри [Entity.remove]
|
||||
*/
|
||||
open fun destroy() {
|
||||
body.world.destroyBody(body)
|
||||
}
|
||||
|
||||
private val bodyInit = lazy {
|
||||
world.physics.createBody(BodyDef(
|
||||
position = position,
|
||||
angle = angle,
|
||||
type = BodyType.DYNAMIC,
|
||||
userData = this
|
||||
))
|
||||
}
|
||||
|
||||
/**
|
||||
* Было ли создано физическое тело
|
||||
*/
|
||||
val bodyInitialized: Boolean
|
||||
get() = bodyInit.isInitialized()
|
||||
|
||||
/**
|
||||
* Физическое тело данного контроллера перемещения
|
||||
*
|
||||
* Не создаётся в мире пока к нему не обратятся
|
||||
*/
|
||||
protected val body by bodyInit
|
||||
|
||||
/**
|
||||
* Вызывается изнутри [Entity], когда оно спавнится в самом мире.
|
||||
*
|
||||
* Так как никто не запрещает нам создавать физические тела в физическом мире
|
||||
* до того, как мы появимся в самом мире, это негативно сказывается на производительности И не является корректным
|
||||
* поведением.
|
||||
*
|
||||
* Поэтому, прикреплять фигуры к физическому телу лучше всего из данной функции.
|
||||
*/
|
||||
open fun onSpawnedInWorld() {
|
||||
|
||||
}
|
||||
|
||||
open val velocity get() = body.linearVelocity
|
||||
|
||||
open fun applyVelocity(velocity: Vector2d) {
|
||||
if (velocity != Vector2d.ZERO) {
|
||||
body.applyLinearImpulseToCenter(velocity)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет, находится ли что-либо под нами
|
||||
*/
|
||||
open val onGround: Boolean get() {
|
||||
for (contact in body.contactEdgeIterator) {
|
||||
if (contact.contact.manifold.localNormal.dot(world.gravity.unitVector) >= 0.97) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* World space AABB, by default returning combined AABB of physical body.
|
||||
*/
|
||||
open val worldAABB: AABB get() {
|
||||
return body.worldSpaceAABB
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called on each world step to update variables and account of changes of
|
||||
* physics world and this physics body.
|
||||
*/
|
||||
open fun think() {
|
||||
mutePositionChanged = true
|
||||
position = body.position
|
||||
angle = body.angle
|
||||
mutePositionChanged = false
|
||||
}
|
||||
|
||||
protected open fun onTouchSurface(velocity: Vector2d, normal: Vector2d) {
|
||||
entity.onTouchSurface(velocity, normal)
|
||||
}
|
||||
|
||||
protected var mutePositionChanged = false
|
||||
|
||||
open fun notifyPositionChanged() {
|
||||
if (mutePositionChanged) {
|
||||
return
|
||||
}
|
||||
|
||||
body.setTransform(entity.position, entity.angle)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Movement controller for logical entities, which does nothing.
|
||||
*/
|
||||
class LogicalMovementController(entity: Entity) : MovementController<Entity>(entity) {
|
||||
override val worldAABB: AABB get() = AABB(position, position)
|
||||
|
||||
// Dummies never touch anything, since they don't have Box2D body
|
||||
override val onGround: Boolean = false
|
||||
override val velocity: Vector2d = Vector2d.ZERO
|
||||
|
||||
override fun think() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
override fun notifyPositionChanged() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
override fun beginContact(contact: AbstractContact) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
override fun endContact(contact: AbstractContact) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
override fun preSolve(contact: AbstractContact, oldManifold: Manifold) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
override fun postSolve(contact: AbstractContact, impulse: ContactImpulse) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DUMMY_AABB = AABB.rectangle(Vector2d.ZERO, 0.1, 0.1)
|
||||
}
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.world.entities
|
||||
|
||||
import ru.dbotthepony.kbox2d.api.ContactImpulse
|
||||
import ru.dbotthepony.kbox2d.api.FixtureDef
|
||||
import ru.dbotthepony.kbox2d.api.Manifold
|
||||
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
|
||||
import ru.dbotthepony.kbox2d.dynamics.contact.AbstractContact
|
||||
import ru.dbotthepony.kstarbound.world.World
|
||||
import ru.dbotthepony.kvector.vector.Vector2d
|
||||
|
||||
class PlayerMovementController(entity: PlayerEntity) : WalkableMovementController<PlayerEntity>(entity) {
|
||||
public override var moveDirection = Move.STAND_STILL
|
||||
|
||||
override fun recreateBodyFixture() {
|
||||
bodyFixture?.destroy()
|
||||
|
||||
if (isDucked) {
|
||||
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,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
recreateBodyFixture()
|
||||
recreateSensors()
|
||||
}
|
||||
|
||||
override fun beginContact(contact: AbstractContact) {
|
||||
|
||||
}
|
||||
|
||||
override fun endContact(contact: AbstractContact) {
|
||||
|
||||
}
|
||||
|
||||
override fun preSolve(contact: AbstractContact, oldManifold: Manifold) {
|
||||
|
||||
}
|
||||
|
||||
override fun postSolve(contact: AbstractContact, impulse: ContactImpulse) {
|
||||
|
||||
}
|
||||
|
||||
override fun canUnDuck(): Boolean {
|
||||
return !world.testSpace(STANDING_AABB + position)
|
||||
}
|
||||
|
||||
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 movement = PlayerMovementController(this)
|
||||
|
||||
override fun thinkAI() {
|
||||
|
||||
}
|
||||
}
|
236
src/main/kotlin/ru/dbotthepony/kstarbound/world/physics/Poly.kt
Normal file
236
src/main/kotlin/ru/dbotthepony/kstarbound/world/physics/Poly.kt
Normal file
@ -0,0 +1,236 @@
|
||||
package ru.dbotthepony.kstarbound.world.physics
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||
import org.lwjgl.opengl.GL11.GL_LINES
|
||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||
import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType
|
||||
import ru.dbotthepony.kvector.api.IStruct2d
|
||||
import ru.dbotthepony.kvector.arrays.Matrix3f
|
||||
import ru.dbotthepony.kvector.util2d.AABB
|
||||
import ru.dbotthepony.kvector.util2d.intersectSegments
|
||||
import ru.dbotthepony.kvector.vector.RGBAColor
|
||||
import ru.dbotthepony.kvector.vector.Vector2d
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
private fun calculateEdges(points: List<Vector2d>): ImmutableList<Poly.Edge> {
|
||||
require(points.size >= 3) { "Provided poly is invalid (only ${points.size} points are defined)" }
|
||||
|
||||
val edges = ImmutableList.Builder<Poly.Edge>()
|
||||
|
||||
for (i in points.indices) {
|
||||
val p0 = points[i]
|
||||
val p1 = points[(i + 1) % points.size]
|
||||
|
||||
val diff = (p1 - p0).unitVector
|
||||
val normal = Vector2d(-diff.y, diff.x)
|
||||
|
||||
edges.add(Poly.Edge(p0, p1, normal))
|
||||
}
|
||||
|
||||
return edges.build()
|
||||
}
|
||||
|
||||
/**
|
||||
* edges are built in clockwise winding
|
||||
*
|
||||
* If poly shape is not convex behavior of SAT is undefined
|
||||
*/
|
||||
class Poly private constructor(val edges: ImmutableList<Edge>, val vertices: ImmutableList<Vector2d>) {
|
||||
constructor(points: List<Vector2d>) : this(calculateEdges(points), ImmutableList.copyOf(points))
|
||||
constructor(aabb: AABB) : this(listOf(aabb.bottomLeft, aabb.topLeft, aabb.topRight, aabb.bottomRight))
|
||||
|
||||
val aabb = AABB(
|
||||
Vector2d(vertices.minOf { it.x }, vertices.minOf { it.y }),
|
||||
Vector2d(vertices.maxOf { it.x }, vertices.maxOf { it.y }),
|
||||
)
|
||||
|
||||
data class Edge(val p0: Vector2d, val p1: Vector2d, val normal: Vector2d) {
|
||||
operator fun plus(other: IStruct2d): Edge {
|
||||
return Edge(p0 + other, p1 + other, normal)
|
||||
}
|
||||
|
||||
operator fun minus(other: IStruct2d): Edge {
|
||||
return Edge(p0 - other, p1 - other, normal)
|
||||
}
|
||||
|
||||
operator fun times(other: IStruct2d): Edge {
|
||||
return Edge(p0 * other, p1 * other, (normal * other).unitVector)
|
||||
}
|
||||
|
||||
operator fun times(other: Double): Edge {
|
||||
return Edge(p0 * other, p1 * other, (normal * other).unitVector)
|
||||
}
|
||||
}
|
||||
|
||||
data class Penetration(val axis: Vector2d, val penetration: Double) : Comparable<Penetration> {
|
||||
val vector = axis * penetration
|
||||
|
||||
override fun compareTo(other: Penetration): Int {
|
||||
return penetration.absoluteValue.compareTo(other.penetration.absoluteValue)
|
||||
}
|
||||
}
|
||||
|
||||
operator fun plus(value: Penetration): Poly {
|
||||
val vertices = ImmutableList.Builder<Vector2d>()
|
||||
val edges = ImmutableList.Builder<Edge>()
|
||||
|
||||
for (v in this.vertices) vertices.add(v + value.vector)
|
||||
for (v in this.edges) edges.add(v + value.vector)
|
||||
|
||||
return Poly(edges.build(), vertices.build())
|
||||
}
|
||||
|
||||
operator fun plus(value: IStruct2d): Poly {
|
||||
val vertices = ImmutableList.Builder<Vector2d>()
|
||||
val edges = ImmutableList.Builder<Edge>()
|
||||
|
||||
for (v in this.vertices) vertices.add(v + value)
|
||||
for (v in this.edges) edges.add(v + value)
|
||||
|
||||
return Poly(edges.build(), vertices.build())
|
||||
}
|
||||
|
||||
operator fun minus(value: IStruct2d): Poly {
|
||||
val vertices = ImmutableList.Builder<Vector2d>()
|
||||
val edges = ImmutableList.Builder<Edge>()
|
||||
|
||||
for (v in this.vertices) vertices.add(v - value)
|
||||
for (v in this.edges) edges.add(v - value)
|
||||
|
||||
return Poly(edges.build(), vertices.build())
|
||||
}
|
||||
|
||||
operator fun times(value: IStruct2d): Poly {
|
||||
val vertices = ImmutableList.Builder<Vector2d>()
|
||||
val edges = ImmutableList.Builder<Edge>()
|
||||
|
||||
for (v in this.vertices) vertices.add(v * value)
|
||||
for (v in this.edges) edges.add(v * value)
|
||||
|
||||
return Poly(edges.build(), vertices.build())
|
||||
}
|
||||
|
||||
operator fun times(value: Double): Poly {
|
||||
val vertices = ImmutableList.Builder<Vector2d>()
|
||||
val edges = ImmutableList.Builder<Edge>()
|
||||
|
||||
for (v in this.vertices) vertices.add(v * value)
|
||||
for (v in this.edges) edges.add(v * value)
|
||||
|
||||
return Poly(edges.build(), vertices.build())
|
||||
}
|
||||
|
||||
// min / max
|
||||
fun project(normal: Vector2d): IStruct2d {
|
||||
var min = vertices.first().dot(normal)
|
||||
var max = min
|
||||
|
||||
for (vertex in vertices) {
|
||||
val dist = vertex.dot(normal)
|
||||
|
||||
min = min.coerceAtMost(dist)
|
||||
max = max.coerceAtLeast(dist)
|
||||
}
|
||||
|
||||
return Vector2d(min, max)
|
||||
}
|
||||
|
||||
fun intersect(other: Poly): Penetration? {
|
||||
if (!aabb.intersectWeak(other.aabb))
|
||||
return null
|
||||
|
||||
val normals = ObjectOpenHashSet<Vector2d>()
|
||||
edges.forEach { normals.add(it.normal) }
|
||||
other.edges.forEach { normals.add(it.normal) }
|
||||
|
||||
val intersections = ArrayList<Penetration>()
|
||||
|
||||
for (normal in normals) {
|
||||
val projectThis = project(normal)
|
||||
val projectOther = other.project(normal)
|
||||
|
||||
if (!intersectSegments(projectThis.component1(), projectThis.component2(), projectOther.component1(), projectOther.component2())) {
|
||||
return null
|
||||
} else {
|
||||
val width = projectThis.component2() - projectThis.component1()
|
||||
|
||||
if (
|
||||
projectOther.component1() in projectThis.component1() .. projectThis.component2() &&
|
||||
projectOther.component2() in projectThis.component1() .. projectThis.component2()
|
||||
) {
|
||||
// other inside this
|
||||
val minMin = projectThis.component1() - projectOther.component1()
|
||||
val maxMax = projectThis.component2() - projectOther.component2()
|
||||
|
||||
if (minMin <= maxMax) {
|
||||
// push to left
|
||||
intersections.add(Penetration(normal, -minMin - width))
|
||||
} else {
|
||||
// push to right
|
||||
intersections.add(Penetration(normal, maxMax + width))
|
||||
}
|
||||
} else if (
|
||||
projectThis.component1() in projectOther.component1() .. projectOther.component2() &&
|
||||
projectThis.component2() in projectOther.component1() .. projectOther.component2()
|
||||
) {
|
||||
// this inside other
|
||||
val minMin = projectThis.component1() - projectOther.component1()
|
||||
val maxMax = projectOther.component2() - projectThis.component2()
|
||||
|
||||
if (minMin <= maxMax) {
|
||||
// push to left
|
||||
intersections.add(Penetration(normal, -minMin - width))
|
||||
} else {
|
||||
// push to right
|
||||
intersections.add(Penetration(normal, maxMax + width))
|
||||
}
|
||||
} else if (projectOther.component1() in projectThis.component1() .. projectThis.component2()) {
|
||||
// other's min point is within this
|
||||
intersections.add(Penetration(normal, projectOther.component1() - projectThis.component2()))
|
||||
} else {
|
||||
// other's max point in within this
|
||||
intersections.add(Penetration(normal, projectOther.component2() - projectThis.component1()))
|
||||
}
|
||||
|
||||
if (intersections.last().penetration == 0.0) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (intersections.isEmpty())
|
||||
return null
|
||||
|
||||
return intersections.min()
|
||||
}
|
||||
|
||||
fun render(client: StarboundClient = StarboundClient.current(), color: RGBAColor = RGBAColor.LIGHT_GREEN) {
|
||||
val program = client.programs.position
|
||||
val lines = program.builder.builder
|
||||
program.use()
|
||||
lines.begin(GeometryType.LINES)
|
||||
|
||||
for (edge in edges) {
|
||||
val current = edge.p0
|
||||
val next = edge.p1
|
||||
lines.vertex(client.stack.last(), current)
|
||||
lines.vertex(client.stack.last(), next)
|
||||
lines.vertex(client.stack.last(), (next + current) / 2.0)
|
||||
lines.vertex(client.stack.last(), (next + current) / 2.0 + edge.normal * 3.0)
|
||||
}
|
||||
|
||||
program.modelMatrix = identity
|
||||
program.colorMultiplier = color
|
||||
program.builder.upload()
|
||||
program.builder.draw(GL_LINES)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "Poly[edges = $edges]"
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val identity = Matrix3f.identity()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user