SAT testcode
This commit is contained in:
parent
2280882247
commit
b9975657d4
@ -1,6 +1,8 @@
|
|||||||
package ru.dbotthepony.kstarbound
|
package ru.dbotthepony.kstarbound
|
||||||
|
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import org.lwjgl.Version
|
import org.lwjgl.Version
|
||||||
import org.lwjgl.glfw.GLFW.glfwSetWindowShouldClose
|
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.util.JVMTimeSource
|
||||||
import ru.dbotthepony.kstarbound.world.api.IChunkCell
|
import ru.dbotthepony.kstarbound.world.api.IChunkCell
|
||||||
import ru.dbotthepony.kstarbound.world.entities.ItemEntity
|
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.json.VersionedJson
|
||||||
import ru.dbotthepony.kstarbound.io.readVarInt
|
import ru.dbotthepony.kstarbound.io.readVarInt
|
||||||
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
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.entities.WorldObject
|
||||||
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
import ru.dbotthepony.kvector.vector.Vector2d
|
import ru.dbotthepony.kvector.vector.Vector2d
|
||||||
import java.io.BufferedInputStream
|
import java.io.BufferedInputStream
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.DataInputStream
|
import java.io.DataInputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.zip.Inflater
|
import java.util.zip.Inflater
|
||||||
import java.util.zip.InflaterInputStream
|
import java.util.zip.InflaterInputStream
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
private val LOGGER = LogManager.getLogger()
|
private val LOGGER = LogManager.getLogger()
|
||||||
|
|
||||||
@ -64,8 +67,6 @@ fun main() {
|
|||||||
Starbound.terminateLoading = true
|
Starbound.terminateLoading = true
|
||||||
}
|
}
|
||||||
|
|
||||||
val ent = PlayerEntity(client.world!!)
|
|
||||||
|
|
||||||
Starbound.onInitialize {
|
Starbound.onInitialize {
|
||||||
//for (chunkX in 17 .. 18) {
|
//for (chunkX in 17 .. 18) {
|
||||||
//for (chunkX in 14 .. 24) {
|
//for (chunkX in 14 .. 24) {
|
||||||
@ -124,10 +125,10 @@ fun main() {
|
|||||||
for (i in 0 .. 0) {
|
for (i in 0 .. 0) {
|
||||||
val item = ItemEntity(client.world!!, item.value)
|
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.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(rand.nextDouble() * 1000.0 - 500.0, rand.nextDouble() * 1000.0 - 500.0))
|
||||||
item.movement.applyVelocity(Vector2d(-1.0, 0.0))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// println(Starbound.statusEffects["firecharge"])
|
// println(Starbound.statusEffects["firecharge"])
|
||||||
@ -154,21 +155,55 @@ fun main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//ent.position += Vector2d(y = 14.0, x = -10.0)
|
//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 = Vector2d(238.0, 685.0)
|
||||||
//client.camera.pos = Vector2f(0f, 0f)
|
//client.camera.pos = Vector2f(0f, 0f)
|
||||||
|
|
||||||
client.onDrawGUI {
|
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("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("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.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.x = ent.pos.x.toFloat()
|
||||||
//client.camera.pos.y = ent.pos.y.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
|
client.box2dRenderer.drawShapes = false
|
||||||
@ -200,23 +235,6 @@ fun main() {
|
|||||||
|
|
||||||
//println(client.camera.velocity.toDoubleVector() * client.frameRenderTime * 0.1)
|
//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) {
|
if (client.input.KEY_ESCAPE_PRESSED) {
|
||||||
glfwSetWindowShouldClose(client.window, true)
|
glfwSetWindowShouldClose(client.window, true)
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,6 @@ import java.nio.ByteBuffer
|
|||||||
import java.nio.ByteOrder
|
import java.nio.ByteOrder
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.CompletableFuture
|
|
||||||
import java.util.concurrent.ForkJoinPool
|
import java.util.concurrent.ForkJoinPool
|
||||||
import java.util.concurrent.ForkJoinWorkerThread
|
import java.util.concurrent.ForkJoinWorkerThread
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
@ -326,23 +325,23 @@ class StarboundClient : Closeable {
|
|||||||
private fun executeQueuedTasks() {
|
private fun executeQueuedTasks() {
|
||||||
foregroundExecutor.executeQueuedTasks()
|
foregroundExecutor.executeQueuedTasks()
|
||||||
|
|
||||||
var next3 = openglCleanQueue.poll() as CleanRef?
|
var next = openglCleanQueue.poll() as CleanRef?
|
||||||
|
|
||||||
while (next3 != null) {
|
while (next != null) {
|
||||||
openglObjectsCleaned++
|
openglObjectsCleaned++
|
||||||
next3.fn.accept(next3.value)
|
next.fn.accept(next.value)
|
||||||
checkForGLError("Removing unreachable OpenGL object")
|
checkForGLError("Removing unreachable OpenGL object")
|
||||||
|
|
||||||
val head = openglCleanQueueHead
|
val head = openglCleanQueueHead
|
||||||
|
|
||||||
if (next3 === head) {
|
if (next === head) {
|
||||||
openglCleanQueueHead = head.next
|
openglCleanQueueHead = head.next
|
||||||
} else {
|
} else {
|
||||||
next3.prev?.next = next3.next
|
next.prev?.next = next.next
|
||||||
next3.next?.prev = next3.prev
|
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_SHORT
|
||||||
import org.lwjgl.opengl.GL45.GL_UNSIGNED_BYTE
|
import org.lwjgl.opengl.GL45.GL_UNSIGNED_BYTE
|
||||||
import ru.dbotthepony.kstarbound.client.gl.BufferObject
|
import ru.dbotthepony.kstarbound.client.gl.BufferObject
|
||||||
|
import ru.dbotthepony.kvector.api.IStruct2d
|
||||||
import ru.dbotthepony.kvector.api.IStruct2f
|
import ru.dbotthepony.kvector.api.IStruct2f
|
||||||
import ru.dbotthepony.kvector.api.IStruct4f
|
import ru.dbotthepony.kvector.api.IStruct4f
|
||||||
import ru.dbotthepony.kvector.arrays.Matrix3f
|
import ru.dbotthepony.kvector.arrays.Matrix3f
|
||||||
@ -279,12 +280,36 @@ class VertexBuilder(
|
|||||||
return this
|
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 {
|
fun vertex(transform: Matrix3f, x: Float, y: Float): VertexBuilder {
|
||||||
vertex()
|
vertex()
|
||||||
position(transform, x, y)
|
position(transform, x, y)
|
||||||
return this
|
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 {
|
private fun pushFloat(attr: VertexAttributes.Attribute, x: Float): VertexBuilder {
|
||||||
vertexMemory.position(vertexCount * attributes.vertexStride + attr.relativeOffset)
|
vertexMemory.position(vertexCount * attributes.vertexStride + attr.relativeOffset)
|
||||||
vertexMemory.putFloat(x)
|
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: 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 {
|
fun uv(x: Float, y: Float): VertexBuilder {
|
||||||
return pushFloat2(requireNotNull(uv) { "Vertex format does not have texture UV attribute" }, x, y)
|
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) {
|
for (i in vertices.indices) {
|
||||||
val current = vertices[i]
|
val current = vertices[i]
|
||||||
val next = vertices[(i + 1) % vertices.size]
|
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(), current).color(color)
|
||||||
lines.builder.vertex(state.stack.last(), next.x.toFloat(), next.y.toFloat()).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) {
|
class ItemRenderer(client: StarboundClient, entity: ItemEntity, chunk: ClientChunk?) : EntityRenderer(client, entity, chunk) {
|
||||||
private val def = entity.def
|
private val def = entity.def
|
||||||
|
private val textures = def.inventoryIcon?.stream()?.map { it.image }?.toList() ?: listOf()
|
||||||
|
|
||||||
override fun render(stack: Matrix4fStack) {
|
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
|
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)
|
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() {
|
override fun thinkInner() {
|
||||||
|
@ -324,10 +324,10 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
|||||||
|
|
||||||
protected val entities = ReferenceOpenHashSet<Entity>()
|
protected val entities = ReferenceOpenHashSet<Entity>()
|
||||||
|
|
||||||
protected abstract fun onEntityAdded(entity: Entity)
|
protected open fun onEntityAdded(entity: Entity) { }
|
||||||
protected abstract fun onEntityTransferedToThis(entity: Entity, otherChunk: This)
|
protected open fun onEntityTransferedToThis(entity: Entity, otherChunk: This) { }
|
||||||
protected abstract fun onEntityTransferedFromThis(entity: Entity, otherChunk: This)
|
protected open fun onEntityTransferedFromThis(entity: Entity, otherChunk: This) { }
|
||||||
protected abstract fun onEntityRemoved(entity: Entity)
|
protected open fun onEntityRemoved(entity: Entity) { }
|
||||||
|
|
||||||
fun addEntity(entity: Entity) {
|
fun addEntity(entity: Entity) {
|
||||||
if (!entities.add(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.LiquidDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
|
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
||||||
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
import ru.dbotthepony.kvector.api.IStruct2i
|
import ru.dbotthepony.kvector.api.IStruct2i
|
||||||
|
import ru.dbotthepony.kvector.util2d.AABB
|
||||||
|
import ru.dbotthepony.kvector.vector.Vector2d
|
||||||
import java.io.DataInputStream
|
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 {
|
interface IChunkCell : IStruct2i {
|
||||||
/**
|
/**
|
||||||
* absolute (in world)
|
* absolute (in world)
|
||||||
@ -26,6 +31,13 @@ interface IChunkCell : IStruct2i {
|
|||||||
return y
|
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 foreground: ITileState
|
||||||
val background: ITileState
|
val background: ITileState
|
||||||
val liquid: ILiquidState
|
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
|
package ru.dbotthepony.kstarbound.world.entities
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||||
import ru.dbotthepony.kstarbound.world.Chunk
|
import ru.dbotthepony.kstarbound.world.Chunk
|
||||||
import ru.dbotthepony.kstarbound.world.World
|
import ru.dbotthepony.kstarbound.world.World
|
||||||
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
import ru.dbotthepony.kvector.vector.Vector2d
|
import ru.dbotthepony.kvector.vector.Vector2d
|
||||||
|
|
||||||
abstract class Entity(val world: World<*, *>) {
|
abstract class Entity(val world: World<*, *>) {
|
||||||
@ -56,8 +58,6 @@ abstract class Entity(val world: World<*, *>) {
|
|||||||
val old = field
|
val old = field
|
||||||
field = Vector2d(world.x.cell(value.x), world.y.cell(value.y))
|
field = Vector2d(world.x.cell(value.x), world.y.cell(value.y))
|
||||||
|
|
||||||
movement.notifyPositionChanged()
|
|
||||||
|
|
||||||
if (isSpawned && !isRemoved) {
|
if (isSpawned && !isRemoved) {
|
||||||
val oldChunkPos = world.chunkFromCell(old)
|
val oldChunkPos = world.chunkFromCell(old)
|
||||||
val newChunkPos = world.chunkFromCell(field)
|
val newChunkPos = world.chunkFromCell(field)
|
||||||
@ -85,9 +85,10 @@ abstract class Entity(val world: World<*, *>) {
|
|||||||
return
|
return
|
||||||
|
|
||||||
field = value
|
field = value
|
||||||
movement.notifyPositionChanged()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var velocity = Vector2d.ZERO
|
||||||
|
val hitboxes = ArrayList<Poly>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whenever is this entity spawned in world ([spawn] called).
|
* Whenever is this entity spawned in world ([spawn] called).
|
||||||
@ -113,8 +114,6 @@ abstract class Entity(val world: World<*, *>) {
|
|||||||
if (chunk == null) {
|
if (chunk == null) {
|
||||||
world.orphanedEntities.add(this)
|
world.orphanedEntities.add(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
movement.onSpawnedInWorld()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun remove() {
|
open fun remove() {
|
||||||
@ -127,24 +126,8 @@ abstract class Entity(val world: World<*, *>) {
|
|||||||
world.entities.remove(this)
|
world.entities.remove(this)
|
||||||
chunk?.removeEntity(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")
|
throw IllegalStateException("Tried to think before spawning in world")
|
||||||
}
|
}
|
||||||
|
|
||||||
movement.think()
|
move()
|
||||||
thinkAI()
|
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 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.api.Manifold
|
||||||
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
|
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
|
||||||
import ru.dbotthepony.kbox2d.dynamics.contact.AbstractContact
|
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.defs.item.api.IItemDefinition
|
||||||
import ru.dbotthepony.kstarbound.world.World
|
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) {
|
class ItemEntity(world: World<*, *>, val def: IItemDefinition) : Entity(world) {
|
||||||
override val movement = object : MovementController<ItemEntity>(this) {
|
init {
|
||||||
override fun beginContact(contact: AbstractContact) {
|
hitboxes.add(Poly(AABB.rectangle(Vector2d.ZERO, 0.75, 0.75)))
|
||||||
// тут надо код подбора предмета игроком, если мы начинаем коллизию с окружностью подбора
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun thinkAI() {
|
override fun thinkInner() {
|
||||||
// TODO: деспавнинг?
|
// 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