Ну мы идем дальше
обрезка геометрии мира тест коллизий сущности
This commit is contained in:
parent
c045a699d4
commit
5fe7668fe5
@ -2,22 +2,13 @@ package ru.dbotthepony.kstarbound
|
|||||||
|
|
||||||
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.*
|
|
||||||
import org.lwjgl.opengl.GL46.*
|
|
||||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||||
import ru.dbotthepony.kstarbound.math.Matrix4f
|
|
||||||
import ru.dbotthepony.kstarbound.client.render.Camera
|
|
||||||
import ru.dbotthepony.kstarbound.client.render.ChunkRenderer
|
|
||||||
import ru.dbotthepony.kstarbound.client.render.TextAlignX
|
|
||||||
import ru.dbotthepony.kstarbound.client.render.TextAlignY
|
|
||||||
import ru.dbotthepony.kstarbound.defs.TileDefinition
|
import ru.dbotthepony.kstarbound.defs.TileDefinition
|
||||||
import ru.dbotthepony.kstarbound.util.Color
|
import ru.dbotthepony.kstarbound.math.Vector2d
|
||||||
import ru.dbotthepony.kstarbound.util.formatBytesShort
|
|
||||||
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_FF
|
|
||||||
import ru.dbotthepony.kstarbound.world.Chunk
|
import ru.dbotthepony.kstarbound.world.Chunk
|
||||||
import ru.dbotthepony.kstarbound.world.ChunkPos
|
import ru.dbotthepony.kstarbound.world.ChunkPos
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.Humanoid
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
private val LOGGER = LogManager.getLogger()
|
private val LOGGER = LogManager.getLogger()
|
||||||
|
|
||||||
@ -41,43 +32,38 @@ fun main() {
|
|||||||
Starbound.onInitialize {
|
Starbound.onInitialize {
|
||||||
chunkA = client.world!!.computeIfAbsent(ChunkPos(0, 0)).chunk
|
chunkA = client.world!!.computeIfAbsent(ChunkPos(0, 0)).chunk
|
||||||
val chunkB = client.world!!.computeIfAbsent(ChunkPos(-1, 0)).chunk
|
val chunkB = client.world!!.computeIfAbsent(ChunkPos(-1, 0)).chunk
|
||||||
|
val chunkC = client.world!!.computeIfAbsent(ChunkPos(-2, 0)).chunk
|
||||||
var x = 0
|
|
||||||
var y = 0
|
|
||||||
|
|
||||||
for (tile in Starbound.tilesAccess.values) {
|
|
||||||
//chunkA!!.background[x, y + 1] = tile
|
|
||||||
//chunkA!!.background[x++, y] = tile
|
|
||||||
|
|
||||||
if (x >= 31) {
|
|
||||||
x = 0
|
|
||||||
y += 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
x = 0
|
|
||||||
y = 0
|
|
||||||
|
|
||||||
for (tile in Starbound.tilesAccess.values) {
|
|
||||||
//chunkB.foreground[x, y + 1] = tile
|
|
||||||
//chunkB.foreground[x++, y] = tile
|
|
||||||
|
|
||||||
if (x > 31) {
|
|
||||||
x = 0
|
|
||||||
y += 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val tile = Starbound.getTileDefinition("alienrock")
|
val tile = Starbound.getTileDefinition("alienrock")
|
||||||
|
|
||||||
for (x in 0 .. 31) {
|
for (x in -48 .. 48) {
|
||||||
for (y in 0 .. 31) {
|
for (y in 0 .. 20) {
|
||||||
chunkA!!.foreground[x, y] = tile
|
val chnk = client.world!!.computeIfAbsent(ChunkPos(x, y))
|
||||||
|
|
||||||
|
for (bx in 0 .. 31) {
|
||||||
|
for (by in 0 .. 3) {
|
||||||
|
chnk.chunk.foreground[bx, by] = tile
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (x in 0 .. 31) {
|
for (x in 0 .. 31) {
|
||||||
for (y in 0 .. 31) {
|
for (y in 0 .. 3) {
|
||||||
|
chunkA!!.foreground[x, y] = tile
|
||||||
|
chunkC.foreground[x, y] = tile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (x in 0 .. 31) {
|
||||||
|
for (y in 8 .. 9) {
|
||||||
|
chunkA!!.foreground[x, y] = tile
|
||||||
|
chunkC.foreground[x, y] = tile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (x in 0 .. 31) {
|
||||||
|
for (y in 0 .. 0) {
|
||||||
chunkB.foreground[x, y] = tile
|
chunkB.foreground[x, y] = tile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -87,16 +73,44 @@ fun main() {
|
|||||||
chunkA!!.foreground[x, y] = null as TileDefinition?
|
chunkA!!.foreground[x, y] = null as TileDefinition?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*val rand = Random()
|
||||||
|
|
||||||
|
for (i in 0 .. 400) {
|
||||||
|
chunkA!!.foreground[rand.nextInt(0, CHUNK_SIZE_FF), rand.nextInt(0, CHUNK_SIZE_FF)] = tile
|
||||||
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
val rand = Random()
|
//val rand = Random()
|
||||||
|
val ent = Humanoid(client.world!!)
|
||||||
|
|
||||||
|
ent.pos += Vector2d(y = 36.0, x = 10.0)
|
||||||
|
|
||||||
|
client.onDrawGUI {
|
||||||
|
client.gl.font.render("${ent.pos}", y = 100f, scale = 0.25f)
|
||||||
|
}
|
||||||
|
|
||||||
|
client.onPostDrawWorld {
|
||||||
|
client.gl.quadWireframe {
|
||||||
|
it.quad(ent.aabb + ent.pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
while (client.renderFrame()) {
|
while (client.renderFrame()) {
|
||||||
Starbound.pollCallbacks()
|
Starbound.pollCallbacks()
|
||||||
|
|
||||||
if (chunkA != null && glfwGetTime() < 10.0) {
|
ent.moveAndCollide(client.frameRenderTime)
|
||||||
val tile = Starbound.getTileDefinition("alienrock")
|
client.camera.pos.x = ent.pos.x.toFloat()
|
||||||
|
client.camera.pos.y = ent.pos.y.toFloat()
|
||||||
|
|
||||||
|
//println(client.camera.velocity.toDoubleVector() * client.frameRenderTime * 0.1)
|
||||||
|
|
||||||
|
//if (ent.onGround)
|
||||||
|
ent.velocity += client.camera.velocity.toDoubleVector() * client.frameRenderTime * 0.1
|
||||||
|
|
||||||
|
//if (chunkA != null && glfwGetTime() < 10.0) {
|
||||||
|
// val tile = Starbound.getTileDefinition("alienrock")
|
||||||
//chunkA!!.foreground[rand.nextInt(0, CHUNK_SIZE_FF), rand.nextInt(0, CHUNK_SIZE_FF)] = tile
|
//chunkA!!.foreground[rand.nextInt(0, CHUNK_SIZE_FF), rand.nextInt(0, CHUNK_SIZE_FF)] = tile
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,8 +9,8 @@ import ru.dbotthepony.kstarbound.world.World
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
|
|
||||||
const val METRES_IN_STARBOUND_UNIT = 0.5
|
const val METRES_IN_STARBOUND_UNIT = 0.25
|
||||||
const val METRES_IN_STARBOUND_UNITf = 0.5f
|
const val METRES_IN_STARBOUND_UNITf = 0.25f
|
||||||
|
|
||||||
const val PIXELS_IN_STARBOUND_UNIT = 8.0
|
const val PIXELS_IN_STARBOUND_UNIT = 8.0
|
||||||
const val PIXELS_IN_STARBOUND_UNITf = 8.0f
|
const val PIXELS_IN_STARBOUND_UNITf = 8.0f
|
||||||
|
@ -6,7 +6,7 @@ data class ClientSettings(
|
|||||||
*
|
*
|
||||||
* Масштаб в единицу означает что один Starbound Unit будет равен 8 пикселям на экране
|
* Масштаб в единицу означает что один Starbound Unit будет равен 8 пикселям на экране
|
||||||
*/
|
*/
|
||||||
var scale: Float = 2f
|
var scale: Float = 2f,
|
||||||
) {
|
|
||||||
|
|
||||||
}
|
var debugCollisions: Boolean = true,
|
||||||
|
)
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
package ru.dbotthepony.kstarbound.client
|
package ru.dbotthepony.kstarbound.client
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.api.IStruct2d
|
||||||
import ru.dbotthepony.kstarbound.api.IStruct2f
|
import ru.dbotthepony.kstarbound.api.IStruct2f
|
||||||
import ru.dbotthepony.kstarbound.client.render.ChunkRenderer
|
import ru.dbotthepony.kstarbound.client.render.ChunkRenderer
|
||||||
import ru.dbotthepony.kstarbound.client.render.renderLayeredList
|
import ru.dbotthepony.kstarbound.client.render.renderLayeredList
|
||||||
import ru.dbotthepony.kstarbound.world.Chunk
|
import ru.dbotthepony.kstarbound.math.AABB
|
||||||
import ru.dbotthepony.kstarbound.world.IWorldChunkTuple
|
import ru.dbotthepony.kstarbound.math.Vector2d
|
||||||
import ru.dbotthepony.kstarbound.world.MutableWorldChunkTuple
|
import ru.dbotthepony.kstarbound.world.*
|
||||||
import ru.dbotthepony.kstarbound.world.World
|
|
||||||
|
|
||||||
class ClientWorldChunkTuple(
|
class ClientWorldChunkTuple(
|
||||||
world: World<*>,
|
world: World<*>,
|
||||||
@ -47,7 +47,7 @@ class ClientWorld(val client: StarboundClient, seed: Long = 0L) : World<ClientWo
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Отрисовывает этот мир с точки зрения [pos] в Starbound Units
|
* Отрисовывает этот с обрезкой невидимой геометрии с точки зрения [size] в Starbound Units
|
||||||
*
|
*
|
||||||
* Все координаты "местности" сохраняются, поэтому, если отрисовывать слишком далеко от 0, 0
|
* Все координаты "местности" сохраняются, поэтому, если отрисовывать слишком далеко от 0, 0
|
||||||
* то геометрия может начать искажаться из-за погрешности плавающей запятой
|
* то геометрия может начать искажаться из-за погрешности плавающей запятой
|
||||||
@ -55,25 +55,22 @@ class ClientWorld(val client: StarboundClient, seed: Long = 0L) : World<ClientWo
|
|||||||
* Обрезает всю заведомо невидимую геометрию на основе аргументов mins и maxs (в пикселях)
|
* Обрезает всю заведомо невидимую геометрию на основе аргументов mins и maxs (в пикселях)
|
||||||
*/
|
*/
|
||||||
fun render(
|
fun render(
|
||||||
pos: IStruct2f,
|
size: AABB,
|
||||||
scale: Float = 1f,
|
|
||||||
|
|
||||||
mins: IStruct2f,
|
|
||||||
maxs: IStruct2f,
|
|
||||||
) {
|
) {
|
||||||
val determineRenderers = ArrayList<ChunkRenderer>()
|
val determineRenderers = ArrayList<ChunkRenderer>()
|
||||||
|
|
||||||
for (chunk in chunkMap.values) {
|
for (chunk in collectInternal(size.encasingChunkPosAABB())) {
|
||||||
determineRenderers.add(chunk.renderer)
|
determineRenderers.add(chunk.renderer)
|
||||||
}
|
}
|
||||||
|
|
||||||
val renderList = ArrayList<ChunkRenderer>()
|
|
||||||
|
|
||||||
for (renderer in determineRenderers) {
|
for (renderer in determineRenderers) {
|
||||||
renderList.add(renderer)
|
|
||||||
renderer.autoBakeStatic()
|
renderer.autoBakeStatic()
|
||||||
}
|
}
|
||||||
|
|
||||||
renderLayeredList(client.gl.matrixStack, renderList)
|
renderLayeredList(client.gl.matrixStack, determineRenderers)
|
||||||
|
|
||||||
|
for (renderer in determineRenderers) {
|
||||||
|
renderer.renderDebug()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,11 +7,15 @@ import org.lwjgl.glfw.GLFWErrorCallback
|
|||||||
import org.lwjgl.opengl.GL46.*
|
import org.lwjgl.opengl.GL46.*
|
||||||
import org.lwjgl.system.MemoryStack
|
import org.lwjgl.system.MemoryStack
|
||||||
import org.lwjgl.system.MemoryUtil
|
import org.lwjgl.system.MemoryUtil
|
||||||
|
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNIT
|
||||||
|
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
|
||||||
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
|
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
|
||||||
import ru.dbotthepony.kstarbound.math.Matrix4f
|
import ru.dbotthepony.kstarbound.math.Matrix4f
|
||||||
import ru.dbotthepony.kstarbound.client.render.Camera
|
import ru.dbotthepony.kstarbound.client.render.Camera
|
||||||
import ru.dbotthepony.kstarbound.client.render.TextAlignX
|
import ru.dbotthepony.kstarbound.client.render.TextAlignX
|
||||||
import ru.dbotthepony.kstarbound.client.render.TextAlignY
|
import ru.dbotthepony.kstarbound.client.render.TextAlignY
|
||||||
|
import ru.dbotthepony.kstarbound.math.AABB
|
||||||
|
import ru.dbotthepony.kstarbound.math.Vector2d
|
||||||
import ru.dbotthepony.kstarbound.math.Vector2f
|
import ru.dbotthepony.kstarbound.math.Vector2f
|
||||||
import ru.dbotthepony.kstarbound.util.Color
|
import ru.dbotthepony.kstarbound.util.Color
|
||||||
import ru.dbotthepony.kstarbound.util.formatBytesShort
|
import ru.dbotthepony.kstarbound.util.formatBytesShort
|
||||||
@ -159,6 +163,24 @@ class StarboundClient : AutoCloseable {
|
|||||||
|
|
||||||
val settings = ClientSettings()
|
val settings = ClientSettings()
|
||||||
|
|
||||||
|
private val onDrawGUI = ArrayList<() -> Unit>()
|
||||||
|
|
||||||
|
fun onDrawGUI(lambda: () -> Unit) {
|
||||||
|
onDrawGUI.add(lambda)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val onPreDrawWorld = ArrayList<() -> Unit>()
|
||||||
|
|
||||||
|
fun onPreDrawWorld(lambda: () -> Unit) {
|
||||||
|
onPreDrawWorld.add(lambda)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val onPostDrawWorld = ArrayList<() -> Unit>()
|
||||||
|
|
||||||
|
fun onPostDrawWorld(lambda: () -> Unit) {
|
||||||
|
onPostDrawWorld.add(lambda)
|
||||||
|
}
|
||||||
|
|
||||||
fun renderFrame(): Boolean {
|
fun renderFrame(): Boolean {
|
||||||
ensureSameThread()
|
ensureSameThread()
|
||||||
|
|
||||||
@ -176,10 +198,22 @@ class StarboundClient : AutoCloseable {
|
|||||||
val maxs = -mins
|
val maxs = -mins
|
||||||
|
|
||||||
gl.matrixStack.push()
|
gl.matrixStack.push()
|
||||||
.translateWithScale(viewportWidth / 2f - camera.pos.x, viewportHeight / 2f - camera.pos.y) // центр экрана + координаты отрисовки мира
|
.translateWithScale(viewportWidth / 2f, viewportHeight / 2f, 2f) // центр экрана + координаты отрисовки мира
|
||||||
.scale(x = settings.scale, y = settings.scale) // масштабируем до нужного размера
|
.scale(x = settings.scale * PIXELS_IN_STARBOUND_UNITf, y = settings.scale * PIXELS_IN_STARBOUND_UNITf) // масштабируем до нужного размера
|
||||||
|
.translateWithScale(-camera.pos.x, -camera.pos.y) // перемещаем вид к камере
|
||||||
|
|
||||||
world?.render(Vector2f.ZERO, mins = mins, maxs = maxs)
|
for (lambda in onPreDrawWorld) {
|
||||||
|
lambda.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
|
world?.render(AABB.rectangle(
|
||||||
|
camera.pos.toDoubleVector(),
|
||||||
|
viewportWidth / settings.scale / PIXELS_IN_STARBOUND_UNIT,
|
||||||
|
viewportHeight / settings.scale / PIXELS_IN_STARBOUND_UNIT))
|
||||||
|
|
||||||
|
for (lambda in onPostDrawWorld) {
|
||||||
|
lambda.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
gl.matrixStack.pop()
|
gl.matrixStack.pop()
|
||||||
|
|
||||||
@ -210,6 +244,10 @@ class StarboundClient : AutoCloseable {
|
|||||||
gl.matrixStack.pop()
|
gl.matrixStack.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (fn in onDrawGUI) {
|
||||||
|
fn.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
val runtime = Runtime.getRuntime()
|
val runtime = Runtime.getRuntime()
|
||||||
|
|
||||||
gl.font.render("FPS: ${(averageFramesPerSecond * 100f).toInt() / 100f}", scale = 0.4f)
|
gl.font.render("FPS: ${(averageFramesPerSecond * 100f).toInt() / 100f}", scale = 0.4f)
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.client
|
||||||
|
|
||||||
|
class UserInput {
|
||||||
|
}
|
@ -38,11 +38,15 @@ data class AttributeListPosition(val name: String, val index: Int, val glType: G
|
|||||||
class GLFlatAttributeList(builder: GLFlatAttributeListBuilder) : IGLAttributeList {
|
class GLFlatAttributeList(builder: GLFlatAttributeListBuilder) : IGLAttributeList {
|
||||||
val attributes: List<AttributeListPosition>
|
val attributes: List<AttributeListPosition>
|
||||||
val size get() = attributes.size
|
val size get() = attributes.size
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Шаг данных аттрибутов, в байтах. Т.е. одна полная вершина будет занимать [stride] байт в памяти.
|
||||||
|
*/
|
||||||
val stride: Int
|
val stride: Int
|
||||||
|
|
||||||
operator fun get(index: Int) = attributes[index]
|
operator fun get(index: Int) = attributes[index]
|
||||||
|
|
||||||
fun vertexBuilder(vertexType: VertexType) = VertexBuilder(this, vertexType)
|
fun vertexBuilder(vertexType: VertexType) = DynamicVertexBuilder(this, vertexType)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val buildList = ArrayList<AttributeListPosition>()
|
val buildList = ArrayList<AttributeListPosition>()
|
||||||
@ -81,6 +85,7 @@ class GLFlatAttributeList(builder: GLFlatAttributeListBuilder) : IGLAttributeLis
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
val VEC2F = GLFlatAttributeListBuilder().also {it.push(GLType.VEC2F)}.build()
|
||||||
val VEC3F = GLFlatAttributeListBuilder().also {it.push(GLType.VEC3F)}.build()
|
val VEC3F = GLFlatAttributeListBuilder().also {it.push(GLType.VEC3F)}.build()
|
||||||
val VERTEX_TEXTURE = GLFlatAttributeListBuilder().also {it.push(GLType.VEC3F).push(GLType.VEC2F)}.build()
|
val VERTEX_TEXTURE = GLFlatAttributeListBuilder().also {it.push(GLType.VEC3F).push(GLType.VEC2F)}.build()
|
||||||
val VERTEX_2D_TEXTURE = GLFlatAttributeListBuilder().also {it.push(GLType.VEC2F).push(GLType.VEC2F)}.build()
|
val VERTEX_2D_TEXTURE = GLFlatAttributeListBuilder().also {it.push(GLType.VEC2F).push(GLType.VEC2F)}.build()
|
||||||
|
@ -9,7 +9,6 @@ import ru.dbotthepony.kstarbound.client.freetype.FreeType
|
|||||||
import ru.dbotthepony.kstarbound.math.Matrix4f
|
import ru.dbotthepony.kstarbound.math.Matrix4f
|
||||||
import ru.dbotthepony.kstarbound.math.Matrix4fStack
|
import ru.dbotthepony.kstarbound.math.Matrix4fStack
|
||||||
import ru.dbotthepony.kstarbound.client.render.Font
|
import ru.dbotthepony.kstarbound.client.render.Font
|
||||||
import ru.dbotthepony.kstarbound.client.render.TileRenderer
|
|
||||||
import ru.dbotthepony.kstarbound.client.render.TileRenderers
|
import ru.dbotthepony.kstarbound.client.render.TileRenderers
|
||||||
import ru.dbotthepony.kstarbound.util.Color
|
import ru.dbotthepony.kstarbound.util.Color
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -83,6 +82,11 @@ interface GLCleanable : Cleaner.Cleanable {
|
|||||||
fun cleanManual(): Unit
|
fun cleanManual(): Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface GLStreamBuilderList {
|
||||||
|
val small: StreamVertexBuilder
|
||||||
|
val statefulSmall: StatefulStreamVertexBuilder
|
||||||
|
}
|
||||||
|
|
||||||
class GLStateTracker {
|
class GLStateTracker {
|
||||||
init {
|
init {
|
||||||
// This line is critical for LWJGL's interoperation with GLFW's
|
// This line is critical for LWJGL's interoperation with GLFW's
|
||||||
@ -329,11 +333,70 @@ class GLStateTracker {
|
|||||||
fragment.unlink()
|
fragment.unlink()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val flatProgram: GLTransformableColorableProgram
|
||||||
|
|
||||||
|
init {
|
||||||
|
val vertex = GLShader.internalVertex("shaders/vertex/flat_vertex_2d.glsl")
|
||||||
|
val fragment = GLShader.internalFragment("shaders/fragment/flat_color.glsl")
|
||||||
|
|
||||||
|
flatProgram = GLTransformableColorableProgram(this, vertex, fragment)
|
||||||
|
|
||||||
|
vertex.unlink()
|
||||||
|
fragment.unlink()
|
||||||
|
}
|
||||||
|
|
||||||
|
val flat2DQuads = object : GLStreamBuilderList {
|
||||||
|
override val small by lazy {
|
||||||
|
return@lazy StreamVertexBuilder(GLFlatAttributeList.VEC2F, VertexType.QUADS, 1024)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val statefulSmall by lazy {
|
||||||
|
return@lazy StatefulStreamVertexBuilder(this@GLStateTracker, small)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val flat2DQuadLines = object : GLStreamBuilderList {
|
||||||
|
override val small by lazy {
|
||||||
|
return@lazy StreamVertexBuilder(GLFlatAttributeList.VEC2F, VertexType.QUADS_AS_LINES, 1024)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val statefulSmall by lazy {
|
||||||
|
return@lazy StatefulStreamVertexBuilder(this@GLStateTracker, small)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val flat2DQuadWireframe = object : GLStreamBuilderList {
|
||||||
|
override val small by lazy {
|
||||||
|
return@lazy StreamVertexBuilder(GLFlatAttributeList.VEC2F, VertexType.QUADS_AS_LINES_WIREFRAME, 1024)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val statefulSmall by lazy {
|
||||||
|
return@lazy StatefulStreamVertexBuilder(this@GLStateTracker, small)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val matrixStack = Matrix4fStack()
|
val matrixStack = Matrix4fStack()
|
||||||
val freeType = FreeType()
|
val freeType = FreeType()
|
||||||
|
|
||||||
val font = Font(this)
|
val font = Font(this)
|
||||||
|
|
||||||
|
fun quadWireframe(lambda: (StreamVertexBuilder) -> Unit) {
|
||||||
|
val stateful = flat2DQuadWireframe.statefulSmall
|
||||||
|
val builder = stateful.builder
|
||||||
|
|
||||||
|
builder.begin()
|
||||||
|
|
||||||
|
lambda.invoke(builder)
|
||||||
|
|
||||||
|
stateful.upload()
|
||||||
|
|
||||||
|
flatProgram.use()
|
||||||
|
flatProgram.color.set(Color.WHITE)
|
||||||
|
flatProgram.transform.set(matrixStack.last)
|
||||||
|
|
||||||
|
stateful.draw(GL_LINES)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val LOGGER = LogManager.getLogger(GLStateTracker::class.java)
|
private val LOGGER = LogManager.getLogger(GLStateTracker::class.java)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package ru.dbotthepony.kstarbound.client.gl
|
package ru.dbotthepony.kstarbound.client.gl
|
||||||
|
|
||||||
import org.lwjgl.opengl.GL46.*
|
import org.lwjgl.opengl.GL46.*
|
||||||
|
import org.lwjgl.system.MemoryUtil
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
@ -41,6 +42,20 @@ class GLVertexBufferObject(val state: GLStateTracker, val type: VBOType = VBOTyp
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun bufferData(data: ByteBuffer, usage: Int, length: Long): GLVertexBufferObject {
|
||||||
|
check(isValid) { "Tried to use NULL GLVertexBufferObject" }
|
||||||
|
state.ensureSameThread()
|
||||||
|
|
||||||
|
if (length > data.remaining().toLong()) {
|
||||||
|
throw IndexOutOfBoundsException("Tried to upload $data into $pointer with offset at ${data.position()} with length of $length, but that is longer than remaining data length of ${data.remaining()}!")
|
||||||
|
}
|
||||||
|
|
||||||
|
nglNamedBufferData(pointer, length, MemoryUtil.memAddress(data), usage)
|
||||||
|
|
||||||
|
checkForGLError()
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
fun bufferData(data: IntArray, usage: Int): GLVertexBufferObject {
|
fun bufferData(data: IntArray, usage: Int): GLVertexBufferObject {
|
||||||
check(isValid) { "Tried to use NULL GLVertexBufferObject" }
|
check(isValid) { "Tried to use NULL GLVertexBufferObject" }
|
||||||
state.ensureSameThread()
|
state.ensureSameThread()
|
||||||
|
@ -1,16 +1,87 @@
|
|||||||
package ru.dbotthepony.kstarbound.client.gl
|
package ru.dbotthepony.kstarbound.client.gl
|
||||||
|
|
||||||
import org.lwjgl.opengl.GL46.*
|
import org.lwjgl.opengl.GL46.*
|
||||||
|
import ru.dbotthepony.kstarbound.math.AABB
|
||||||
|
import java.io.Closeable
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.nio.ByteOrder
|
import java.nio.ByteOrder
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
enum class VertexType(val elements: Int, val indicies: IntArray) {
|
enum class VertexType(val elements: Int, val indicies: IntArray) {
|
||||||
|
LINES(2, intArrayOf(0, 1)),
|
||||||
TRIANGLES(3, intArrayOf(0, 1, 2)),
|
TRIANGLES(3, intArrayOf(0, 1, 2)),
|
||||||
QUADS(4, intArrayOf(0, 1, 2, 1, 2, 3))
|
QUADS(4, intArrayOf(0, 1, 2, 1, 2, 3)),
|
||||||
|
QUADS_AS_LINES(4, intArrayOf(0, 1, 0, 2, 1, 3, 2, 3)),
|
||||||
|
QUADS_AS_LINES_WIREFRAME(4, intArrayOf(0, 1, 0, 2, 1, 3, 2, 3, 0, 3, 1, 2)),
|
||||||
}
|
}
|
||||||
|
|
||||||
typealias VertexTransformer = (VertexBuilder.Vertex, Int) -> VertexBuilder.Vertex
|
interface IVertexBuilder<This : IVertexBuilder<This, VertexType>, VertexType : IVertex<VertexType, This>> {
|
||||||
|
val type: ru.dbotthepony.kstarbound.client.gl.VertexType
|
||||||
|
val indexCount: Int
|
||||||
|
|
||||||
|
fun begin(): This
|
||||||
|
fun vertex(): VertexType
|
||||||
|
fun checkValid()
|
||||||
|
fun upload(vbo: GLVertexBufferObject, ebo: GLVertexBufferObject, drawType: Int = GL_DYNAMIC_DRAW)
|
||||||
|
|
||||||
|
fun quad(
|
||||||
|
x0: Float,
|
||||||
|
y0: Float,
|
||||||
|
x1: Float,
|
||||||
|
y1: Float,
|
||||||
|
lambda: VertexTransformer = emptyTransform
|
||||||
|
): This {
|
||||||
|
check(type.elements == 4) { "Currently building $type" }
|
||||||
|
|
||||||
|
lambda(vertex().pushVec2f(x0, y0), 0).end()
|
||||||
|
lambda(vertex().pushVec2f(x1, y0), 1).end()
|
||||||
|
lambda(vertex().pushVec2f(x0, y1), 2).end()
|
||||||
|
lambda(vertex().pushVec2f(x1, y1), 3).end()
|
||||||
|
|
||||||
|
return this as This
|
||||||
|
}
|
||||||
|
|
||||||
|
fun quad(aabb: AABB, lambda: VertexTransformer = emptyTransform): This {
|
||||||
|
return quad(
|
||||||
|
aabb.mins.x.toFloat(),
|
||||||
|
aabb.mins.y.toFloat(),
|
||||||
|
aabb.maxs.x.toFloat(),
|
||||||
|
aabb.maxs.y.toFloat(),
|
||||||
|
lambda
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun quadZ(
|
||||||
|
x0: Float,
|
||||||
|
y0: Float,
|
||||||
|
x1: Float,
|
||||||
|
y1: Float,
|
||||||
|
z: Float,
|
||||||
|
lambda: VertexTransformer = emptyTransform
|
||||||
|
): This {
|
||||||
|
check(type.elements == 4) { "Currently building $type" }
|
||||||
|
|
||||||
|
lambda(vertex().pushVec3f(x0, y0, z), 0).end()
|
||||||
|
lambda(vertex().pushVec3f(x1, y0, z), 1).end()
|
||||||
|
lambda(vertex().pushVec3f(x0, y1, z), 2).end()
|
||||||
|
lambda(vertex().pushVec3f(x1, y1, z), 3).end()
|
||||||
|
|
||||||
|
return this as This
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IVertex<This : IVertex<This, VertexBuilderType>, VertexBuilderType> {
|
||||||
|
fun checkValid()
|
||||||
|
fun expect(name: String): This
|
||||||
|
fun expect(type: GLType): This
|
||||||
|
fun pushVec3f(x: Float, y: Float, z: Float): This
|
||||||
|
fun pushVec2f(x: Float, y: Float): This
|
||||||
|
fun end(): VertexBuilderType
|
||||||
|
}
|
||||||
|
|
||||||
|
typealias VertexTransformer = (IVertex<*, *>, Int) -> IVertex<*, *>
|
||||||
private val emptyTransform: VertexTransformer = { it, _ -> it }
|
private val emptyTransform: VertexTransformer = { it, _ -> it }
|
||||||
|
private val EMPTY_BUFFER = ByteBuffer.allocateDirect(0)
|
||||||
|
|
||||||
object VertexTransformers {
|
object VertexTransformers {
|
||||||
fun uv(u0: Float,
|
fun uv(u0: Float,
|
||||||
@ -58,111 +129,93 @@ object VertexTransformers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class VertexBuilder(val attributes: GLFlatAttributeList, private val type: VertexType) {
|
class DynamicVertexBuilder(val attributes: GLFlatAttributeList, override val type: VertexType) : IVertexBuilder<DynamicVertexBuilder, DynamicVertexBuilder.Vertex> {
|
||||||
private val verticies = ArrayList<Vertex>()
|
private val verticies = ArrayList<Vertex>()
|
||||||
val indexCount get() = (verticies.size / type.elements) * type.indicies.size
|
override val indexCount get() = (verticies.size / type.elements) * type.indicies.size
|
||||||
|
|
||||||
fun begin(): VertexBuilder {
|
override fun begin(): DynamicVertexBuilder {
|
||||||
verticies.clear()
|
verticies.clear()
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun vertex(): Vertex {
|
override fun vertex(): Vertex {
|
||||||
return Vertex()
|
return Vertex()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun quadZ(
|
override fun checkValid() {
|
||||||
x0: Float,
|
|
||||||
y0: Float,
|
|
||||||
x1: Float,
|
|
||||||
y1: Float,
|
|
||||||
z: Float,
|
|
||||||
lambda: VertexTransformer = emptyTransform
|
|
||||||
): VertexBuilder {
|
|
||||||
check(type == VertexType.QUADS) { "Currently building $type" }
|
|
||||||
|
|
||||||
lambda(Vertex().pushVec3f(x0, y0, z), 0).end()
|
|
||||||
lambda(Vertex().pushVec3f(x1, y0, z), 1).end()
|
|
||||||
lambda(Vertex().pushVec3f(x0, y1, z), 2).end()
|
|
||||||
lambda(Vertex().pushVec3f(x1, y1, z), 3).end()
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun quad(
|
|
||||||
x0: Float,
|
|
||||||
y0: Float,
|
|
||||||
x1: Float,
|
|
||||||
y1: Float,
|
|
||||||
lambda: VertexTransformer = emptyTransform
|
|
||||||
): VertexBuilder {
|
|
||||||
check(type == VertexType.QUADS) { "Currently building $type" }
|
|
||||||
|
|
||||||
lambda(Vertex().pushVec2f(x0, y0), 0).end()
|
|
||||||
lambda(Vertex().pushVec2f(x1, y0), 1).end()
|
|
||||||
lambda(Vertex().pushVec2f(x0, y1), 2).end()
|
|
||||||
lambda(Vertex().pushVec2f(x1, y1), 3).end()
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun checkValid() {
|
|
||||||
for (vertex in verticies) {
|
for (vertex in verticies) {
|
||||||
vertex.checkValid()
|
vertex.checkValid()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Загружает (копирует) данные в указанные буферы, с их текущей позиции
|
||||||
|
*/
|
||||||
|
fun upload(
|
||||||
|
vertexBuffer: ByteBuffer,
|
||||||
|
elementBuffer: ByteBuffer,
|
||||||
|
) {
|
||||||
|
check(verticies.size % type.elements == 0) { "Not fully built (expected ${type.elements} verticies to be present for each element, last element has only ${verticies.size % type.elements})" }
|
||||||
|
|
||||||
|
require(vertexBuffer.order() == ByteOrder.nativeOrder()) { "Byte order of $vertexBuffer does not match native order" }
|
||||||
|
require(elementBuffer.order() == ByteOrder.nativeOrder()) { "Byte order of $elementBuffer does not match native order" }
|
||||||
|
|
||||||
|
checkValid()
|
||||||
|
|
||||||
|
for (vertex in verticies) {
|
||||||
|
vertex.upload(vertexBuffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
var offsetVertex = 0
|
||||||
|
|
||||||
|
for (i in 0 until verticies.size / type.elements) {
|
||||||
|
for (i2 in type.indicies.indices) {
|
||||||
|
elementBuffer.putInt(type.indicies[i2] + offsetVertex)
|
||||||
|
}
|
||||||
|
|
||||||
|
offsetVertex += type.elements
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Загружает буфер в указанные VBO и EBO
|
* Загружает буфер в указанные VBO и EBO
|
||||||
*
|
*
|
||||||
* операция создаёт мусор вне кучи и довольно медленная
|
* операция создаёт мусор вне кучи и довольно медленная
|
||||||
*/
|
*/
|
||||||
fun upload(vbo: GLVertexBufferObject, ebo: GLVertexBufferObject, drawType: Int = GL_DYNAMIC_DRAW) {
|
override fun upload(vbo: GLVertexBufferObject, ebo: GLVertexBufferObject, drawType: Int) {
|
||||||
require(vbo.isArray) { "$vbo is not an array" }
|
require(vbo.isArray) { "$vbo is not an array" }
|
||||||
require(ebo.isElementArray) { "$vbo is not an element array" }
|
require(ebo.isElementArray) { "$vbo is not an element array" }
|
||||||
|
|
||||||
checkValid()
|
|
||||||
|
|
||||||
check(verticies.size % type.elements == 0) { "Not fully built (expected ${type.elements} verticies to be present for each element, last element has only ${verticies.size % type.elements})" }
|
check(verticies.size % type.elements == 0) { "Not fully built (expected ${type.elements} verticies to be present for each element, last element has only ${verticies.size % type.elements})" }
|
||||||
|
|
||||||
vbo.bind()
|
checkValid()
|
||||||
ebo.bind()
|
|
||||||
|
|
||||||
if (verticies.size == 0) {
|
if (verticies.size == 0) {
|
||||||
vbo.bufferData(intArrayOf(), drawType)
|
vbo.bufferData(EMPTY_BUFFER, drawType)
|
||||||
ebo.bufferData(intArrayOf(), drawType)
|
ebo.bufferData(EMPTY_BUFFER, drawType)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val bytes = ByteBuffer.allocateDirect(verticies.size * attributes.stride)
|
val vertexBuffer = ByteBuffer.allocateDirect(verticies.size * attributes.stride)
|
||||||
bytes.order(ByteOrder.nativeOrder())
|
vertexBuffer.order(ByteOrder.nativeOrder())
|
||||||
|
|
||||||
for (vertex in verticies) {
|
val elementBuffer = ByteBuffer.allocateDirect((verticies.size / type.elements) * type.indicies.size * 4)
|
||||||
vertex.upload(bytes)
|
elementBuffer.order(ByteOrder.nativeOrder())
|
||||||
}
|
|
||||||
|
|
||||||
check(bytes.position() == bytes.capacity()) { "Buffer is not fully filled (position: ${bytes.position()}; capacity: ${bytes.capacity()})" }
|
upload(vertexBuffer, elementBuffer)
|
||||||
|
|
||||||
bytes.position(0)
|
check(vertexBuffer.position() == vertexBuffer.capacity()) { "Vertex Buffer is not fully filled (position: ${vertexBuffer.position()}; capacity: ${vertexBuffer.capacity()})" }
|
||||||
vbo.bufferData(bytes, drawType)
|
check(elementBuffer.position() == elementBuffer.capacity()) { "Element Buffer is not fully filled (position: ${elementBuffer.position()}; capacity: ${elementBuffer.capacity()})" }
|
||||||
|
|
||||||
val elementIndicies = IntArray((verticies.size / type.elements) * type.indicies.size)
|
vertexBuffer.position(0)
|
||||||
var offset = 0
|
elementBuffer.position(0)
|
||||||
var offsetVertex = 0
|
|
||||||
|
|
||||||
for (i in 0 until verticies.size / type.elements) {
|
vbo.bufferData(vertexBuffer, drawType)
|
||||||
for (i2 in type.indicies.indices) {
|
ebo.bufferData(elementBuffer, drawType)
|
||||||
elementIndicies[offset + i2] = type.indicies[i2] + offsetVertex
|
|
||||||
}
|
|
||||||
|
|
||||||
offset += type.indicies.size
|
|
||||||
offsetVertex += type.elements
|
|
||||||
}
|
|
||||||
|
|
||||||
ebo.bufferData(elementIndicies, drawType)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class Vertex {
|
inner class Vertex : IVertex<Vertex, DynamicVertexBuilder> {
|
||||||
init {
|
init {
|
||||||
verticies.add(this)
|
verticies.add(this)
|
||||||
}
|
}
|
||||||
@ -193,7 +246,7 @@ class VertexBuilder(val attributes: GLFlatAttributeList, private val type: Verte
|
|||||||
} }.joinToString("; ")})"
|
} }.joinToString("; ")})"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun expect(name: String): Vertex {
|
override fun expect(name: String): Vertex {
|
||||||
if (index >= attributes.size) {
|
if (index >= attributes.size) {
|
||||||
throw IllegalStateException("Reached end of attribute list early, expected $name")
|
throw IllegalStateException("Reached end of attribute list early, expected $name")
|
||||||
}
|
}
|
||||||
@ -205,7 +258,7 @@ class VertexBuilder(val attributes: GLFlatAttributeList, private val type: Verte
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun expect(type: GLType): Vertex {
|
override fun expect(type: GLType): Vertex {
|
||||||
if (index >= attributes.size) {
|
if (index >= attributes.size) {
|
||||||
throw IllegalStateException("Reached end of attribute list early, expected type $type")
|
throw IllegalStateException("Reached end of attribute list early, expected type $type")
|
||||||
}
|
}
|
||||||
@ -217,19 +270,19 @@ class VertexBuilder(val attributes: GLFlatAttributeList, private val type: Verte
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pushVec3f(x: Float, y: Float, z: Float): Vertex {
|
override fun pushVec3f(x: Float, y: Float, z: Float): Vertex {
|
||||||
expect(GLType.VEC3F)
|
expect(GLType.VEC3F)
|
||||||
store[index++] = floatArrayOf(x, y, z)
|
store[index++] = floatArrayOf(x, y, z)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pushVec2f(x: Float, y: Float): Vertex {
|
override fun pushVec2f(x: Float, y: Float): Vertex {
|
||||||
expect(GLType.VEC2F)
|
expect(GLType.VEC2F)
|
||||||
store[index++] = floatArrayOf(x, y)
|
store[index++] = floatArrayOf(x, y)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkValid() {
|
override fun checkValid() {
|
||||||
for (elem in store.indices) {
|
for (elem in store.indices) {
|
||||||
if (store[elem] == null) {
|
if (store[elem] == null) {
|
||||||
throw IllegalStateException("Vertex element at position $elem is null")
|
throw IllegalStateException("Vertex element at position $elem is null")
|
||||||
@ -237,10 +290,231 @@ class VertexBuilder(val attributes: GLFlatAttributeList, private val type: Verte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun end(): VertexBuilder {
|
override fun end(): DynamicVertexBuilder {
|
||||||
checkValid()
|
checkValid()
|
||||||
return this@VertexBuilder
|
return this@DynamicVertexBuilder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "Поточная" версия [DynamicVertexBuilder], ориентированная на скорость работы и имеющая фиксированный размер буфера
|
||||||
|
*
|
||||||
|
* Главные отличия:
|
||||||
|
* * Данный объект не желательно создавать каждый раз когда надо отрисовать какое либо количество геометрии, а использовать уже существующий, который
|
||||||
|
* удовлетворяет требованиям (формат вершин, и их потенциально максимальное количество)
|
||||||
|
* * Максимальное количество vertex'ов фиксированно и равняется [maxElements] * [VertexType.elements]
|
||||||
|
* * Имеет два встроенных DirectByteBuffer и НЕ позволяет загружать данные в другие ByteBuffer, только во внутренние ByteBuffer и только в VBO; EBO
|
||||||
|
*/
|
||||||
|
class StreamVertexBuilder(
|
||||||
|
val attributes: GLFlatAttributeList,
|
||||||
|
override val type: VertexType,
|
||||||
|
val maxElements: Int,
|
||||||
|
) : IVertexBuilder<StreamVertexBuilder, StreamVertexBuilder.Vertex> {
|
||||||
|
val maxVertexNum = maxElements * type.elements
|
||||||
|
var nextVertex = 0
|
||||||
|
private set
|
||||||
|
|
||||||
|
override val indexCount get() = (nextVertex / type.elements) * type.indicies.size
|
||||||
|
val maxIndexCount = maxElements * type.indicies.size
|
||||||
|
|
||||||
|
val elementIndexType = when (maxIndexCount) {
|
||||||
|
// api performance issue 102: glDrawElements uses element index type 'GL_UNSIGNED_BYTE' that is not optimal for the current hardware configuration; consider using 'GL_UNSIGNED_SHORT' instead
|
||||||
|
// in 0 .. 255 -> GL_UNSIGNED_BYTE
|
||||||
|
in 0 .. 65535 -> GL_UNSIGNED_SHORT
|
||||||
|
else -> GL_UNSIGNED_INT
|
||||||
|
}
|
||||||
|
|
||||||
|
private var head: Vertex? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Буфер для VBO достаточного размера
|
||||||
|
*/
|
||||||
|
private val vertexBuffer = ByteBuffer.allocateDirect(maxVertexNum * attributes.stride)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Буфер для EBO достаточного размера
|
||||||
|
*/
|
||||||
|
private val elementBuffer = ByteBuffer.allocateDirect(maxElements * type.indicies.size * 4)
|
||||||
|
|
||||||
|
init {
|
||||||
|
vertexBuffer.order(ByteOrder.nativeOrder())
|
||||||
|
elementBuffer.order(ByteOrder.nativeOrder())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeElementIndex(value: Int) {
|
||||||
|
when (elementIndexType) {
|
||||||
|
GL_UNSIGNED_BYTE -> elementBuffer.put(value.toByte())
|
||||||
|
GL_UNSIGNED_SHORT -> elementBuffer.putShort(value.toShort())
|
||||||
|
else -> elementBuffer.putInt(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var offsetElementIndex = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Устанавливает метку этого билдера в ноль.
|
||||||
|
*
|
||||||
|
* Не обнуляет память буферов!
|
||||||
|
*/
|
||||||
|
override fun begin(): StreamVertexBuilder {
|
||||||
|
nextVertex = 0
|
||||||
|
offsetElementIndex = 0
|
||||||
|
head = null
|
||||||
|
vertexBuffer.position(0)
|
||||||
|
elementBuffer.position(0)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun vertex(): Vertex {
|
||||||
|
return Vertex()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun upload(vbo: GLVertexBufferObject, ebo: GLVertexBufferObject, drawType: Int) {
|
||||||
|
require(vbo.isArray) { "$vbo is not an array" }
|
||||||
|
require(ebo.isElementArray) { "$vbo is not an element array" }
|
||||||
|
|
||||||
|
if (nextVertex == 0) {
|
||||||
|
vbo.bufferData(EMPTY_BUFFER, drawType)
|
||||||
|
ebo.bufferData(EMPTY_BUFFER, drawType)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
checkValid()
|
||||||
|
|
||||||
|
val a = vertexBuffer.position().toLong()
|
||||||
|
val b = elementBuffer.position().toLong()
|
||||||
|
|
||||||
|
vertexBuffer.position(0)
|
||||||
|
elementBuffer.position(0)
|
||||||
|
|
||||||
|
vbo.bufferData(vertexBuffer, drawType, length = a)
|
||||||
|
ebo.bufferData(elementBuffer, drawType, length = b)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun checkValid() {
|
||||||
|
var vertex = head
|
||||||
|
|
||||||
|
while (vertex != null) {
|
||||||
|
vertex.checkValid()
|
||||||
|
vertex = vertex.previous
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class Vertex : IVertex<Vertex, StreamVertexBuilder> {
|
||||||
|
private val vertexIndex = nextVertex++
|
||||||
|
val previous = head
|
||||||
|
private var bufferPosition = vertexIndex * attributes.stride
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (vertexIndex >= maxVertexNum) {
|
||||||
|
throw IndexOutOfBoundsException("Tried to push new vertex $vertexIndex, when already above limit of $maxVertexNum!")
|
||||||
|
}
|
||||||
|
|
||||||
|
head = this
|
||||||
|
|
||||||
|
for (i2 in type.indicies.indices) {
|
||||||
|
writeElementIndex(type.indicies[i2] + offsetElementIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
offsetElementIndex += type.elements
|
||||||
|
}
|
||||||
|
|
||||||
|
private var index = 0
|
||||||
|
|
||||||
|
override fun checkValid() {
|
||||||
|
check(index == attributes.size) { "Vertex $vertexIndex is not fully filled (only $index attributes provided, ${attributes.size} required)" }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun expect(name: String): Vertex {
|
||||||
|
if (index >= attributes.size) {
|
||||||
|
throw IllegalStateException("Reached end of attribute list early, expected $name")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attributes[index].name != name) {
|
||||||
|
throw IllegalStateException("Expected $name, got ${attributes[index].name}[${attributes[index].glType}] (at position $index)")
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun expect(type: GLType): Vertex {
|
||||||
|
if (index >= attributes.size) {
|
||||||
|
throw IllegalStateException("Reached end of attribute list early, expected type $type")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attributes[index].glType != type) {
|
||||||
|
throw IllegalStateException("Expected $type, got ${attributes[index].name}[${attributes[index].glType}] (at position $index)")
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pushVec3f(x: Float, y: Float, z: Float): Vertex {
|
||||||
|
expect(GLType.VEC3F)
|
||||||
|
vertexBuffer.position(bufferPosition)
|
||||||
|
vertexBuffer.putFloat(x)
|
||||||
|
vertexBuffer.putFloat(y)
|
||||||
|
vertexBuffer.putFloat(z)
|
||||||
|
index++
|
||||||
|
bufferPosition += 12
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pushVec2f(x: Float, y: Float): Vertex {
|
||||||
|
expect(GLType.VEC2F)
|
||||||
|
vertexBuffer.position(bufferPosition)
|
||||||
|
vertexBuffer.putFloat(x)
|
||||||
|
vertexBuffer.putFloat(y)
|
||||||
|
index++
|
||||||
|
bufferPosition += 8
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun end(): StreamVertexBuilder {
|
||||||
|
check(index == attributes.size) { "Vertex $vertexIndex is not fully filled (only $index attributes provided, ${attributes.size} required)" }
|
||||||
|
return this@StreamVertexBuilder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class StatefulStreamVertexBuilder(
|
||||||
|
val state: GLStateTracker,
|
||||||
|
val builder: StreamVertexBuilder
|
||||||
|
) : Closeable, IVertexBuilder<StreamVertexBuilder, StreamVertexBuilder.Vertex> by builder {
|
||||||
|
private val vao = state.newVAO()
|
||||||
|
private val vbo = state.newVBO()
|
||||||
|
private val ebo = state.newEBO()
|
||||||
|
|
||||||
|
init {
|
||||||
|
vao.bind()
|
||||||
|
vbo.bind()
|
||||||
|
ebo.bind()
|
||||||
|
|
||||||
|
builder.attributes.apply(vao, true)
|
||||||
|
|
||||||
|
vao.unbind()
|
||||||
|
vbo.unbind()
|
||||||
|
ebo.unbind()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun upload(drawType: Int = GL_DYNAMIC_DRAW) {
|
||||||
|
builder.upload(vbo, ebo, drawType)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bind() = vao.bind()
|
||||||
|
fun unbind() = vao.unbind()
|
||||||
|
|
||||||
|
fun draw(primitives: Int = GL_TRIANGLES) {
|
||||||
|
bind()
|
||||||
|
glDrawElements(primitives, builder.indexCount, builder.elementIndexType, 0L)
|
||||||
|
checkForGLError()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
vao.close()
|
||||||
|
vbo.close()
|
||||||
|
ebo.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3,7 +3,7 @@ package ru.dbotthepony.kstarbound.client.render
|
|||||||
import org.lwjgl.opengl.GL46.*
|
import org.lwjgl.opengl.GL46.*
|
||||||
import ru.dbotthepony.kstarbound.client.gl.GLShaderProgram
|
import ru.dbotthepony.kstarbound.client.gl.GLShaderProgram
|
||||||
import ru.dbotthepony.kstarbound.client.gl.GLVertexArrayObject
|
import ru.dbotthepony.kstarbound.client.gl.GLVertexArrayObject
|
||||||
import ru.dbotthepony.kstarbound.client.gl.VertexBuilder
|
import ru.dbotthepony.kstarbound.client.gl.DynamicVertexBuilder
|
||||||
import ru.dbotthepony.kstarbound.client.gl.checkForGLError
|
import ru.dbotthepony.kstarbound.client.gl.checkForGLError
|
||||||
import ru.dbotthepony.kstarbound.math.FloatMatrix
|
import ru.dbotthepony.kstarbound.math.FloatMatrix
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ class BakedStaticMesh(
|
|||||||
) : AutoCloseable {
|
) : AutoCloseable {
|
||||||
private var onClose = {}
|
private var onClose = {}
|
||||||
|
|
||||||
constructor(programState: BakedProgramState, builder: VertexBuilder) : this(
|
constructor(programState: BakedProgramState, builder: DynamicVertexBuilder) : this(
|
||||||
programState,
|
programState,
|
||||||
builder.indexCount,
|
builder.indexCount,
|
||||||
programState.program.state.newVAO(),
|
programState.program.state.newVAO(),
|
||||||
|
@ -7,7 +7,7 @@ class Camera {
|
|||||||
/**
|
/**
|
||||||
* Позиция этой камеры в Starbound Unit'ах
|
* Позиция этой камеры в Starbound Unit'ах
|
||||||
*/
|
*/
|
||||||
val pos = MutableVector3f()
|
val pos = MutableVector2f()
|
||||||
|
|
||||||
var pressedLeft = false
|
var pressedLeft = false
|
||||||
private set
|
private set
|
||||||
@ -30,22 +30,30 @@ class Camera {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun tick(delta: Double) {
|
val velocity: MutableVector2f get() {
|
||||||
|
val vec = MutableVector2f()
|
||||||
|
|
||||||
if (pressedLeft) {
|
if (pressedLeft) {
|
||||||
pos.x -= (delta * FREEVIEW_SENS).toFloat()
|
vec.x -= (FREEVIEW_SENS).toFloat()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pressedRight) {
|
if (pressedRight) {
|
||||||
pos.x += (delta * FREEVIEW_SENS).toFloat()
|
vec.x += (FREEVIEW_SENS).toFloat()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pressedUp) {
|
if (pressedUp) {
|
||||||
pos.y += (delta * FREEVIEW_SENS).toFloat()
|
vec.y += (FREEVIEW_SENS).toFloat()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pressedDown) {
|
if (pressedDown) {
|
||||||
pos.y -= (delta * FREEVIEW_SENS).toFloat()
|
vec.y -= (FREEVIEW_SENS).toFloat()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return vec
|
||||||
|
}
|
||||||
|
|
||||||
|
fun tick(delta: Double) {
|
||||||
|
pos + velocity * delta.toFloat()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -7,6 +7,7 @@ import ru.dbotthepony.kstarbound.math.FloatMatrix
|
|||||||
import ru.dbotthepony.kstarbound.math.Matrix4f
|
import ru.dbotthepony.kstarbound.math.Matrix4f
|
||||||
import ru.dbotthepony.kstarbound.math.Matrix4fStack
|
import ru.dbotthepony.kstarbound.math.Matrix4fStack
|
||||||
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE
|
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE
|
||||||
|
import ru.dbotthepony.kstarbound.world.CHUNK_SIZEf
|
||||||
import ru.dbotthepony.kstarbound.world.Chunk
|
import ru.dbotthepony.kstarbound.world.Chunk
|
||||||
import ru.dbotthepony.kstarbound.world.ITileChunk
|
import ru.dbotthepony.kstarbound.world.ITileChunk
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
@ -117,6 +118,8 @@ class ChunkRenderer(val state: GLStateTracker, val chunk: Chunk, val world: Clie
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val debugCollisions get() = world?.client?.settings?.debugCollisions ?: false
|
||||||
|
|
||||||
val transform = Matrix4f().translate(x = chunk.pos.x * CHUNK_SIZE * PIXELS_IN_STARBOUND_UNITf, y = chunk.pos.x * CHUNK_SIZE * PIXELS_IN_STARBOUND_UNITf)
|
val transform = Matrix4f().translate(x = chunk.pos.x * CHUNK_SIZE * PIXELS_IN_STARBOUND_UNITf, y = chunk.pos.x * CHUNK_SIZE * PIXELS_IN_STARBOUND_UNITf)
|
||||||
|
|
||||||
private val unloadableBakedMeshes = ArrayList<BakedStaticMesh>()
|
private val unloadableBakedMeshes = ArrayList<BakedStaticMesh>()
|
||||||
@ -217,13 +220,25 @@ class ChunkRenderer(val state: GLStateTracker, val chunk: Chunk, val world: Clie
|
|||||||
foreground.autoUpload()
|
foreground.autoUpload()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun renderDebug() {
|
||||||
|
if (debugCollisions) {
|
||||||
|
state.quadWireframe {
|
||||||
|
it.quad(chunk.aabb.mins.x.toFloat(), chunk.aabb.mins.y.toFloat(), chunk.aabb.maxs.x.toFloat(), chunk.aabb.maxs.y.toFloat())
|
||||||
|
|
||||||
|
for (layer in chunk.foreground.collisionLayers()) {
|
||||||
|
it.quad(layer.mins.x.toFloat(), layer.mins.y.toFloat(), layer.maxs.x.toFloat(), layer.maxs.y.toFloat())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val meshDeque = ArrayDeque<Pair<BakedStaticMesh, Int>>()
|
private val meshDeque = ArrayDeque<Pair<BakedStaticMesh, Int>>()
|
||||||
|
|
||||||
override fun renderLayerFromStack(zPos: Int, transform: Matrix4fStack): Int {
|
override fun renderLayerFromStack(zPos: Int, transform: Matrix4fStack): Int {
|
||||||
if (meshDeque.isEmpty())
|
if (meshDeque.isEmpty())
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
transform.push().translateWithScale(x = chunk.pos.x * CHUNK_SIZE * PIXELS_IN_STARBOUND_UNITf, y = chunk.pos.y * CHUNK_SIZE * PIXELS_IN_STARBOUND_UNITf)
|
transform.push().translateWithScale(x = chunk.pos.x * CHUNK_SIZEf, y = chunk.pos.y * CHUNK_SIZEf)
|
||||||
var pair = meshDeque.last()
|
var pair = meshDeque.last()
|
||||||
|
|
||||||
while (pair.second >= zPos) {
|
while (pair.second >= zPos) {
|
||||||
|
@ -301,7 +301,7 @@ class Font(
|
|||||||
ebo.bind()
|
ebo.bind()
|
||||||
vbo.bind()
|
vbo.bind()
|
||||||
|
|
||||||
val builder = VertexBuilder(GLFlatAttributeList.VERTEX_2D_TEXTURE, VertexType.QUADS)
|
val builder = DynamicVertexBuilder(GLFlatAttributeList.VERTEX_2D_TEXTURE, VertexType.QUADS)
|
||||||
|
|
||||||
builder.quad(0f, 0f, width, height, VertexTransformers.uv())
|
builder.quad(0f, 0f, width, height, VertexTransformers.uv())
|
||||||
builder.upload(vbo, ebo, GL_STATIC_DRAW)
|
builder.upload(vbo, ebo, GL_STATIC_DRAW)
|
||||||
|
@ -15,13 +15,13 @@ import kotlin.collections.HashMap
|
|||||||
|
|
||||||
data class TileLayer(
|
data class TileLayer(
|
||||||
val bakedProgramState: BakedProgramState,
|
val bakedProgramState: BakedProgramState,
|
||||||
val vertexBuilder: VertexBuilder,
|
val vertexBuilder: DynamicVertexBuilder,
|
||||||
val zPos: Int)
|
val zPos: Int)
|
||||||
|
|
||||||
class TileLayerList {
|
class TileLayerList {
|
||||||
private val layers = HashMap<BakedProgramState, ArrayList<TileLayer>>()
|
private val layers = HashMap<BakedProgramState, ArrayList<TileLayer>>()
|
||||||
|
|
||||||
fun getLayer(programState: BakedProgramState, zLevel: Int, compute: () -> VertexBuilder): VertexBuilder {
|
fun getLayer(programState: BakedProgramState, zLevel: Int, compute: () -> DynamicVertexBuilder): DynamicVertexBuilder {
|
||||||
val list = layers.computeIfAbsent(programState) {ArrayList()}
|
val list = layers.computeIfAbsent(programState) {ArrayList()}
|
||||||
|
|
||||||
for (layer in list) {
|
for (layer in list) {
|
||||||
@ -160,25 +160,25 @@ class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) {
|
|||||||
val bakedBackgroundProgramState = state.tileRenderers.background(texture)
|
val bakedBackgroundProgramState = state.tileRenderers.background(texture)
|
||||||
// private var notifiedDepth = false
|
// private var notifiedDepth = false
|
||||||
|
|
||||||
private fun tesselateAt(piece: TileRenderPiece, getter: ITileChunk, builder: VertexBuilder, pos: Vector2i, offset: Vector2i = Vector2i.ZERO) {
|
private fun tesselateAt(piece: TileRenderPiece, getter: ITileChunk, builder: DynamicVertexBuilder, pos: Vector2i, offset: Vector2i = Vector2i.ZERO) {
|
||||||
val fx = pos.x.toFloat()
|
val fx = pos.x.toFloat()
|
||||||
val fy = pos.y.toFloat()
|
val fy = pos.y.toFloat()
|
||||||
|
|
||||||
var a = fx
|
var a = fx
|
||||||
var b = fy
|
var b = fy
|
||||||
|
|
||||||
var c = fx + piece.textureSize.x / BASELINE_TEXTURE_SIZE
|
var c = fx + piece.textureSize.x / PIXELS_IN_STARBOUND_UNITf
|
||||||
var d = fy + piece.textureSize.y / BASELINE_TEXTURE_SIZE
|
var d = fy + piece.textureSize.y / PIXELS_IN_STARBOUND_UNITf
|
||||||
|
|
||||||
if (offset != Vector2i.ZERO) {
|
if (offset != Vector2i.ZERO) {
|
||||||
a += offset.x / BASELINE_TEXTURE_SIZE
|
a += offset.x / PIXELS_IN_STARBOUND_UNITf
|
||||||
|
|
||||||
// в json файлах y указан как положительный вверх,
|
// в json файлах y указан как положительный вверх,
|
||||||
// что соответствует нашему миру
|
// что соответствует нашему миру
|
||||||
b += offset.y / BASELINE_TEXTURE_SIZE
|
b += offset.y / PIXELS_IN_STARBOUND_UNITf
|
||||||
|
|
||||||
c += offset.x / BASELINE_TEXTURE_SIZE
|
c += offset.x / PIXELS_IN_STARBOUND_UNITf
|
||||||
d += offset.y / BASELINE_TEXTURE_SIZE
|
d += offset.y / PIXELS_IN_STARBOUND_UNITf
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tile.render.variants == 0 || piece.texture != null || piece.variantStride == null) {
|
if (tile.render.variants == 0 || piece.texture != null || piece.variantStride == null) {
|
||||||
@ -186,10 +186,10 @@ class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) {
|
|||||||
val (u1, v1) = texture.pixelToUV(piece.texturePosition + piece.textureSize)
|
val (u1, v1) = texture.pixelToUV(piece.texturePosition + piece.textureSize)
|
||||||
|
|
||||||
builder.quadZ(
|
builder.quadZ(
|
||||||
a * PIXELS_IN_STARBOUND_UNITf,
|
a,
|
||||||
b * PIXELS_IN_STARBOUND_UNITf,
|
b,
|
||||||
c * PIXELS_IN_STARBOUND_UNITf,
|
c,
|
||||||
d * PIXELS_IN_STARBOUND_UNITf,
|
d,
|
||||||
Z_LEVEL, VertexTransformers.uv(u0, v1, u1, v0))
|
Z_LEVEL, VertexTransformers.uv(u0, v1, u1, v0))
|
||||||
} else {
|
} else {
|
||||||
val variant = (getter.randomDoubleFor(pos) * tile.render.variants).toInt()
|
val variant = (getter.randomDoubleFor(pos) * tile.render.variants).toInt()
|
||||||
@ -198,15 +198,15 @@ class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) {
|
|||||||
val (u1, v1) = texture.pixelToUV(piece.texturePosition + piece.textureSize + piece.variantStride * variant)
|
val (u1, v1) = texture.pixelToUV(piece.texturePosition + piece.textureSize + piece.variantStride * variant)
|
||||||
|
|
||||||
builder.quadZ(
|
builder.quadZ(
|
||||||
a * PIXELS_IN_STARBOUND_UNITf,
|
a,
|
||||||
b * PIXELS_IN_STARBOUND_UNITf,
|
b,
|
||||||
c * PIXELS_IN_STARBOUND_UNITf,
|
c,
|
||||||
d * PIXELS_IN_STARBOUND_UNITf,
|
d,
|
||||||
Z_LEVEL, VertexTransformers.uv(u0, v1, u1, v0))
|
Z_LEVEL, VertexTransformers.uv(u0, v1, u1, v0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun tesselatePiece(matchPiece: TileRenderMatchPiece, getter: ITileChunk, layers: TileLayerList, pos: Vector2i, thisBuilder: VertexBuilder, background: Boolean): TileRenderTesselateResult {
|
private fun tesselatePiece(matchPiece: TileRenderMatchPiece, getter: ITileChunk, layers: TileLayerList, pos: Vector2i, thisBuilder: DynamicVertexBuilder, background: Boolean): TileRenderTesselateResult {
|
||||||
if (matchPiece.test(getter, tile, pos)) {
|
if (matchPiece.test(getter, tile, pos)) {
|
||||||
for (renderPiece in matchPiece.pieces) {
|
for (renderPiece in matchPiece.pieces) {
|
||||||
if (renderPiece.piece.texture != null) {
|
if (renderPiece.piece.texture != null) {
|
||||||
@ -219,7 +219,7 @@ class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tesselateAt(renderPiece.piece, getter, layers.getLayer(program, tile.render.zLevel) {
|
tesselateAt(renderPiece.piece, getter, layers.getLayer(program, tile.render.zLevel) {
|
||||||
return@getLayer VertexBuilder(GLFlatAttributeList.VERTEX_TEXTURE, VertexType.QUADS)
|
return@getLayer DynamicVertexBuilder(GLFlatAttributeList.VERTEX_TEXTURE, VertexType.QUADS)
|
||||||
}, pos, renderPiece.offset)
|
}, pos, renderPiece.offset)
|
||||||
} else {
|
} else {
|
||||||
tesselateAt(renderPiece.piece, getter, thisBuilder, pos, renderPiece.offset)
|
tesselateAt(renderPiece.piece, getter, thisBuilder, pos, renderPiece.offset)
|
||||||
@ -259,7 +259,7 @@ class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) {
|
|||||||
tile.render.renderTemplate ?: return
|
tile.render.renderTemplate ?: return
|
||||||
|
|
||||||
val builder = layers.getLayer(if (background) bakedBackgroundProgramState else bakedProgramState, tile.render.zLevel) {
|
val builder = layers.getLayer(if (background) bakedBackgroundProgramState else bakedProgramState, tile.render.zLevel) {
|
||||||
return@getLayer VertexBuilder(GLFlatAttributeList.VERTEX_TEXTURE, VertexType.QUADS)
|
return@getLayer DynamicVertexBuilder(GLFlatAttributeList.VERTEX_TEXTURE, VertexType.QUADS)
|
||||||
}
|
}
|
||||||
|
|
||||||
for ((_, matcher) in tile.render.renderTemplate.matches) {
|
for ((_, matcher) in tile.render.renderTemplate.matches) {
|
||||||
@ -274,7 +274,6 @@ class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val BASELINE_TEXTURE_SIZE = 8f
|
|
||||||
const val Z_LEVEL = 10f
|
const val Z_LEVEL = 10f
|
||||||
private val LOGGER = LogManager.getLogger()
|
private val LOGGER = LogManager.getLogger()
|
||||||
}
|
}
|
||||||
|
476
src/main/kotlin/ru/dbotthepony/kstarbound/math/AABB.kt
Normal file
476
src/main/kotlin/ru/dbotthepony/kstarbound/math/AABB.kt
Normal file
@ -0,0 +1,476 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.math
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.api.IStruct2d
|
||||||
|
import ru.dbotthepony.kstarbound.world.ChunkPos
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
|
data class IntersectionTime(
|
||||||
|
val invEntry: Vector2d,
|
||||||
|
val invExit: Vector2d,
|
||||||
|
val entry: Vector2d,
|
||||||
|
val exit: Vector2d,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
val ZERO = IntersectionTime(Vector2d.ZERO, Vector2d.ZERO, Vector2d.ZERO, Vector2d.ZERO)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class SweepResult(
|
||||||
|
val normal: Vector2d,
|
||||||
|
val collisionTime: Double,
|
||||||
|
val intersectionTime: IntersectionTime
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
val ZERO = SweepResult(Vector2d.ZERO, 1.0, IntersectionTime.ZERO)
|
||||||
|
val INTERSECT = SweepResult(Vector2d.ZERO, 0.0, IntersectionTime.ZERO)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Класс для описания Axis Aligned Bounding Box, двумя векторами,
|
||||||
|
* где [mins] - нижняя левая точка,
|
||||||
|
* [maxs] - верхняя правая
|
||||||
|
*/
|
||||||
|
data class AABB(val mins: Vector2d, val maxs: Vector2d) {
|
||||||
|
init {
|
||||||
|
require(mins.x < maxs.x) { "mins.x ${mins.x} is more or equal to maxs.x ${maxs.x}" }
|
||||||
|
require(mins.y < maxs.y) { "mins.y ${mins.y} is more or equal to maxs.y ${maxs.y}" }
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun plus(other: AABB) = AABB(mins + other.mins, maxs + other.maxs)
|
||||||
|
operator fun minus(other: AABB) = AABB(mins - other.mins, maxs - other.maxs)
|
||||||
|
operator fun times(other: AABB) = AABB(mins * other.mins, maxs * other.maxs)
|
||||||
|
operator fun div(other: AABB) = AABB(mins / other.mins, maxs / other.maxs)
|
||||||
|
|
||||||
|
operator fun plus(other: Vector2d) = AABB(mins + other, maxs + other)
|
||||||
|
operator fun minus(other: Vector2d) = AABB(mins - other, maxs - other)
|
||||||
|
operator fun times(other: Vector2d) = AABB(mins * other, maxs * other)
|
||||||
|
operator fun div(other: Vector2d) = AABB(mins / other, maxs / other)
|
||||||
|
|
||||||
|
operator fun plus(other: Double) = AABB(mins + other, maxs + other)
|
||||||
|
operator fun minus(other: Double) = AABB(mins - other, maxs - other)
|
||||||
|
operator fun times(other: Double) = AABB(mins * other, maxs * other)
|
||||||
|
operator fun div(other: Double) = AABB(mins / other, maxs / other)
|
||||||
|
|
||||||
|
val xSpan get() = maxs.x - mins.x
|
||||||
|
val ySpan get() = maxs.y - mins.y
|
||||||
|
val centre get() = mins + maxs * 0.5
|
||||||
|
|
||||||
|
val A get() = mins
|
||||||
|
val B get() = Vector2d(mins.x, maxs.y)
|
||||||
|
val C get() = maxs
|
||||||
|
val D get() = Vector2d(maxs.x, mins.y)
|
||||||
|
|
||||||
|
val bottomLeft get() = A
|
||||||
|
val topLeft get() = B
|
||||||
|
val topRight get() = C
|
||||||
|
val bottomRight get() = D
|
||||||
|
|
||||||
|
val width get() = (maxs.x - mins.x) / 2.0
|
||||||
|
val height get() = (maxs.y - mins.y) / 2.0
|
||||||
|
|
||||||
|
val diameter get() = mins.distance(maxs)
|
||||||
|
val radius get() = diameter / 2.0
|
||||||
|
|
||||||
|
fun isInside(point: Vector2d): Boolean {
|
||||||
|
return point.x in mins.x .. maxs.x && point.y in mins.y .. maxs.y
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Есть ли пересечение между этим AABB и [other]
|
||||||
|
*
|
||||||
|
* Считается, что они пересекаются, даже если у них просто равна одна из осей
|
||||||
|
*/
|
||||||
|
fun intersect(other: AABB): Boolean {
|
||||||
|
val intersectX: Boolean
|
||||||
|
|
||||||
|
if (xSpan <= other.xSpan)
|
||||||
|
intersectX = mins.x in other.mins.x .. other.maxs.x || maxs.x in other.mins.x .. other.maxs.x
|
||||||
|
else
|
||||||
|
intersectX = other.mins.x in mins.x .. maxs.x || other.maxs.x in mins.x .. maxs.x
|
||||||
|
|
||||||
|
if (!intersectX)
|
||||||
|
return false
|
||||||
|
|
||||||
|
val intersectY: Boolean
|
||||||
|
|
||||||
|
if (ySpan <= other.ySpan)
|
||||||
|
intersectY = mins.y in other.mins.y .. other.maxs.y || maxs.y in other.mins.y .. other.maxs.y
|
||||||
|
else
|
||||||
|
intersectY = other.mins.y in mins.y .. maxs.y || other.maxs.y in mins.y .. maxs.y
|
||||||
|
|
||||||
|
return intersectY
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Есть ли пересечение между этим AABB и [other]
|
||||||
|
*
|
||||||
|
* Считается, что они НЕ пересекаются, если у них просто равна одна из осей
|
||||||
|
*/
|
||||||
|
fun intersectWeak(other: AABB): Boolean {
|
||||||
|
if (maxs.x == other.mins.x || mins.x == other.maxs.x || maxs.y == other.mins.y || mins.y == other.maxs.y)
|
||||||
|
return false
|
||||||
|
|
||||||
|
val intersectX: Boolean
|
||||||
|
|
||||||
|
if (xSpan <= other.xSpan)
|
||||||
|
intersectX = mins.x in other.mins.x .. other.maxs.x || maxs.x in other.mins.x .. other.maxs.x
|
||||||
|
else
|
||||||
|
intersectX = other.mins.x in mins.x .. maxs.x || other.maxs.x in mins.x .. maxs.x
|
||||||
|
|
||||||
|
if (!intersectX)
|
||||||
|
return false
|
||||||
|
|
||||||
|
val intersectY: Boolean
|
||||||
|
|
||||||
|
if (ySpan <= other.ySpan)
|
||||||
|
intersectY = mins.y in other.mins.y .. other.maxs.y || maxs.y in other.mins.y .. other.maxs.y
|
||||||
|
else
|
||||||
|
intersectY = other.mins.y in mins.y .. maxs.y || other.maxs.y in mins.y .. maxs.y
|
||||||
|
|
||||||
|
return intersectY
|
||||||
|
}
|
||||||
|
|
||||||
|
fun intersectionDepth(other: AABB): Vector2d {
|
||||||
|
val xDepth: Double
|
||||||
|
val yDepth: Double
|
||||||
|
|
||||||
|
val thisCentre = centre
|
||||||
|
val otherCentre = other.centre
|
||||||
|
|
||||||
|
if (thisCentre.x > otherCentre.x) {
|
||||||
|
// считаем, что мы вошли справа
|
||||||
|
xDepth = mins.x - other.maxs.x
|
||||||
|
} else {
|
||||||
|
// считаем, что мы вошли слева
|
||||||
|
xDepth = maxs.x - other.mins.x
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thisCentre.y > otherCentre.y) {
|
||||||
|
// считаем, что мы вошли сверху
|
||||||
|
yDepth = mins.y - other.maxs.y
|
||||||
|
} else {
|
||||||
|
// считаем, что мы вошли снизу
|
||||||
|
yDepth = maxs.x - other.mins.x
|
||||||
|
}
|
||||||
|
|
||||||
|
return Vector2d(xDepth, yDepth)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pushOutFrom(other: AABB): Vector2d {
|
||||||
|
if (!intersect(other))
|
||||||
|
return Vector2d.ZERO
|
||||||
|
|
||||||
|
val depth = intersectionDepth(other)
|
||||||
|
|
||||||
|
if (depth.x.absoluteValue < depth.y.absoluteValue) {
|
||||||
|
return Vector2d(x = depth.x)
|
||||||
|
} else {
|
||||||
|
return Vector2d(y = depth.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Рассчитывает "время" пересечения
|
||||||
|
*
|
||||||
|
* https://www.gamedev.net/tutorials/programming/general-and-gameplay-programming/swept-aabb-collision-detection-and-response-r3084/
|
||||||
|
*
|
||||||
|
* Исправленный комментатором той же статьи от hypernewbie
|
||||||
|
*/
|
||||||
|
fun intersectionTime(other: AABB, velocity: Vector2d): IntersectionTime {
|
||||||
|
val xInvEntry: Double
|
||||||
|
val yInvEntry: Double
|
||||||
|
val xInvExit: Double
|
||||||
|
val yInvExit: Double
|
||||||
|
|
||||||
|
if (velocity.x > 0.0) {
|
||||||
|
xInvEntry = other.mins.x - maxs.x
|
||||||
|
xInvExit = other.maxs.x - mins.x
|
||||||
|
} else {
|
||||||
|
xInvEntry = other.maxs.x - mins.x
|
||||||
|
xInvExit = other.mins.x - maxs.x
|
||||||
|
}
|
||||||
|
|
||||||
|
if (velocity.y > 0.0) {
|
||||||
|
yInvEntry = other.mins.y - maxs.y
|
||||||
|
yInvExit = other.maxs.y - mins.y
|
||||||
|
} else {
|
||||||
|
yInvEntry = other.maxs.y - mins.y
|
||||||
|
yInvExit = other.mins.y - maxs.y
|
||||||
|
}
|
||||||
|
|
||||||
|
var xEntry: Double
|
||||||
|
var yEntry: Double
|
||||||
|
val xExit: Double
|
||||||
|
val yExit: Double
|
||||||
|
|
||||||
|
if (velocity.x == 0.0) {
|
||||||
|
xEntry = Double.NEGATIVE_INFINITY
|
||||||
|
xExit = Double.POSITIVE_INFINITY
|
||||||
|
} else {
|
||||||
|
xEntry = xInvEntry / velocity.x
|
||||||
|
xExit = xInvExit / velocity.x
|
||||||
|
}
|
||||||
|
|
||||||
|
if (velocity.y == 0.0) {
|
||||||
|
yEntry = Double.NEGATIVE_INFINITY
|
||||||
|
yExit = Double.POSITIVE_INFINITY
|
||||||
|
} else {
|
||||||
|
yEntry = yInvEntry / velocity.y
|
||||||
|
yExit = yInvExit / velocity.y
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yEntry > 1.0) yEntry = Double.NEGATIVE_INFINITY
|
||||||
|
if (xEntry > 1.0) xEntry = Double.NEGATIVE_INFINITY
|
||||||
|
|
||||||
|
return IntersectionTime(
|
||||||
|
Vector2d(xInvEntry, yInvEntry),
|
||||||
|
Vector2d(xInvExit, yInvExit),
|
||||||
|
Vector2d(xEntry, yEntry),
|
||||||
|
Vector2d(xExit, yExit),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Рассчитывает нормаль пересечения и процент пути ("время"), на котором произошло столкновение.
|
||||||
|
*
|
||||||
|
* Если столкновение не произошло, то возвращается [SweepResult.ZERO]
|
||||||
|
*
|
||||||
|
* Внимание: Если пересечение уже произошло (т.е. другой AABB пересекается с this), то данный метод
|
||||||
|
* вернёт заведомо ложный результат (т.е. "нет пересечения")
|
||||||
|
*
|
||||||
|
* https://www.gamedev.net/tutorials/programming/general-and-gameplay-programming/swept-aabb-collision-detection-and-response-r3084/
|
||||||
|
*
|
||||||
|
* Исправленный комментатором той же статьи от hypernewbie
|
||||||
|
*/
|
||||||
|
fun sweep(other: AABB, velocity: Vector2d): SweepResult {
|
||||||
|
val time = intersectionTime(other, velocity)
|
||||||
|
val (near, far, entry, exit) = time
|
||||||
|
val (xEntry, yEntry) = entry
|
||||||
|
val (xExit, yExit) = exit
|
||||||
|
|
||||||
|
val entryTime = xEntry.coerceAtLeast(yEntry)
|
||||||
|
val exitTime = xExit.coerceAtLeast(yExit)
|
||||||
|
|
||||||
|
// гарантированно нет столкновения
|
||||||
|
if (entryTime > exitTime || xEntry < 0.0 && yEntry < 0.0) {
|
||||||
|
return SweepResult.ZERO
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xEntry < 0.0) {
|
||||||
|
if (maxs.x < other.mins.x || mins.x > other.maxs.x)
|
||||||
|
return SweepResult.ZERO
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yEntry < 0.0) {
|
||||||
|
if (maxs.y < other.mins.y || mins.y > other.maxs.y)
|
||||||
|
return SweepResult.ZERO
|
||||||
|
}
|
||||||
|
|
||||||
|
val (xInvEntry, yInvEntry) = near
|
||||||
|
val normal: Vector2d
|
||||||
|
|
||||||
|
if (xEntry > yEntry) {
|
||||||
|
if (xInvEntry < 0.0) {
|
||||||
|
normal = Vector2d.RIGHT
|
||||||
|
} else {
|
||||||
|
normal = Vector2d.LEFT
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (yInvEntry < 0.0) {
|
||||||
|
normal = Vector2d.UP
|
||||||
|
} else {
|
||||||
|
normal = Vector2d.DOWN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SweepResult(normal, entryTime, time)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Рассчитывает нормаль пересечения и процент пути ("время"), на котором произошло столкновение.
|
||||||
|
*
|
||||||
|
* Если столкновение не произошло, то возвращается [SweepResult.ZERO]
|
||||||
|
*
|
||||||
|
* Если данный AABB уже столкнулся с [other], возвращается [SweepResult.INTERSECT]
|
||||||
|
*
|
||||||
|
* https://www.gamedev.net/tutorials/programming/general-and-gameplay-programming/swept-aabb-collision-detection-and-response-r3084/
|
||||||
|
*/
|
||||||
|
fun safeSweep(other: AABB, velocity: Vector2d): SweepResult {
|
||||||
|
if (intersect(other)) {
|
||||||
|
return SweepResult.INTERSECT
|
||||||
|
}
|
||||||
|
|
||||||
|
return sweep(other, velocity)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun encasingIntAABB(): AABBi {
|
||||||
|
return AABBi(
|
||||||
|
Vector2i(roundByAbsoluteValue(mins.x), roundByAbsoluteValue(mins.y)),
|
||||||
|
Vector2i(roundByAbsoluteValue(maxs.x), roundByAbsoluteValue(maxs.y)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun encasingChunkPosAABB(): AABBi {
|
||||||
|
return AABBi(
|
||||||
|
Vector2i(ChunkPos.tileToChunkComponent(roundByAbsoluteValue(mins.x)), ChunkPos.tileToChunkComponent(roundByAbsoluteValue(mins.y))),
|
||||||
|
Vector2i(ChunkPos.tileToChunkComponent(roundByAbsoluteValue(maxs.x)), ChunkPos.tileToChunkComponent(roundByAbsoluteValue(maxs.y))),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Возвращает AABB, который содержит в себе оба AABB
|
||||||
|
*/
|
||||||
|
fun combine(other: AABB): AABB {
|
||||||
|
val minX = mins.x.coerceAtMost(other.mins.x)
|
||||||
|
val minY = mins.y.coerceAtMost(other.mins.y)
|
||||||
|
val maxX = maxs.x.coerceAtLeast(other.maxs.x)
|
||||||
|
val maxY = maxs.y.coerceAtLeast(other.maxs.y)
|
||||||
|
|
||||||
|
return AABB(Vector2d(minX, minY), Vector2d(maxX, maxY))
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun rectangle(pos: IStruct2d, width: Double, height: Double = width): AABB {
|
||||||
|
val (x, y) = pos
|
||||||
|
|
||||||
|
return AABB(
|
||||||
|
Vector2d(x - width / 2.0, y - height / 2.0),
|
||||||
|
Vector2d(x + width / 2.0, y + height / 2.0),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class AABBi(val mins: Vector2i, val maxs: Vector2i) {
|
||||||
|
init {
|
||||||
|
require(mins.x <= maxs.x) { "mins.x ${mins.x} is more than maxs.x ${maxs.x}" }
|
||||||
|
require(mins.y <= maxs.y) { "mins.y ${mins.y} is more than maxs.y ${maxs.y}" }
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun plus(other: AABBi) = AABBi(mins + other.mins, maxs + other.maxs)
|
||||||
|
operator fun minus(other: AABBi) = AABBi(mins - other.mins, maxs - other.maxs)
|
||||||
|
operator fun times(other: AABBi) = AABBi(mins * other.mins, maxs * other.maxs)
|
||||||
|
operator fun div(other: AABBi) = AABBi(mins / other.mins, maxs / other.maxs)
|
||||||
|
|
||||||
|
operator fun plus(other: Vector2i) = AABBi(mins + other, maxs + other)
|
||||||
|
operator fun minus(other: Vector2i) = AABBi(mins - other, maxs - other)
|
||||||
|
operator fun times(other: Vector2i) = AABBi(mins * other, maxs * other)
|
||||||
|
operator fun div(other: Vector2i) = AABBi(mins / other, maxs / other)
|
||||||
|
|
||||||
|
operator fun plus(other: Int) = AABBi(mins + other, maxs + other)
|
||||||
|
operator fun minus(other: Int) = AABBi(mins - other, maxs - other)
|
||||||
|
operator fun times(other: Int) = AABBi(mins * other, maxs * other)
|
||||||
|
operator fun div(other: Int) = AABBi(mins / other, maxs / other)
|
||||||
|
|
||||||
|
val xSpan get() = maxs.x - mins.x
|
||||||
|
val ySpan get() = maxs.y - mins.y
|
||||||
|
val centre get() = mins.toDoubleVector() + maxs.toDoubleVector() * 0.5
|
||||||
|
|
||||||
|
val A get() = mins
|
||||||
|
val B get() = Vector2i(mins.x, maxs.y)
|
||||||
|
val C get() = maxs
|
||||||
|
val D get() = Vector2i(maxs.x, mins.y)
|
||||||
|
|
||||||
|
val bottomLeft get() = A
|
||||||
|
val topLeft get() = B
|
||||||
|
val topRight get() = C
|
||||||
|
val bottomRight get() = D
|
||||||
|
|
||||||
|
val width get() = (maxs.x - mins.x) / 2
|
||||||
|
val height get() = (maxs.y - mins.y) / 2
|
||||||
|
|
||||||
|
val diameter get() = mins.distance(maxs)
|
||||||
|
val radius get() = diameter / 2.0
|
||||||
|
|
||||||
|
fun isInside(point: Vector2i): Boolean {
|
||||||
|
return point.x in mins.x .. maxs.x && point.y in mins.y .. maxs.y
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Есть ли пересечение между этим AABB и [other]
|
||||||
|
*
|
||||||
|
* Считается, что они пересекаются, даже если у них просто равна одна из осей
|
||||||
|
*/
|
||||||
|
fun intersect(other: AABBi): Boolean {
|
||||||
|
val intersectX: Boolean
|
||||||
|
|
||||||
|
if (xSpan <= other.xSpan)
|
||||||
|
intersectX = mins.x in other.mins.x .. other.maxs.x || maxs.x in other.mins.x .. other.maxs.x
|
||||||
|
else
|
||||||
|
intersectX = other.mins.x in mins.x .. maxs.x || other.maxs.x in mins.x .. maxs.x
|
||||||
|
|
||||||
|
if (!intersectX)
|
||||||
|
return false
|
||||||
|
|
||||||
|
val intersectY: Boolean
|
||||||
|
|
||||||
|
if (ySpan <= other.ySpan)
|
||||||
|
intersectY = mins.y in other.mins.y .. other.maxs.y || maxs.y in other.mins.y .. other.maxs.y
|
||||||
|
else
|
||||||
|
intersectY = other.mins.y in mins.y .. maxs.y || other.maxs.y in mins.y .. maxs.y
|
||||||
|
|
||||||
|
return intersectY
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Есть ли пересечение между этим AABB и [other]
|
||||||
|
*
|
||||||
|
* Считается, что они НЕ пересекаются, если у них просто равна одна из осей
|
||||||
|
*/
|
||||||
|
fun intersectWeak(other: AABBi): Boolean {
|
||||||
|
if (maxs.x == other.mins.x || mins.x == other.maxs.x || maxs.y == other.mins.y || mins.y == other.maxs.y)
|
||||||
|
return false
|
||||||
|
|
||||||
|
val intersectX: Boolean
|
||||||
|
|
||||||
|
if (xSpan <= other.xSpan)
|
||||||
|
intersectX = mins.x in other.mins.x .. other.maxs.x || maxs.x in other.mins.x .. other.maxs.x
|
||||||
|
else
|
||||||
|
intersectX = other.mins.x in mins.x .. maxs.x || other.maxs.x in mins.x .. maxs.x
|
||||||
|
|
||||||
|
if (!intersectX)
|
||||||
|
return false
|
||||||
|
|
||||||
|
val intersectY: Boolean
|
||||||
|
|
||||||
|
if (ySpan <= other.ySpan)
|
||||||
|
intersectY = mins.y in other.mins.y .. other.maxs.y || maxs.y in other.mins.y .. other.maxs.y
|
||||||
|
else
|
||||||
|
intersectY = other.mins.y in mins.y .. maxs.y || other.maxs.y in mins.y .. maxs.y
|
||||||
|
|
||||||
|
return intersectY
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toDoubleAABB() = AABB(mins.toDoubleVector(), maxs.toDoubleVector())
|
||||||
|
|
||||||
|
private inner class Iterator<T>(private val factory: (x: Int, y: Int) -> T) : kotlin.collections.Iterator<T> {
|
||||||
|
private var x = mins.x
|
||||||
|
private var y = mins.y
|
||||||
|
private var next = true
|
||||||
|
|
||||||
|
override fun hasNext(): Boolean {
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun next(): T {
|
||||||
|
if (!next)
|
||||||
|
throw IllegalStateException()
|
||||||
|
|
||||||
|
val obj = factory.invoke(x++, y)
|
||||||
|
|
||||||
|
if (x > maxs.x) {
|
||||||
|
x = mins.x
|
||||||
|
|
||||||
|
if (++y > maxs.y) {
|
||||||
|
next = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val vectors: kotlin.collections.Iterator<Vector2i> get() = Iterator(::Vector2i)
|
||||||
|
val chunkPositions: kotlin.collections.Iterator<ChunkPos> get() = Iterator(::ChunkPos)
|
||||||
|
}
|
@ -21,6 +21,10 @@ interface IMatrixLikeFloat : IMatrixLike {
|
|||||||
operator fun get(row: Int, column: Int): Float
|
operator fun get(row: Int, column: Int): Float
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IMatrixLikeDouble : IMatrixLike {
|
||||||
|
operator fun get(row: Int, column: Int): Double
|
||||||
|
}
|
||||||
|
|
||||||
interface IMatrix : IMatrixLike {
|
interface IMatrix : IMatrixLike {
|
||||||
operator fun plus(other: IMatrix): IMatrix
|
operator fun plus(other: IMatrix): IMatrix
|
||||||
operator fun minus(other: IMatrix): IMatrix
|
operator fun minus(other: IMatrix): IMatrix
|
||||||
|
27
src/main/kotlin/ru/dbotthepony/kstarbound/math/Utils.kt
Normal file
27
src/main/kotlin/ru/dbotthepony/kstarbound/math/Utils.kt
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.math
|
||||||
|
|
||||||
|
fun lerp(t: Double, a: Double, b: Double): Double {
|
||||||
|
return a * (1.0 - t) + b * t
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Выполняет преобразование [value] типа [Double] в [Int] так,
|
||||||
|
* что выходной [Int] всегда будет больше или равен по модулю [value]
|
||||||
|
*/
|
||||||
|
fun roundByAbsoluteValue(value: Double): Int {
|
||||||
|
if (value > 0.0) {
|
||||||
|
if (value % 1.0 != 0.0) {
|
||||||
|
return value.toInt() + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.toInt()
|
||||||
|
} else if (value == -0.0 || value == 0.0) {
|
||||||
|
return 0
|
||||||
|
} else {
|
||||||
|
if (value % 1.0 != -0.0) {
|
||||||
|
return value.toInt() - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.toInt()
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,17 @@
|
|||||||
package ru.dbotthepony.kstarbound.math
|
package ru.dbotthepony.kstarbound.math
|
||||||
|
|
||||||
import com.google.gson.JsonArray
|
import com.google.gson.JsonArray
|
||||||
import ru.dbotthepony.kstarbound.api.IStruct2f
|
import ru.dbotthepony.kstarbound.api.*
|
||||||
import ru.dbotthepony.kstarbound.api.IStruct2i
|
|
||||||
import ru.dbotthepony.kstarbound.api.IStruct3f
|
|
||||||
import ru.dbotthepony.kstarbound.api.IStruct4f
|
|
||||||
import kotlin.math.cos
|
import kotlin.math.cos
|
||||||
|
import kotlin.math.pow
|
||||||
import kotlin.math.sin
|
import kotlin.math.sin
|
||||||
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
|
// Так как у нас нет шаблонов ни в Java, ни в Kotlin
|
||||||
|
// а дженерики вызывают autoboxing
|
||||||
|
// приходится создавать "бетонные" реализации для каждого вида вектора
|
||||||
|
|
||||||
|
// а ведь компилятор мог бы это генерировать.
|
||||||
|
|
||||||
abstract class IVector2i<T : IVector2i<T>> : IMatrixLike, IMatrixLikeInt, IStruct2i {
|
abstract class IVector2i<T : IVector2i<T>> : IMatrixLike, IMatrixLikeInt, IStruct2i {
|
||||||
override val columns = 1
|
override val columns = 1
|
||||||
@ -20,13 +25,76 @@ abstract class IVector2i<T : IVector2i<T>> : IMatrixLike, IMatrixLikeInt, IStruc
|
|||||||
operator fun times(other: IVector2i<*>) = make(x * other.x, y * other.y)
|
operator fun times(other: IVector2i<*>) = make(x * other.x, y * other.y)
|
||||||
operator fun div(other: IVector2i<*>) = make(x / other.x, y / other.y)
|
operator fun div(other: IVector2i<*>) = make(x / other.x, y / other.y)
|
||||||
|
|
||||||
|
//operator fun plus(other: IVector2f<*>) = Vector2f(x + other.x, y + other.y)
|
||||||
|
//operator fun minus(other: IVector2f<*>) = Vector2f(x - other.x, y - other.y)
|
||||||
|
//operator fun times(other: IVector2f<*>) = Vector2f(x * other.x, y * other.y)
|
||||||
|
//operator fun div(other: IVector2f<*>) = Vector2f(x / other.x, y / other.y)
|
||||||
|
|
||||||
|
//operator fun plus(other: IVector2d<*>) = Vector2d(x + other.x, y + other.y)
|
||||||
|
//operator fun minus(other: IVector2d<*>) = Vector2d(x - other.x, y - other.y)
|
||||||
|
//operator fun times(other: IVector2d<*>) = Vector2d(x * other.x, y * other.y)
|
||||||
|
//operator fun div(other: IVector2d<*>) = Vector2d(x / other.x, y / other.y)
|
||||||
|
|
||||||
operator fun div(other: Int) = make(x / other, y / other)
|
operator fun div(other: Int) = make(x / other, y / other)
|
||||||
operator fun times(other: Int) = make(x * other, y * other)
|
operator fun times(other: Int) = make(x * other, y * other)
|
||||||
operator fun minus(other: Int) = make(x - other, y - other)
|
operator fun minus(other: Int) = make(x - other, y - other)
|
||||||
operator fun plus(other: Int) = make(x + other, y + other)
|
operator fun plus(other: Int) = make(x + other, y + other)
|
||||||
|
|
||||||
|
//operator fun div(other: Float) = Vector2f(x / other, y / other)
|
||||||
|
//operator fun times(other: Float) = Vector2f(x * other, y * other)
|
||||||
|
//operator fun minus(other: Float) = Vector2f(x - other, y - other)
|
||||||
|
//operator fun plus(other: Float) = Vector2f(x + other, y + other)
|
||||||
|
|
||||||
|
//operator fun div(other: Double) = Vector2d(x / other, y / other)
|
||||||
|
//operator fun times(other: Double) = Vector2d(x * other, y * other)
|
||||||
|
//operator fun minus(other: Double) = Vector2d(x - other, y - other)
|
||||||
|
//operator fun plus(other: Double) = Vector2d(x + other, y + other)
|
||||||
|
|
||||||
operator fun unaryMinus() = make(-x, -y)
|
operator fun unaryMinus() = make(-x, -y)
|
||||||
|
|
||||||
|
val length get() = sqrt(x.toDouble() * x.toDouble() + y.toDouble() * y.toDouble())
|
||||||
|
|
||||||
|
fun dotProduct(other: IVector2i<*>): Double {
|
||||||
|
return other.x.toDouble() * x.toDouble() + other.y.toDouble() * y.toDouble()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dotProduct(other: IVector2f<*>): Double {
|
||||||
|
return other.x.toDouble() * x.toDouble() + other.y.toDouble() * y.toDouble()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dotProduct(other: IVector2d<*>): Double {
|
||||||
|
return other.x * x.toDouble() + other.y * y.toDouble()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun InvDotProduct(other: IVector2i<*>): Double {
|
||||||
|
return other.x.toDouble() * y.toDouble() + other.y.toDouble() * x.toDouble()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun InvDotProduct(other: IVector2f<*>): Double {
|
||||||
|
return other.x.toDouble() * y.toDouble() + other.y.toDouble() * x.toDouble()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun InvDotProduct(other: IVector2d<*>): Double {
|
||||||
|
return other.x * y.toDouble() + other.y * x.toDouble()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun distance(other: IVector2i<*>): Double {
|
||||||
|
return sqrt((x - other.x).toDouble().pow(2.0) + (y - other.y).toDouble().pow(2.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun distance(other: IVector2f<*>): Double {
|
||||||
|
return sqrt((x - other.x).toDouble().pow(2.0) + (y - other.y).toDouble().pow(2.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun distance(other: IVector2d<*>): Double {
|
||||||
|
return sqrt((x - other.x).toDouble().pow(2.0) + (y - other.y).toDouble().pow(2.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
val normalized: Vector2d get() {
|
||||||
|
val len = length
|
||||||
|
return Vector2d(x / len, y / len)
|
||||||
|
}
|
||||||
|
|
||||||
fun left() = make(x - 1, y)
|
fun left() = make(x - 1, y)
|
||||||
fun right() = make(x + 1, y)
|
fun right() = make(x + 1, y)
|
||||||
fun up() = make(x, y + 1)
|
fun up() = make(x, y + 1)
|
||||||
@ -45,6 +113,9 @@ abstract class IVector2i<T : IVector2i<T>> : IMatrixLike, IMatrixLikeInt, IStruc
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected abstract fun make(x: Int, y: Int): T
|
protected abstract fun make(x: Int, y: Int): T
|
||||||
|
|
||||||
|
fun toFloatVector() = Vector2f(x.toFloat(), y.toFloat())
|
||||||
|
fun toDoubleVector() = Vector2d(x.toDouble(), y.toDouble())
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Vector2i(override val x: Int = 0, override val y: Int = 0) : IVector2i<Vector2i>() {
|
data class Vector2i(override val x: Int = 0, override val y: Int = 0) : IVector2i<Vector2i>() {
|
||||||
@ -101,6 +172,49 @@ abstract class IVector2f<T : IVector2f<T>> : IMatrixLike, IMatrixLikeFloat, IStr
|
|||||||
fun up() = make(x, y + 1)
|
fun up() = make(x, y + 1)
|
||||||
fun down() = make(x, y - 1)
|
fun down() = make(x, y - 1)
|
||||||
|
|
||||||
|
val length get() = sqrt(x.toDouble() * x.toDouble() + y.toDouble() * y.toDouble())
|
||||||
|
|
||||||
|
fun dotProduct(other: IVector2i<*>): Double {
|
||||||
|
return other.x.toDouble() * x.toDouble() + other.y.toDouble() * y.toDouble()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dotProduct(other: IVector2f<*>): Double {
|
||||||
|
return other.x.toDouble() * x.toDouble() + other.y.toDouble() * y.toDouble()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dotProduct(other: IVector2d<*>): Double {
|
||||||
|
return other.x * x.toDouble() + other.y * y.toDouble()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun InvDotProduct(other: IVector2i<*>): Double {
|
||||||
|
return other.x.toDouble() * y.toDouble() + other.y.toDouble() * x.toDouble()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun InvDotProduct(other: IVector2f<*>): Double {
|
||||||
|
return other.x.toDouble() * y.toDouble() + other.y.toDouble() * x.toDouble()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun InvDotProduct(other: IVector2d<*>): Double {
|
||||||
|
return other.x * y.toDouble() + other.y * x.toDouble()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun distance(other: IVector2i<*>): Double {
|
||||||
|
return sqrt((x - other.x).toDouble().pow(2.0) + (y - other.y).toDouble().pow(2.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun distance(other: IVector2f<*>): Double {
|
||||||
|
return sqrt((x - other.x).toDouble().pow(2.0) + (y - other.y).toDouble().pow(2.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun distance(other: IVector2d<*>): Double {
|
||||||
|
return sqrt((x - other.x).toDouble().pow(2.0) + (y - other.y).toDouble().pow(2.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
val normalized: Vector2d get() {
|
||||||
|
val len = length
|
||||||
|
return Vector2d(x / len, y / len)
|
||||||
|
}
|
||||||
|
|
||||||
override fun get(row: Int, column: Int): Float {
|
override fun get(row: Int, column: Int): Float {
|
||||||
if (column != 0) {
|
if (column != 0) {
|
||||||
throw IndexOutOfBoundsException("Column must be 0 ($column given)")
|
throw IndexOutOfBoundsException("Column must be 0 ($column given)")
|
||||||
@ -114,6 +228,8 @@ abstract class IVector2f<T : IVector2f<T>> : IMatrixLike, IMatrixLikeFloat, IStr
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected abstract fun make(x: Float, y: Float): T
|
protected abstract fun make(x: Float, y: Float): T
|
||||||
|
|
||||||
|
fun toDoubleVector() = Vector2d(x.toDouble(), y.toDouble())
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Vector2f(override val x: Float = 0f, override val y: Float = 0f) : IVector2f<Vector2f>() {
|
data class Vector2f(override val x: Float = 0f, override val y: Float = 0f) : IVector2f<Vector2f>() {
|
||||||
@ -146,6 +262,122 @@ data class MutableVector2f(override var x: Float = 0f, override var y: Float = 0
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class IVector2d<T : IVector2d<T>> : IMatrixLike, IMatrixLikeDouble, IStruct2d {
|
||||||
|
override val columns = 1
|
||||||
|
override val rows = 2
|
||||||
|
|
||||||
|
abstract val x: Double
|
||||||
|
abstract val y: Double
|
||||||
|
|
||||||
|
operator fun plus(other: IVector2d<*>) = make(x + other.x, y + other.y)
|
||||||
|
operator fun minus(other: IVector2d<*>) = make(x - other.x, y - other.y)
|
||||||
|
operator fun times(other: IVector2d<*>) = make(x * other.x, y * other.y)
|
||||||
|
operator fun div(other: IVector2d<*>) = make(x / other.x, y / other.y)
|
||||||
|
|
||||||
|
operator fun plus(other: Double) = make(x + other, y + other)
|
||||||
|
operator fun minus(other: Double) = make(x - other, y - other)
|
||||||
|
operator fun times(other: Double) = make(x * other, y * other)
|
||||||
|
operator fun div(other: Double) = make(x / other, y / other)
|
||||||
|
|
||||||
|
operator fun unaryMinus() = make(-x, -y)
|
||||||
|
|
||||||
|
val length get() = sqrt(x * x + y * y)
|
||||||
|
|
||||||
|
fun dotProduct(other: IVector2i<*>): Double {
|
||||||
|
return other.x * x + other.y * y
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dotProduct(other: IVector2f<*>): Double {
|
||||||
|
return other.x * x + other.y * y
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dotProduct(other: IVector2d<*>): Double {
|
||||||
|
return other.x * x + other.y * y
|
||||||
|
}
|
||||||
|
|
||||||
|
fun invDotProduct(other: IVector2i<*>): Double {
|
||||||
|
return other.x * y + other.y * x
|
||||||
|
}
|
||||||
|
|
||||||
|
fun invDotProduct(other: IVector2f<*>): Double {
|
||||||
|
return other.x * y + other.y * x
|
||||||
|
}
|
||||||
|
|
||||||
|
fun invDotProduct(other: IVector2d<*>): Double {
|
||||||
|
return other.x * y + other.y * x
|
||||||
|
}
|
||||||
|
|
||||||
|
fun distance(other: IVector2i<*>): Double {
|
||||||
|
return sqrt((x - other.x).toDouble().pow(2.0) + (y - other.y).toDouble().pow(2.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun distance(other: IVector2f<*>): Double {
|
||||||
|
return sqrt((x - other.x).toDouble().pow(2.0) + (y - other.y).toDouble().pow(2.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun distance(other: IVector2d<*>): Double {
|
||||||
|
return sqrt((x - other.x).toDouble().pow(2.0) + (y - other.y).toDouble().pow(2.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
val normalized: Vector2d get() {
|
||||||
|
val len = length
|
||||||
|
return Vector2d(x / len, y / len)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun left() = make(x - 1, y)
|
||||||
|
fun right() = make(x + 1, y)
|
||||||
|
fun up() = make(x, y + 1)
|
||||||
|
fun down() = make(x, y - 1)
|
||||||
|
|
||||||
|
override fun get(row: Int, column: Int): Double {
|
||||||
|
if (column != 0) {
|
||||||
|
throw IndexOutOfBoundsException("Column must be 0 ($column given)")
|
||||||
|
}
|
||||||
|
|
||||||
|
return when (row) {
|
||||||
|
0 -> x
|
||||||
|
1 -> y
|
||||||
|
else -> throw IndexOutOfBoundsException("Row out of bounds: $row")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract fun make(x: Double, y: Double): T
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Vector2d(override val x: Double = 0.0, override val y: Double = 0.0) : IVector2d<Vector2d>() {
|
||||||
|
override fun make(x: Double, y: Double) = Vector2d(x, y)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromJson(input: JsonArray): Vector2d {
|
||||||
|
return Vector2d(input[0].asDouble, input[1].asDouble)
|
||||||
|
}
|
||||||
|
|
||||||
|
val ZERO = Vector2d()
|
||||||
|
val LEFT = Vector2d().left()
|
||||||
|
val RIGHT = Vector2d().right()
|
||||||
|
val UP = Vector2d().up()
|
||||||
|
val DOWN = Vector2d().down()
|
||||||
|
|
||||||
|
val INVERT_X = Vector2d(-1.0, 1.0)
|
||||||
|
val INVERT_Y = Vector2d(1.0, -1.0)
|
||||||
|
val INVERT_XY = Vector2d(-1.0, -1.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class MutableVector2d(override var x: Double = 0.0, override var y: Double = 0.0) : IVector2d<MutableVector2d>() {
|
||||||
|
override fun make(x: Double, y: Double): MutableVector2d {
|
||||||
|
this.x = x
|
||||||
|
this.y = y
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromJson(input: JsonArray): MutableVector2d {
|
||||||
|
return MutableVector2d(input[0].asDouble, input[1].asDouble)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
abstract class IVector3f<T : IVector3f<T>> : IMatrixLike, IMatrixLikeFloat, IStruct3f {
|
abstract class IVector3f<T : IVector3f<T>> : IMatrixLike, IMatrixLikeFloat, IStruct3f {
|
||||||
override val columns = 1
|
override val columns = 1
|
||||||
override val rows = 3
|
override val rows = 3
|
||||||
@ -166,6 +398,12 @@ abstract class IVector3f<T : IVector3f<T>> : IMatrixLike, IMatrixLikeFloat, IStr
|
|||||||
|
|
||||||
operator fun unaryMinus() = make(-x, -y, -z)
|
operator fun unaryMinus() = make(-x, -y, -z)
|
||||||
|
|
||||||
|
val length get() = sqrt(x.toDouble() * x.toDouble() + y.toDouble() * y.toDouble() + z.toDouble() * z.toDouble())
|
||||||
|
|
||||||
|
fun dotProduct(other: IVector3f<*>): Double {
|
||||||
|
return other.x.toDouble() * x.toDouble() + other.y.toDouble() * y.toDouble() + other.z.toDouble() * z.toDouble()
|
||||||
|
}
|
||||||
|
|
||||||
override fun get(row: Int, column: Int): Float {
|
override fun get(row: Int, column: Int): Float {
|
||||||
if (column != 0) {
|
if (column != 0) {
|
||||||
throw IndexOutOfBoundsException("Column must be 0 ($column given)")
|
throw IndexOutOfBoundsException("Column must be 0 ($column given)")
|
||||||
@ -344,3 +582,85 @@ data class MutableVector4f(override var x: Float = 0f, override var y: Float = 0
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class IVector4d<T : IVector4d<T>> : IMatrixLike, IMatrixLikeDouble, IStruct4d {
|
||||||
|
abstract val x: Double
|
||||||
|
abstract val y: Double
|
||||||
|
abstract val z: Double
|
||||||
|
abstract val w: Double
|
||||||
|
|
||||||
|
operator fun plus(other: IVector4f<*>) = make(x + other.x, y + other.y, z + other.z, w + other.w)
|
||||||
|
operator fun minus(other: IVector4f<*>) = make(x - other.x, y - other.y, z - other.z, w + other.w)
|
||||||
|
operator fun times(other: IVector4f<*>) = make(x * other.x, y * other.y, z * other.z, w + other.w)
|
||||||
|
operator fun div(other: IVector4f<*>) = make(x / other.x, y / other.y, z / other.z, w + other.w)
|
||||||
|
|
||||||
|
operator fun plus(other: Double) = make(x + other, y + other, z + other, w + other)
|
||||||
|
operator fun minus(other: Double) = make(x - other, y - other, z - other, w - other)
|
||||||
|
operator fun times(other: Double) = make(x * other, y * other, z * other, w * other)
|
||||||
|
operator fun div(other: Double) = make(x / other, y / other, z / other, w / other)
|
||||||
|
|
||||||
|
operator fun unaryMinus() = make(-x, -y, -z, -w)
|
||||||
|
|
||||||
|
override val columns = 1
|
||||||
|
override val rows = 4
|
||||||
|
|
||||||
|
override fun get(row: Int, column: Int): Double {
|
||||||
|
if (column != 0) {
|
||||||
|
throw IndexOutOfBoundsException("Column must be 0 ($column given)")
|
||||||
|
}
|
||||||
|
|
||||||
|
return when (row) {
|
||||||
|
0 -> x
|
||||||
|
1 -> y
|
||||||
|
2 -> z
|
||||||
|
3 -> w
|
||||||
|
else -> throw IndexOutOfBoundsException("Row out of bounds: $row")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun times(other: IMatrixLikeDouble): T {
|
||||||
|
if (other.rows >= 4 && other.columns >= 4) {
|
||||||
|
val x = this.x * other[0, 0] +
|
||||||
|
this.y * other[0, 1] +
|
||||||
|
this.z * other[0, 2] +
|
||||||
|
this.w * other[0, 3]
|
||||||
|
|
||||||
|
val y = this.x * other[1, 0] +
|
||||||
|
this.y * other[1, 1] +
|
||||||
|
this.z * other[1, 2] +
|
||||||
|
this.w * other[1, 3]
|
||||||
|
|
||||||
|
val z = this.x * other[2, 0] +
|
||||||
|
this.y * other[2, 1] +
|
||||||
|
this.z * other[2, 2] +
|
||||||
|
this.w * other[2, 3]
|
||||||
|
|
||||||
|
val w = this.x * other[3, 0] +
|
||||||
|
this.y * other[3, 1] +
|
||||||
|
this.z * other[3, 2] +
|
||||||
|
this.w * other[3, 3]
|
||||||
|
|
||||||
|
return make(x, y, z, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
throw IllegalArgumentException("Incompatible matrix provided: ${other.rows} x ${other.columns}")
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract fun make(x: Double, y: Double, z: Double, w: Double): T
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Vector4d(override val x: Double = 0.0, override val y: Double = 0.0, override val z: Double = 0.0, override val w: Double = 0.0) : IVector4d<Vector4d>() {
|
||||||
|
override fun make(x: Double, y: Double, z: Double, w: Double): Vector4d {
|
||||||
|
return Vector4d(x, y, z, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class MutableVector4d(override var x: Double = 0.0, override var y: Double = 0.0, override var z: Double = 0.0, override var w: Double = 0.0) : IVector4d<MutableVector4d>() {
|
||||||
|
override fun make(x: Double, y: Double, z: Double, w: Double): MutableVector4d {
|
||||||
|
this.x = x
|
||||||
|
this.y = y
|
||||||
|
this.z = z
|
||||||
|
this.w = w
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -9,8 +9,8 @@ private const val PETIBYTE = TEBIBYTE * 1024L
|
|||||||
fun formatBytesShort(input: Long): String {
|
fun formatBytesShort(input: Long): String {
|
||||||
return when (input) {
|
return when (input) {
|
||||||
in 0 until KIBIBYTE -> "${input}b"
|
in 0 until KIBIBYTE -> "${input}b"
|
||||||
in KIBIBYTE until MEBIBYTE -> "%.2fKiB".format((input / KIBIBYTE).toDouble() + (input % KIBIBYTE).toDouble() / KIBIBYTE)
|
in KIBIBYTE until MEBIBYTE -> "${(((input / KIBIBYTE).toDouble() + (input % KIBIBYTE).toDouble() / KIBIBYTE) * 100.0).toLong().toDouble() / 100.0}KiB"
|
||||||
in MEBIBYTE until GIBIBYTE -> "%.2fMiB".format((input / MEBIBYTE).toDouble() + (input % MEBIBYTE).toDouble() / MEBIBYTE)
|
in MEBIBYTE until GIBIBYTE -> "${(((input / MEBIBYTE).toDouble() + (input % MEBIBYTE).toDouble() / MEBIBYTE) * 100.0).toLong().toDouble() / 100.0}MiB"
|
||||||
else -> "${input}b"
|
else -> "${input}b"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,12 @@ package ru.dbotthepony.kstarbound.world
|
|||||||
|
|
||||||
import ru.dbotthepony.kstarbound.api.IStruct2i
|
import ru.dbotthepony.kstarbound.api.IStruct2i
|
||||||
import ru.dbotthepony.kstarbound.defs.TileDefinition
|
import ru.dbotthepony.kstarbound.defs.TileDefinition
|
||||||
|
import ru.dbotthepony.kstarbound.math.AABB
|
||||||
import ru.dbotthepony.kstarbound.math.IVector2i
|
import ru.dbotthepony.kstarbound.math.IVector2i
|
||||||
|
import ru.dbotthepony.kstarbound.math.Vector2d
|
||||||
import ru.dbotthepony.kstarbound.math.Vector2i
|
import ru.dbotthepony.kstarbound.math.Vector2i
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Представляет из себя класс, который содержит состояние тайла на заданной позиции
|
* Представляет из себя класс, который содержит состояние тайла на заданной позиции
|
||||||
@ -146,7 +150,10 @@ interface IMutableTileChunk : ITileChunk, ITileSetter
|
|||||||
|
|
||||||
const val CHUNK_SHIFT = 5
|
const val CHUNK_SHIFT = 5
|
||||||
const val CHUNK_SIZE = 1 shl CHUNK_SHIFT // 32
|
const val CHUNK_SIZE = 1 shl CHUNK_SHIFT // 32
|
||||||
|
|
||||||
const val CHUNK_SIZE_FF = CHUNK_SIZE - 1
|
const val CHUNK_SIZE_FF = CHUNK_SIZE - 1
|
||||||
|
const val CHUNK_SIZEf = CHUNK_SIZE.toFloat()
|
||||||
|
const val CHUNK_SIZEd = CHUNK_SIZE.toDouble()
|
||||||
|
|
||||||
data class ChunkPos(override val x: Int, override val y: Int) : IVector2i<ChunkPos>() {
|
data class ChunkPos(override val x: Int, override val y: Int) : IVector2i<ChunkPos>() {
|
||||||
constructor(pos: IStruct2i) : this(pos.component1(), pos.component2())
|
constructor(pos: IStruct2i) : this(pos.component1(), pos.component2())
|
||||||
@ -158,7 +165,25 @@ data class ChunkPos(override val x: Int, override val y: Int) : IVector2i<ChunkP
|
|||||||
companion object {
|
companion object {
|
||||||
fun fromTilePosition(input: IStruct2i): ChunkPos {
|
fun fromTilePosition(input: IStruct2i): ChunkPos {
|
||||||
val (x, y) = input
|
val (x, y) = input
|
||||||
return ChunkPos(x shr CHUNK_SHIFT, y shr CHUNK_SHIFT)
|
return ChunkPos(tileToChunkComponent(x), tileToChunkComponent(y))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fromTilePosition(x: Int, y: Int): ChunkPos {
|
||||||
|
return ChunkPos(tileToChunkComponent(x), tileToChunkComponent(y))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun normalizeCoordinate(input: Int): Int {
|
||||||
|
val band = input and CHUNK_SIZE_FF
|
||||||
|
|
||||||
|
if (band < 0) {
|
||||||
|
return band + CHUNK_SIZE_FF
|
||||||
|
}
|
||||||
|
|
||||||
|
return band
|
||||||
|
}
|
||||||
|
|
||||||
|
fun tileToChunkComponent(comp: Int): Int {
|
||||||
|
return comp shr CHUNK_SHIFT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -294,6 +319,8 @@ open class Chunk(val world: World<*>?, val pos: ChunkPos) {
|
|||||||
changeset++
|
changeset++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val aabb = aabbBase + Vector2d(pos.x * CHUNK_SIZE.toDouble(), pos.y * CHUNK_SIZE.toDouble())
|
||||||
|
|
||||||
inner class TileLayer : IMutableTileChunk {
|
inner class TileLayer : IMutableTileChunk {
|
||||||
/**
|
/**
|
||||||
* Возвращает счётчик изменений этого слоя
|
* Возвращает счётчик изменений этого слоя
|
||||||
@ -306,6 +333,65 @@ open class Chunk(val world: World<*>?, val pos: ChunkPos) {
|
|||||||
this@Chunk.changeset++
|
this@Chunk.changeset++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val collisionCache = ArrayList<AABB>()
|
||||||
|
private val collisionCacheView = Collections.unmodifiableCollection(collisionCache)
|
||||||
|
private var collisionChangeset = -1
|
||||||
|
|
||||||
|
private fun bakeCollisions() {
|
||||||
|
collisionChangeset = changeset
|
||||||
|
val seen = BooleanArray(tiles.size)
|
||||||
|
|
||||||
|
collisionCache.clear()
|
||||||
|
|
||||||
|
val xAdd = pos.x * CHUNK_SIZEd
|
||||||
|
val yAdd = pos.y * CHUNK_SIZEd
|
||||||
|
|
||||||
|
for (y in 0 .. CHUNK_SIZE_FF) {
|
||||||
|
var first: Int? = null
|
||||||
|
var last = 0
|
||||||
|
|
||||||
|
for (x in 0 .. CHUNK_SIZE_FF) {
|
||||||
|
if (tiles[x or (y shl CHUNK_SHIFT)] != null) {
|
||||||
|
if (first == null) {
|
||||||
|
first = x
|
||||||
|
}
|
||||||
|
|
||||||
|
last = x
|
||||||
|
} else {
|
||||||
|
if (first != null) {
|
||||||
|
collisionCache.add(AABB(
|
||||||
|
Vector2d(x = xAdd + first.toDouble(), y = y.toDouble() + yAdd),
|
||||||
|
Vector2d(x = xAdd + last.toDouble() + 1.0, y = y.toDouble() + 1.0 + yAdd),
|
||||||
|
))
|
||||||
|
|
||||||
|
first = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (first != null) {
|
||||||
|
collisionCache.add(AABB(
|
||||||
|
Vector2d(x = first.toDouble() + xAdd, y = y.toDouble() + yAdd),
|
||||||
|
Vector2d(x = last.toDouble() + 1.0 + xAdd, y = y.toDouble() + 1.0 + yAdd),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Возвращает список AABB тайлов этого слоя
|
||||||
|
*
|
||||||
|
* Данный список напрямую указывает на внутреннее состояние и будет изменён при перестройке
|
||||||
|
* коллизии чанка, поэтому если необходим стабильный список, его необходимо скопировать
|
||||||
|
*/
|
||||||
|
fun collisionLayers(): Collection<AABB> {
|
||||||
|
if (collisionChangeset != changeset) {
|
||||||
|
bakeCollisions()
|
||||||
|
}
|
||||||
|
|
||||||
|
return collisionCacheView
|
||||||
|
}
|
||||||
|
|
||||||
override val pos: ChunkPos
|
override val pos: ChunkPos
|
||||||
get() = this@Chunk.pos
|
get() = this@Chunk.pos
|
||||||
|
|
||||||
@ -354,5 +440,10 @@ open class Chunk(val world: World<*>?, val pos: ChunkPos) {
|
|||||||
override fun get(x: Int, y: Int): ChunkTile? = null
|
override fun get(x: Int, y: Int): ChunkTile? = null
|
||||||
override fun set(x: Int, y: Int, tile: TileDefinition?): ChunkTile? = null
|
override fun set(x: Int, y: Int, tile: TileDefinition?): ChunkTile? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val aabbBase = AABB(
|
||||||
|
Vector2d.ZERO,
|
||||||
|
Vector2d(CHUNK_SIZE.toDouble(), CHUNK_SIZE.toDouble()),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package ru.dbotthepony.kstarbound.world
|
package ru.dbotthepony.kstarbound.world
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.METRES_IN_STARBOUND_UNIT
|
||||||
import ru.dbotthepony.kstarbound.defs.TileDefinition
|
import ru.dbotthepony.kstarbound.defs.TileDefinition
|
||||||
|
import ru.dbotthepony.kstarbound.math.AABBi
|
||||||
|
import ru.dbotthepony.kstarbound.math.Vector2d
|
||||||
import ru.dbotthepony.kstarbound.math.Vector2i
|
import ru.dbotthepony.kstarbound.math.Vector2i
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -40,11 +43,19 @@ open class MutableWorldChunkTuple(
|
|||||||
override var bottom: IWorldChunkTuple?,
|
override var bottom: IWorldChunkTuple?,
|
||||||
) : IMutableWorldChunkTuple
|
) : IMutableWorldChunkTuple
|
||||||
|
|
||||||
@Suppress("WeakerAccess")
|
const val EARTH_FREEFALL_ACCELERATION = 9.8312 / METRES_IN_STARBOUND_UNIT
|
||||||
|
|
||||||
abstract class World<T : IMutableWorldChunkTuple>(val seed: Long = 0L) {
|
abstract class World<T : IMutableWorldChunkTuple>(val seed: Long = 0L) {
|
||||||
protected val chunkMap = HashMap<ChunkPos, T>()
|
protected val chunkMap = HashMap<ChunkPos, T>()
|
||||||
protected var lastAccessedChunk: T? = null
|
protected var lastAccessedChunk: T? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Стандартное ускорение свободного падения в Starbound Units/секунда^2
|
||||||
|
*
|
||||||
|
* При Vector2d.ZERO = невесомость
|
||||||
|
*/
|
||||||
|
var gravity = Vector2d(0.0, -EARTH_FREEFALL_ACCELERATION)
|
||||||
|
|
||||||
protected abstract fun tupleFactory(
|
protected abstract fun tupleFactory(
|
||||||
chunk: Chunk,
|
chunk: Chunk,
|
||||||
top: IWorldChunkTuple?,
|
top: IWorldChunkTuple?,
|
||||||
@ -155,22 +166,56 @@ abstract class World<T : IMutableWorldChunkTuple>(val seed: Long = 0L) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getTile(pos: Vector2i): ChunkTile? {
|
fun getTile(pos: Vector2i): ChunkTile? {
|
||||||
return getChunkInternal(ChunkPos.fromTilePosition(pos))?.chunk?.foreground?.get(pos.x, pos.y)
|
return getChunkInternal(ChunkPos.fromTilePosition(pos))?.chunk?.foreground?.get(ChunkPos.normalizeCoordinate(pos.x), ChunkPos.normalizeCoordinate(pos.y))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setTile(pos: Vector2i, tile: TileDefinition?): IWorldChunkTuple {
|
fun setTile(pos: Vector2i, tile: TileDefinition?): IWorldChunkTuple {
|
||||||
val chunk = computeIfAbsentInternal(ChunkPos.fromTilePosition(pos))
|
val chunk = computeIfAbsentInternal(ChunkPos.fromTilePosition(pos))
|
||||||
chunk.chunk.foreground[pos.x and CHUNK_SIZE_FF, pos.y and CHUNK_SIZE_FF] = tile
|
chunk.chunk.foreground[ChunkPos.normalizeCoordinate(pos.x), ChunkPos.normalizeCoordinate(pos.y)] = tile
|
||||||
return chunk
|
return chunk
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getBackgroundTile(pos: Vector2i): ChunkTile? {
|
fun getBackgroundTile(pos: Vector2i): ChunkTile? {
|
||||||
return getChunkInternal(ChunkPos.fromTilePosition(pos))?.chunk?.background?.get(pos.x, pos.y)
|
return getChunkInternal(ChunkPos.fromTilePosition(pos))?.chunk?.background?.get(ChunkPos.normalizeCoordinate(pos.x), ChunkPos.normalizeCoordinate(pos.y))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setBackgroundTile(pos: Vector2i, tile: TileDefinition?): IWorldChunkTuple {
|
fun setBackgroundTile(pos: Vector2i, tile: TileDefinition?): IWorldChunkTuple {
|
||||||
val chunk = computeIfAbsentInternal(ChunkPos.fromTilePosition(pos))
|
val chunk = computeIfAbsentInternal(ChunkPos.fromTilePosition(pos))
|
||||||
chunk.chunk.background[pos.x and CHUNK_SIZE_FF, pos.y and CHUNK_SIZE_FF] = tile
|
chunk.chunk.background[ChunkPos.normalizeCoordinate(pos.x), ChunkPos.normalizeCoordinate(pos.y)] = tile
|
||||||
return chunk
|
return chunk
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected open fun collectInternal(boundingBox: AABBi): List<T> {
|
||||||
|
val output = ArrayList<T>()
|
||||||
|
|
||||||
|
for (pos in boundingBox.chunkPositions) {
|
||||||
|
val chunk = getChunkInternal(pos)
|
||||||
|
|
||||||
|
if (chunk != null) {
|
||||||
|
output.add(chunk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Возвращает все чанки, которые пересекаются с заданным [boundingBox]
|
||||||
|
*/
|
||||||
|
open fun collect(boundingBox: AABBi): List<IWorldChunkTuple> {
|
||||||
|
val output = ArrayList<IWorldChunkTuple>()
|
||||||
|
|
||||||
|
for (chunk in collectInternal(boundingBox)) {
|
||||||
|
output.add(WorldChunkTuple(
|
||||||
|
world = chunk.world,
|
||||||
|
chunk = chunk.chunk,
|
||||||
|
top = chunk.top,
|
||||||
|
left = chunk.left,
|
||||||
|
right = chunk.right,
|
||||||
|
bottom = chunk.bottom,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.world.entities
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.world.World
|
||||||
|
|
||||||
|
open class AliveEntity(world: World<*>) : Entity(world) {
|
||||||
|
var maxHealth = 10.0
|
||||||
|
var health = 10.0
|
||||||
|
}
|
@ -0,0 +1,179 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.world.entities
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager
|
||||||
|
import ru.dbotthepony.kstarbound.math.AABB
|
||||||
|
import ru.dbotthepony.kstarbound.math.Vector2d
|
||||||
|
import ru.dbotthepony.kstarbound.math.lerp
|
||||||
|
import ru.dbotthepony.kstarbound.world.Chunk
|
||||||
|
import ru.dbotthepony.kstarbound.world.World
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
|
enum class CollisionResolution {
|
||||||
|
STOP,
|
||||||
|
BOUNCE,
|
||||||
|
PUSH,
|
||||||
|
SLIDE,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Определяет из себя сущность в мире, которая имеет позицию, скорость и коробку столкновений
|
||||||
|
*/
|
||||||
|
open class Entity(val world: World<*>) {
|
||||||
|
var chunk: Chunk? = null
|
||||||
|
protected set
|
||||||
|
|
||||||
|
val worldaabb get() = aabb + pos
|
||||||
|
var pos = Vector2d()
|
||||||
|
var velocity = Vector2d()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Касается ли сущность земли
|
||||||
|
*
|
||||||
|
* Данный флаг выставляется при обработке скорости, если данный флаг не будет выставлен
|
||||||
|
* правильно, то сущность будет иметь очень плохое движение в стороны
|
||||||
|
*
|
||||||
|
* Так же от него зависит то, может ли сущность двигаться, если она не парит
|
||||||
|
*
|
||||||
|
* Если сущность касается земли, то на неё не действует гравитация
|
||||||
|
*/
|
||||||
|
var onGround = false
|
||||||
|
protected set
|
||||||
|
|
||||||
|
// наследуемые свойства
|
||||||
|
open val aabb = AABB.rectangle(Vector2d.ZERO, 0.9, 0.9)
|
||||||
|
open val affectedByGravity = true
|
||||||
|
open val collisionResolution = CollisionResolution.STOP
|
||||||
|
|
||||||
|
protected open fun onTouchGround(velocity: Vector2d, normal: Vector2d) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun propagateVelocity(delta: Double) {
|
||||||
|
if (velocity.length == 0.0)
|
||||||
|
return
|
||||||
|
|
||||||
|
var deltaMovement = velocity * delta
|
||||||
|
|
||||||
|
var potentialAABB = worldaabb + deltaMovement
|
||||||
|
var combined = worldaabb.combine(potentialAABB)
|
||||||
|
val collected = world.collect((combined).encasingChunkPosAABB()).map { it.chunk.foreground.collisionLayers() }
|
||||||
|
|
||||||
|
if (collected.isNotEmpty()) {
|
||||||
|
var newOnGround = false
|
||||||
|
|
||||||
|
for (iteration in 0 .. 100) {
|
||||||
|
var collided = false
|
||||||
|
|
||||||
|
for (aabbList in collected) {
|
||||||
|
for (aabb in aabbList) {
|
||||||
|
if (!newOnGround && iteration == 0) {
|
||||||
|
if (worldaabb.sweep(aabb, world.gravity * delta).collisionTime < 1.0) {
|
||||||
|
newOnGround = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// залез в блоки?
|
||||||
|
if (potentialAABB.intersectWeak(aabb)) {
|
||||||
|
val push = worldaabb.pushOutFrom(aabb)
|
||||||
|
|
||||||
|
if (push.length > 0.0) {
|
||||||
|
velocity -= push * delta * 100.0
|
||||||
|
deltaMovement = velocity * delta
|
||||||
|
potentialAABB = worldaabb + deltaMovement
|
||||||
|
combined = worldaabb.combine(potentialAABB)
|
||||||
|
|
||||||
|
onGround = true
|
||||||
|
collided = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ранний тест (отсечение заведомо не пересекаемой геометрии)
|
||||||
|
if (!aabb.intersect(combined)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// обычный тест коллизии
|
||||||
|
val (normal, collisionTime) = worldaabb.sweep(aabb, deltaMovement)
|
||||||
|
|
||||||
|
if (collisionTime != 1.0) {
|
||||||
|
val remainingTime = 1.0 - collisionTime
|
||||||
|
val oldVelocity = velocity
|
||||||
|
|
||||||
|
when (collisionResolution) {
|
||||||
|
CollisionResolution.STOP -> {
|
||||||
|
velocity *= remainingTime
|
||||||
|
}
|
||||||
|
|
||||||
|
CollisionResolution.PUSH -> {
|
||||||
|
var dot = deltaMovement.invDotProduct(normal)
|
||||||
|
val magnitude = deltaMovement.length * remainingTime
|
||||||
|
|
||||||
|
if (dot > 0.0) {
|
||||||
|
dot = 1.0
|
||||||
|
} else {
|
||||||
|
dot = -1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
velocity = Vector2d(dot * normal.y * magnitude, dot * normal.x * magnitude) / delta
|
||||||
|
}
|
||||||
|
|
||||||
|
CollisionResolution.SLIDE -> {
|
||||||
|
val dot = deltaMovement.invDotProduct(normal) * remainingTime
|
||||||
|
velocity = Vector2d(dot * normal.y, dot * normal.x) / delta
|
||||||
|
}
|
||||||
|
|
||||||
|
CollisionResolution.BOUNCE -> {
|
||||||
|
velocity *= remainingTime
|
||||||
|
|
||||||
|
if (normal.x.absoluteValue > 0.00001 && normal.y.absoluteValue > 0.00001) {
|
||||||
|
velocity *= Vector2d.INVERT_XY
|
||||||
|
} else if (normal.x.absoluteValue > 0.00001) {
|
||||||
|
velocity *= Vector2d.INVERT_X
|
||||||
|
} else if (normal.y.absoluteValue > 0.00001) {
|
||||||
|
velocity *= Vector2d.INVERT_Y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
collided = true
|
||||||
|
|
||||||
|
if (!newOnGround) {
|
||||||
|
newOnGround = normal.dotProduct(world.gravity) <= -0.98
|
||||||
|
}
|
||||||
|
|
||||||
|
deltaMovement = velocity * delta
|
||||||
|
potentialAABB = worldaabb + deltaMovement
|
||||||
|
onTouchGround(oldVelocity, normal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!collided) {
|
||||||
|
//println("Resolved collision on $iteration")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onGround = newOnGround
|
||||||
|
//println(newOnGround)
|
||||||
|
} else {
|
||||||
|
onGround = false
|
||||||
|
}
|
||||||
|
|
||||||
|
pos += velocity * delta
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun moveAndCollide(delta: Double) {
|
||||||
|
if (!onGround && affectedByGravity)
|
||||||
|
velocity += world.gravity * delta
|
||||||
|
else if (affectedByGravity)
|
||||||
|
velocity *= Vector2d(lerp(delta, 1.0, 0.01), 1.0)
|
||||||
|
|
||||||
|
propagateVelocity(delta)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LOGGER = LogManager.getLogger(Entity::class.java)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.world.entities
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.math.AABB
|
||||||
|
import ru.dbotthepony.kstarbound.math.Vector2d
|
||||||
|
import ru.dbotthepony.kstarbound.world.World
|
||||||
|
|
||||||
|
open class Humanoid(world: World<*>) : AliveEntity(world) {
|
||||||
|
override val aabb = AABB.rectangle(Vector2d.ZERO, 1.8, 3.7)
|
||||||
|
override val collisionResolution = CollisionResolution.SLIDE
|
||||||
|
}
|
9
src/main/resources/shaders/fragment/flat_color.glsl
Normal file
9
src/main/resources/shaders/fragment/flat_color.glsl
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
#version 460
|
||||||
|
|
||||||
|
uniform vec4 _color;
|
||||||
|
out vec4 _color_out;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
_color_out = _color;
|
||||||
|
}
|
9
src/main/resources/shaders/vertex/flat_vertex_2d.glsl
Normal file
9
src/main/resources/shaders/vertex/flat_vertex_2d.glsl
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
#version 460
|
||||||
|
|
||||||
|
layout (location = 0) in vec2 _pos;
|
||||||
|
uniform mat4 _transform;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = _transform * vec4(_pos, 0.5, 1.0);
|
||||||
|
}
|
51
src/test/kotlin/ru/dbotthepony/kstarbound/test/MathTests.kt
Normal file
51
src/test/kotlin/ru/dbotthepony/kstarbound/test/MathTests.kt
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.test
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import ru.dbotthepony.kstarbound.math.AABB
|
||||||
|
import ru.dbotthepony.kstarbound.math.Vector2d
|
||||||
|
import ru.dbotthepony.kstarbound.math.roundByAbsoluteValue
|
||||||
|
|
||||||
|
object MathTests {
|
||||||
|
@Test
|
||||||
|
@DisplayName("roundByAbsoluteValue test")
|
||||||
|
fun roundByAbsoluteValueTest() {
|
||||||
|
check(roundByAbsoluteValue(0.0) == 0)
|
||||||
|
check(roundByAbsoluteValue(0.1) == 1)
|
||||||
|
check(roundByAbsoluteValue(1.1) == 2)
|
||||||
|
check(roundByAbsoluteValue(-0.1) == -1)
|
||||||
|
check(roundByAbsoluteValue(-0.0) == 0)
|
||||||
|
check(roundByAbsoluteValue(-1.0) == -1)
|
||||||
|
check(roundByAbsoluteValue(-1.1) == -2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("AABB Basic Math")
|
||||||
|
fun basicAABB() {
|
||||||
|
val a = AABB.rectangle(Vector2d.ZERO, 1.0, 1.0)
|
||||||
|
|
||||||
|
check(a.intersect(AABB.rectangle(Vector2d(-1.0), 1.0, 1.0)))
|
||||||
|
check(!a.intersectWeak(AABB.rectangle(Vector2d(-1.0), 1.0, 1.0)))
|
||||||
|
check(!a.intersect(AABB.rectangle(Vector2d(-2.0), 1.0, 1.0)))
|
||||||
|
check(!a.intersectWeak(AABB.rectangle(Vector2d(-2.0), 1.0, 1.0)))
|
||||||
|
|
||||||
|
check(a.intersect(AABB.rectangle(Vector2d(-0.9), 1.0, 1.0)))
|
||||||
|
check(a.intersectWeak(AABB.rectangle(Vector2d(-0.9), 1.0, 1.0)))
|
||||||
|
|
||||||
|
val bigA = AABB.rectangle(Vector2d.ZERO, 200.0, 200.0)
|
||||||
|
val smallB = AABB.rectangle(Vector2d.ZERO, 1.0, 1.0)
|
||||||
|
|
||||||
|
check(bigA.intersect(smallB))
|
||||||
|
check(smallB.intersect(bigA))
|
||||||
|
|
||||||
|
check(bigA.intersectWeak(smallB))
|
||||||
|
check(smallB.intersectWeak(bigA))
|
||||||
|
|
||||||
|
check(AABB.rectangle(Vector2d.ZERO, 1.0, 1.0) == AABB(Vector2d(-0.5, -0.5), Vector2d(0.5, 0.5)))
|
||||||
|
|
||||||
|
val combineA = AABB(Vector2d(0.0, 0.0), Vector2d(2.0, 2.0))
|
||||||
|
val combineB = AABB(Vector2d(2.0, 5.0), Vector2d(4.0, 6.0))
|
||||||
|
|
||||||
|
check(combineA.combine(combineB) == AABB(Vector2d(0.0, 0.0), Vector2d(4.0, 6.0))) { combineA.combine(combineB).toString() }
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user