Отрисовка сущностей
This commit is contained in:
parent
f907124af6
commit
c3863d8ea2
@ -30,7 +30,7 @@ fun main() {
|
|||||||
Starbound.terminateLoading = true
|
Starbound.terminateLoading = true
|
||||||
}
|
}
|
||||||
|
|
||||||
var chunkA: Chunk? = null
|
var chunkA: Chunk<*, *>? = null
|
||||||
|
|
||||||
val ent = PlayerEntity(client.world!!)
|
val ent = PlayerEntity(client.world!!)
|
||||||
|
|
||||||
@ -122,12 +122,6 @@ fun main() {
|
|||||||
client.camera.pos.y = ent.pos.y.toFloat()
|
client.camera.pos.y = ent.pos.y.toFloat()
|
||||||
}
|
}
|
||||||
|
|
||||||
client.onPostDrawWorld {
|
|
||||||
client.gl.quadWireframe {
|
|
||||||
it.quad(ent.worldaabb)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ent.spawn()
|
ent.spawn()
|
||||||
|
|
||||||
while (client.renderFrame()) {
|
while (client.renderFrame()) {
|
||||||
|
312
src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt
Normal file
312
src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt
Normal file
@ -0,0 +1,312 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.client
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
|
||||||
|
import ru.dbotthepony.kstarbound.client.render.BakedStaticMesh
|
||||||
|
import ru.dbotthepony.kstarbound.client.render.EntityRenderer
|
||||||
|
import ru.dbotthepony.kstarbound.client.render.ILayeredRenderer
|
||||||
|
import ru.dbotthepony.kstarbound.client.render.TileLayerList
|
||||||
|
import ru.dbotthepony.kstarbound.math.Matrix4fStack
|
||||||
|
import ru.dbotthepony.kstarbound.math.Vector2d
|
||||||
|
import ru.dbotthepony.kstarbound.world.*
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.Entity
|
||||||
|
import java.io.Closeable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Псевдо zPos у фоновых тайлов
|
||||||
|
*
|
||||||
|
* Добавление этого числа к zPos гарантирует, что фоновые тайлы будут отрисованы
|
||||||
|
* первыми (на самом дальнем плане)
|
||||||
|
*/
|
||||||
|
const val Z_LEVEL_BACKGROUND = 60000
|
||||||
|
|
||||||
|
class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, ClientChunk>(world, pos), Closeable, ILayeredRenderer {
|
||||||
|
val state: GLStateTracker get() = world.client.gl
|
||||||
|
|
||||||
|
private inner class TileLayerRenderer(private val layerChangeset: () -> Int, private val isBackground: Boolean) : AutoCloseable {
|
||||||
|
private val layers = TileLayerList()
|
||||||
|
val bakedMeshes = ArrayList<Pair<BakedStaticMesh, Int>>()
|
||||||
|
private var changeset = -1
|
||||||
|
|
||||||
|
fun bake(view: ITileChunk) {
|
||||||
|
if (state.isSameThread()) {
|
||||||
|
for (mesh in bakedMeshes) {
|
||||||
|
mesh.first.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
bakedMeshes.clear()
|
||||||
|
} else {
|
||||||
|
for (mesh in bakedMeshes) {
|
||||||
|
unloadableBakedMeshes.add(mesh.first)
|
||||||
|
}
|
||||||
|
|
||||||
|
bakedMeshes.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
layers.clear()
|
||||||
|
|
||||||
|
for ((pos, tile) in view.posToTile) {
|
||||||
|
if (tile != null) {
|
||||||
|
val renderer = state.tileRenderers.get(tile.def.materialName)
|
||||||
|
renderer.tesselate(view, layers, pos, background = isBackground)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadRenderers(view: ITileChunk) {
|
||||||
|
for ((_, tile) in view.posToTile) {
|
||||||
|
if (tile != null) {
|
||||||
|
state.tileRenderers.get(tile.def.materialName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun uploadStatic(clear: Boolean = true) {
|
||||||
|
for ((baked, builder, zLevel) in layers.buildList()) {
|
||||||
|
bakedMeshes.add(BakedStaticMesh(baked, builder) to zLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clear) {
|
||||||
|
layers.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun render(stack: Matrix4fStack) {
|
||||||
|
val transform = stack.last
|
||||||
|
|
||||||
|
for (mesh in bakedMeshes) {
|
||||||
|
mesh.first.render(transform)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun autoBake(provider: () -> ITileChunk) {
|
||||||
|
if (changeset != layerChangeset.invoke()) {
|
||||||
|
this.bake(provider.invoke())
|
||||||
|
changeset = layerChangeset.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun autoUpload() {
|
||||||
|
if (layers.isNotEmpty) {
|
||||||
|
for (mesh in bakedMeshes) {
|
||||||
|
mesh.first.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
bakedMeshes.clear()
|
||||||
|
|
||||||
|
for ((baked, builder, zLevel) in layers.buildList()) {
|
||||||
|
bakedMeshes.add(BakedStaticMesh(baked, builder) to zLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
layers.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun autoBakeAndUpload(provider: () -> ITileChunk) {
|
||||||
|
autoBake(provider)
|
||||||
|
autoUpload()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bakeAndRender(transform: Matrix4fStack, provider: () -> ITileChunk) {
|
||||||
|
autoBakeAndUpload(provider)
|
||||||
|
render(transform)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
for (mesh in bakedMeshes) {
|
||||||
|
mesh.first.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val debugCollisions get() = world.client.settings.debugCollisions
|
||||||
|
val posVector2d = Vector2d(x = pos.x * CHUNK_SIZEd, y = pos.y * CHUNK_SIZEd)
|
||||||
|
|
||||||
|
private val unloadableBakedMeshes = ArrayList<BakedStaticMesh>()
|
||||||
|
|
||||||
|
private val foregroundRenderer = TileLayerRenderer(foreground::changeset, isBackground = false)
|
||||||
|
private val backgroundRenderer = TileLayerRenderer(background::changeset, isBackground = true)
|
||||||
|
|
||||||
|
private fun getForegroundView(): ITileChunk {
|
||||||
|
return world.getForegroundView(pos)!!
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getBackgroundView(): ITileChunk {
|
||||||
|
return world.getBackgroundView(pos)!!
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Принудительно подгружает в GLStateTracker все необходимые рендереры (ибо им нужны текстуры и прочее)
|
||||||
|
*
|
||||||
|
* Вызывается перед tesselateStatic()
|
||||||
|
*/
|
||||||
|
fun loadRenderers() {
|
||||||
|
unloadUnused()
|
||||||
|
|
||||||
|
foregroundRenderer.loadRenderers(getForegroundView())
|
||||||
|
backgroundRenderer.loadRenderers(getBackgroundView())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unloadUnused() {
|
||||||
|
if (unloadableBakedMeshes.size != 0) {
|
||||||
|
for (baked in unloadableBakedMeshes) {
|
||||||
|
baked.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
unloadableBakedMeshes.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Отрисовывает всю геометрию напрямую
|
||||||
|
*/
|
||||||
|
fun render(stack: Matrix4fStack) {
|
||||||
|
unloadUnused()
|
||||||
|
|
||||||
|
backgroundRenderer.render(stack)
|
||||||
|
foregroundRenderer.render(stack)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Отрисовывает всю геометрию напрямую, с проверкой, изменился ли чанк
|
||||||
|
*/
|
||||||
|
fun bakeAndRender(stack: Matrix4fStack) {
|
||||||
|
unloadUnused()
|
||||||
|
|
||||||
|
backgroundRenderer.bakeAndRender(stack, this::getBackgroundView)
|
||||||
|
foregroundRenderer.bakeAndRender(stack, this::getForegroundView)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Тесселирует "статичную" геометрию в builders (к примеру тайлы), с проверкой, изменилось ли что либо,
|
||||||
|
* и загружает её в видеопамять.
|
||||||
|
*
|
||||||
|
* Может быть вызван вне рендер потока (ибо в любом случае он требует некой "стаитичности" данных в чанке)
|
||||||
|
* но только если до этого был вызыван loadRenderers() и геометрия чанка не поменялась
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
fun bake() {
|
||||||
|
if (state.isSameThread())
|
||||||
|
unloadUnused()
|
||||||
|
|
||||||
|
backgroundRenderer.autoBake(this::getBackgroundView)
|
||||||
|
foregroundRenderer.autoBake(this::getForegroundView)
|
||||||
|
|
||||||
|
if (state.isSameThread())
|
||||||
|
upload()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Загружает в видеопамять всю геометрию напрямую, если есть что загружать
|
||||||
|
*/
|
||||||
|
fun upload() {
|
||||||
|
unloadUnused()
|
||||||
|
|
||||||
|
backgroundRenderer.autoUpload()
|
||||||
|
foregroundRenderer.autoUpload()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun renderDebug() {
|
||||||
|
if (debugCollisions) {
|
||||||
|
state.quadWireframe {
|
||||||
|
it.quad(aabb.mins.x.toFloat(), aabb.mins.y.toFloat(), aabb.maxs.x.toFloat(), aabb.maxs.y.toFloat())
|
||||||
|
|
||||||
|
for (layer in foreground.collisionLayers()) {
|
||||||
|
it.quad(layer.mins.x.toFloat(), layer.mins.y.toFloat(), layer.maxs.x.toFloat(), layer.maxs.y.toFloat())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (renderer in entityRenderers.values) {
|
||||||
|
renderer.renderDebug()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val layerQueue = ArrayDeque<Pair<(Matrix4fStack) -> Unit, Int>>()
|
||||||
|
|
||||||
|
override fun renderLayerFromStack(zPos: Int, stack: Matrix4fStack): Int {
|
||||||
|
if (layerQueue.isEmpty())
|
||||||
|
return -1
|
||||||
|
|
||||||
|
stack.push().translateWithScale(x = pos.x * CHUNK_SIZEf, y = pos.y * CHUNK_SIZEf)
|
||||||
|
var pair = layerQueue.last()
|
||||||
|
|
||||||
|
while (pair.second >= zPos) {
|
||||||
|
pair.first.invoke(stack)
|
||||||
|
|
||||||
|
layerQueue.removeLast()
|
||||||
|
|
||||||
|
if (layerQueue.isEmpty()) {
|
||||||
|
stack.pop()
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
pair = layerQueue.last()
|
||||||
|
}
|
||||||
|
|
||||||
|
stack.pop()
|
||||||
|
return layerQueue.last().second
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bottomMostZLevel(): Int {
|
||||||
|
if (layerQueue.isEmpty()) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return layerQueue.last().second
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun prepareForLayeredRender() {
|
||||||
|
layerQueue.clear()
|
||||||
|
|
||||||
|
for ((baked, zLevel) in backgroundRenderer.bakedMeshes) {
|
||||||
|
layerQueue.add(baked::renderStacked to (zLevel + Z_LEVEL_BACKGROUND))
|
||||||
|
}
|
||||||
|
|
||||||
|
for ((baked, zLevel) in foregroundRenderer.bakedMeshes) {
|
||||||
|
layerQueue.add(baked::renderStacked to zLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (renderer in entityRenderers.values) {
|
||||||
|
layerQueue.add(lambda@{ it: Matrix4fStack ->
|
||||||
|
val relative = renderer.renderPos - posVector2d
|
||||||
|
it.push().translateWithScale(relative.x.toFloat(), relative.y.toFloat())
|
||||||
|
renderer.render(it)
|
||||||
|
it.pop()
|
||||||
|
return@lambda
|
||||||
|
} to renderer.layer)
|
||||||
|
}
|
||||||
|
|
||||||
|
layerQueue.sortBy {
|
||||||
|
return@sortBy it.second
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val entityRenderers = HashMap<Entity, EntityRenderer>()
|
||||||
|
|
||||||
|
override fun onEntityAdded(entity: Entity) {
|
||||||
|
entityRenderers[entity] = EntityRenderer(state, entity, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEntityTransferedToThis(entity: Entity, otherChunk: ClientChunk) {
|
||||||
|
val renderer = otherChunk.entityRenderers[entity] ?: throw IllegalStateException("$otherChunk has no renderer for $entity!")
|
||||||
|
entityRenderers[entity] = renderer
|
||||||
|
renderer.chunk = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEntityTransferedFromThis(entity: Entity, otherChunk: ClientChunk) {
|
||||||
|
entityRenderers.remove(entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEntityRemoved(entity: Entity) {
|
||||||
|
entityRenderers.remove(entity)!!.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
backgroundRenderer.close()
|
||||||
|
foregroundRenderer.close()
|
||||||
|
|
||||||
|
for (renderer in entityRenderers.values) {
|
||||||
|
renderer.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,49 +1,14 @@
|
|||||||
package ru.dbotthepony.kstarbound.client
|
package ru.dbotthepony.kstarbound.client
|
||||||
|
|
||||||
import org.lwjgl.glfw.GLFW.glfwGetTime
|
|
||||||
import ru.dbotthepony.kstarbound.api.IStruct2d
|
|
||||||
import ru.dbotthepony.kstarbound.api.IStruct2f
|
|
||||||
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.math.AABB
|
import ru.dbotthepony.kstarbound.math.AABB
|
||||||
import ru.dbotthepony.kstarbound.math.Vector2d
|
|
||||||
import ru.dbotthepony.kstarbound.world.*
|
import ru.dbotthepony.kstarbound.world.*
|
||||||
|
|
||||||
class ClientWorldChunkTuple(
|
class ClientWorld(val client: StarboundClient, seed: Long = 0L) : World<ClientWorld, ClientChunk>(seed) {
|
||||||
world: World<*>,
|
override fun chunkFactory(pos: ChunkPos): ClientChunk {
|
||||||
chunk: Chunk,
|
return ClientChunk(
|
||||||
top: IWorldChunkTuple?,
|
|
||||||
left: IWorldChunkTuple?,
|
|
||||||
right: IWorldChunkTuple?,
|
|
||||||
bottom: IWorldChunkTuple?,
|
|
||||||
|
|
||||||
val renderer: ChunkRenderer
|
|
||||||
) : MutableWorldChunkTuple(
|
|
||||||
world,
|
|
||||||
chunk,
|
|
||||||
top,
|
|
||||||
left,
|
|
||||||
right,
|
|
||||||
bottom,
|
|
||||||
)
|
|
||||||
|
|
||||||
class ClientWorld(val client: StarboundClient, seed: Long = 0L) : World<ClientWorldChunkTuple>(seed) {
|
|
||||||
override fun tupleFactory(
|
|
||||||
chunk: Chunk,
|
|
||||||
top: IWorldChunkTuple?,
|
|
||||||
left: IWorldChunkTuple?,
|
|
||||||
right: IWorldChunkTuple?,
|
|
||||||
bottom: IWorldChunkTuple?
|
|
||||||
): ClientWorldChunkTuple {
|
|
||||||
return ClientWorldChunkTuple(
|
|
||||||
world = this,
|
world = this,
|
||||||
chunk = chunk,
|
pos = pos,
|
||||||
top = top,
|
|
||||||
left = left,
|
|
||||||
right = right,
|
|
||||||
bottom = bottom,
|
|
||||||
|
|
||||||
renderer = ChunkRenderer(client.gl, chunk, this)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,14 +23,11 @@ class ClientWorld(val client: StarboundClient, seed: Long = 0L) : World<ClientWo
|
|||||||
fun render(
|
fun render(
|
||||||
size: AABB,
|
size: AABB,
|
||||||
) {
|
) {
|
||||||
val determineRenderers = ArrayList<ChunkRenderer>()
|
val determineRenderers = ArrayList<ClientChunk>()
|
||||||
|
|
||||||
for (chunk in collectInternal(size.encasingChunkPosAABB())) {
|
for (chunk in collectInternal(size.encasingChunkPosAABB())) {
|
||||||
determineRenderers.add(chunk.renderer)
|
determineRenderers.add(chunk.chunk)
|
||||||
}
|
chunk.chunk.bake()
|
||||||
|
|
||||||
for (renderer in determineRenderers) {
|
|
||||||
renderer.autoBakeStatic()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderLayeredList(client.gl.matrixStack, determineRenderers)
|
renderLayeredList(client.gl.matrixStack, determineRenderers)
|
||||||
|
@ -198,9 +198,6 @@ class StarboundClient : AutoCloseable {
|
|||||||
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT)
|
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT)
|
||||||
gl.matrixStack.clear(viewportMatrixGame.toMutableMatrix())
|
gl.matrixStack.clear(viewportMatrixGame.toMutableMatrix())
|
||||||
|
|
||||||
val mins = Vector2f((-viewportWidth / 2f) / settings.scale, (-viewportHeight / 2f) / settings.scale)
|
|
||||||
val maxs = -mins
|
|
||||||
|
|
||||||
gl.matrixStack.push()
|
gl.matrixStack.push()
|
||||||
.translateWithScale(viewportWidth / 2f, viewportHeight / 2f, 2f) // центр экрана + координаты отрисовки мира
|
.translateWithScale(viewportWidth / 2f, viewportHeight / 2f, 2f) // центр экрана + координаты отрисовки мира
|
||||||
.scale(x = settings.scale * PIXELS_IN_STARBOUND_UNITf, y = settings.scale * PIXELS_IN_STARBOUND_UNITf) // масштабируем до нужного размера
|
.scale(x = settings.scale * PIXELS_IN_STARBOUND_UNITf, y = settings.scale * PIXELS_IN_STARBOUND_UNITf) // масштабируем до нужного размера
|
||||||
|
@ -111,12 +111,12 @@ class GLStateTracker {
|
|||||||
var cleanManual = false
|
var cleanManual = false
|
||||||
|
|
||||||
val cleanable = cleaner.register(ref) {
|
val cleanable = cleaner.register(ref) {
|
||||||
|
if (!cleanManual)
|
||||||
|
LOGGER.error("{} with ID {} got leaked.", name, nativeRef)
|
||||||
|
|
||||||
cleanerHits.add {
|
cleanerHits.add {
|
||||||
fn(nativeRef)
|
fn(nativeRef)
|
||||||
checkForGLError()
|
checkForGLError()
|
||||||
|
|
||||||
if (!cleanManual)
|
|
||||||
LOGGER.error("{} with ID {} got leaked.", name, nativeRef)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import ru.dbotthepony.kstarbound.client.gl.GLVertexArrayObject
|
|||||||
import ru.dbotthepony.kstarbound.client.gl.DynamicVertexBuilder
|
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
|
||||||
|
import ru.dbotthepony.kstarbound.math.Matrix4fStack
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Служит для быстрой настройки состояния для будущей отрисовки
|
* Служит для быстрой настройки состояния для будущей отрисовки
|
||||||
@ -83,6 +84,8 @@ class BakedStaticMesh(
|
|||||||
checkForGLError()
|
checkForGLError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun renderStacked(transform: Matrix4fStack) = render(transform.last)
|
||||||
|
|
||||||
var isValid = true
|
var isValid = true
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
@ -1,287 +0,0 @@
|
|||||||
package ru.dbotthepony.kstarbound.client.render
|
|
||||||
|
|
||||||
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
|
|
||||||
import ru.dbotthepony.kstarbound.client.ClientWorld
|
|
||||||
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
|
|
||||||
import ru.dbotthepony.kstarbound.math.FloatMatrix
|
|
||||||
import ru.dbotthepony.kstarbound.math.Matrix4f
|
|
||||||
import ru.dbotthepony.kstarbound.math.Matrix4fStack
|
|
||||||
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.ITileChunk
|
|
||||||
import kotlin.collections.ArrayList
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Псевдо zPos у фоновых тайлов
|
|
||||||
*
|
|
||||||
* Добавление этого числа к zPos гарантирует, что фоновые тайлы будут отрисованы
|
|
||||||
* первыми (на самом дальнем плане)
|
|
||||||
*/
|
|
||||||
const val Z_LEVEL_BACKGROUND = 60000
|
|
||||||
|
|
||||||
class ChunkRenderer(val state: GLStateTracker, val chunk: Chunk, val world: ClientWorld? = null) : AutoCloseable, ILayeredRenderer {
|
|
||||||
private inner class TileLayerRenderer(private val layerChangeset: () -> Int, private val isBackground: Boolean) : AutoCloseable {
|
|
||||||
private val layers = TileLayerList()
|
|
||||||
val bakedMeshes = ArrayList<Pair<BakedStaticMesh, Int>>()
|
|
||||||
private var changeset = -1
|
|
||||||
|
|
||||||
fun tesselateStatic(view: ITileChunk) {
|
|
||||||
if (state.isSameThread()) {
|
|
||||||
for (mesh in bakedMeshes) {
|
|
||||||
mesh.first.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
bakedMeshes.clear()
|
|
||||||
} else {
|
|
||||||
for (mesh in bakedMeshes) {
|
|
||||||
unloadableBakedMeshes.add(mesh.first)
|
|
||||||
}
|
|
||||||
|
|
||||||
bakedMeshes.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
layers.clear()
|
|
||||||
|
|
||||||
for ((pos, tile) in view.posToTile) {
|
|
||||||
if (tile != null) {
|
|
||||||
val renderer = state.tileRenderers.get(tile.def.materialName)
|
|
||||||
renderer.tesselate(view, layers, pos, background = isBackground)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadRenderers(view: ITileChunk) {
|
|
||||||
for ((_, tile) in view.posToTile) {
|
|
||||||
if (tile != null) {
|
|
||||||
state.tileRenderers.get(tile.def.materialName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun uploadStatic(clear: Boolean = true) {
|
|
||||||
for ((baked, builder, zLevel) in layers.buildList()) {
|
|
||||||
bakedMeshes.add(BakedStaticMesh(baked, builder) to zLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clear) {
|
|
||||||
layers.clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun render(transform: FloatMatrix<*>) {
|
|
||||||
for (mesh in bakedMeshes) {
|
|
||||||
mesh.first.render(transform)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun bakeAndRender(transform: FloatMatrix<*>, provider: () -> ITileChunk) {
|
|
||||||
if (changeset != layerChangeset.invoke()) {
|
|
||||||
this.tesselateStatic(provider.invoke())
|
|
||||||
this.uploadStatic()
|
|
||||||
|
|
||||||
changeset = layerChangeset.invoke()
|
|
||||||
}
|
|
||||||
|
|
||||||
render(transform)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun autoBake(provider: () -> ITileChunk) {
|
|
||||||
if (changeset != layerChangeset.invoke()) {
|
|
||||||
this.tesselateStatic(provider.invoke())
|
|
||||||
this.uploadStatic()
|
|
||||||
|
|
||||||
changeset = layerChangeset.invoke()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun autoUpload() {
|
|
||||||
if (layers.isNotEmpty) {
|
|
||||||
for (mesh in bakedMeshes) {
|
|
||||||
mesh.first.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
bakedMeshes.clear()
|
|
||||||
|
|
||||||
for ((baked, builder, zLevel) in layers.buildList()) {
|
|
||||||
bakedMeshes.add(BakedStaticMesh(baked, builder) to zLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
layers.clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun close() {
|
|
||||||
for (mesh in bakedMeshes) {
|
|
||||||
mesh.first.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
private val unloadableBakedMeshes = ArrayList<BakedStaticMesh>()
|
|
||||||
|
|
||||||
private val foreground = TileLayerRenderer(chunk.foreground::changeset, isBackground = false)
|
|
||||||
private val background = TileLayerRenderer(chunk.background::changeset, isBackground = true)
|
|
||||||
|
|
||||||
private fun getForeground(): ITileChunk {
|
|
||||||
return world?.getForegroundView(chunk.pos) ?: chunk.foreground
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getBackground(): ITileChunk {
|
|
||||||
return world?.getBackgroundView(chunk.pos) ?: chunk.background
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Тесселирует "статичную" геометрию в builders (к примеру тайлы).
|
|
||||||
*
|
|
||||||
* Может быть вызван вне рендер потока (ибо в любом случае он требует некой "стаитичности" данных в чанке)
|
|
||||||
* но только если до этого был вызыван loadRenderers() и геометрия чанка не поменялась
|
|
||||||
*/
|
|
||||||
fun tesselateStatic() {
|
|
||||||
foreground.tesselateStatic(getForeground())
|
|
||||||
background.tesselateStatic(getBackground())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Принудительно подгружает в GLStateTracker все необходимые рендереры (ибо им нужны текстуры и прочее)
|
|
||||||
*
|
|
||||||
* Вызывается перед tesselateStatic()
|
|
||||||
*/
|
|
||||||
fun loadRenderers() {
|
|
||||||
unloadUnused()
|
|
||||||
|
|
||||||
foreground.loadRenderers(getForeground())
|
|
||||||
background.loadRenderers(getBackground())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun unloadUnused() {
|
|
||||||
if (unloadableBakedMeshes.size != 0) {
|
|
||||||
for (baked in unloadableBakedMeshes) {
|
|
||||||
baked.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
unloadableBakedMeshes.clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun uploadStatic(clear: Boolean = true) {
|
|
||||||
unloadUnused()
|
|
||||||
|
|
||||||
foreground.uploadStatic(clear)
|
|
||||||
background.uploadStatic(clear)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Отрисовывает всю геометрию напрямую
|
|
||||||
*/
|
|
||||||
fun render(transform: FloatMatrix<*> = state.matrixStack.last) {
|
|
||||||
unloadUnused()
|
|
||||||
|
|
||||||
background.render(transform)
|
|
||||||
foreground.render(transform)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Отрисовывает всю геометрию напрямую, с проверкой, изменился ли чанк
|
|
||||||
*/
|
|
||||||
fun bakeAndRender(transform: FloatMatrix<*> = state.matrixStack.last) {
|
|
||||||
unloadUnused()
|
|
||||||
|
|
||||||
background.bakeAndRender(transform, this::getBackground)
|
|
||||||
foreground.bakeAndRender(transform, this::getForeground)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Запекает всю геометрию напрямую, с проверкой, изменился ли чанк,
|
|
||||||
* и загружает её, если вызвано в рендер потоке
|
|
||||||
*/
|
|
||||||
fun autoBakeStatic() {
|
|
||||||
if (state.isSameThread())
|
|
||||||
unloadUnused()
|
|
||||||
|
|
||||||
background.autoBake(this::getBackground)
|
|
||||||
foreground.autoBake(this::getForeground)
|
|
||||||
|
|
||||||
if (state.isSameThread())
|
|
||||||
autoUploadStatic()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Загружает в видеопамять всю геометрию напрямую, если есть что загружать
|
|
||||||
*/
|
|
||||||
fun autoUploadStatic() {
|
|
||||||
unloadUnused()
|
|
||||||
|
|
||||||
background.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>>()
|
|
||||||
|
|
||||||
override fun renderLayerFromStack(zPos: Int, transform: Matrix4fStack): Int {
|
|
||||||
if (meshDeque.isEmpty())
|
|
||||||
return -1
|
|
||||||
|
|
||||||
transform.push().translateWithScale(x = chunk.pos.x * CHUNK_SIZEf, y = chunk.pos.y * CHUNK_SIZEf)
|
|
||||||
var pair = meshDeque.last()
|
|
||||||
|
|
||||||
while (pair.second >= zPos) {
|
|
||||||
pair.first.render(transform.last)
|
|
||||||
|
|
||||||
meshDeque.removeLast()
|
|
||||||
|
|
||||||
if (meshDeque.isEmpty()) {
|
|
||||||
transform.pop()
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
pair = meshDeque.last()
|
|
||||||
}
|
|
||||||
|
|
||||||
transform.pop()
|
|
||||||
return meshDeque.last().second
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun bottomMostZLevel(): Int {
|
|
||||||
if (meshDeque.isEmpty()) {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
return meshDeque.last().second
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun prepareForLayeredRender() {
|
|
||||||
meshDeque.clear()
|
|
||||||
|
|
||||||
for ((baked, zLevel) in background.bakedMeshes) {
|
|
||||||
meshDeque.add(baked to (zLevel + Z_LEVEL_BACKGROUND))
|
|
||||||
}
|
|
||||||
|
|
||||||
meshDeque.addAll(foreground.bakedMeshes)
|
|
||||||
|
|
||||||
meshDeque.sortBy {
|
|
||||||
return@sortBy it.second
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun close() {
|
|
||||||
background.close()
|
|
||||||
foreground.close()
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,33 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.client.render
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.client.ClientChunk
|
||||||
|
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
|
||||||
|
import ru.dbotthepony.kstarbound.math.Matrix4fStack
|
||||||
|
import ru.dbotthepony.kstarbound.math.Vector2d
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.Entity
|
||||||
|
import java.io.Closeable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Базовый класс, отвечающий за отрисовку определённого ентити в мире
|
||||||
|
*
|
||||||
|
* Считается, что процесс отрисовки ограничен лишь одним слоем (т.е. отрисовка происходит в один проход)
|
||||||
|
*/
|
||||||
|
open class EntityRenderer(val state: GLStateTracker, val entity: Entity, open var chunk: ClientChunk?) : Closeable {
|
||||||
|
open val renderPos: Vector2d get() = entity.pos
|
||||||
|
|
||||||
|
open fun render(stack: Matrix4fStack) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun renderDebug() {
|
||||||
|
if (chunk?.world?.client?.settings?.debugCollisions == true) {
|
||||||
|
state.quadWireframe(entity.worldaabb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open val layer: Int = 100
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,5 @@
|
|||||||
package ru.dbotthepony.kstarbound.client.render
|
package ru.dbotthepony.kstarbound.client.render
|
||||||
|
|
||||||
import ru.dbotthepony.kstarbound.math.FloatMatrix
|
|
||||||
import ru.dbotthepony.kstarbound.math.Matrix4f
|
|
||||||
import ru.dbotthepony.kstarbound.math.Matrix4fStack
|
import ru.dbotthepony.kstarbound.math.Matrix4fStack
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -24,7 +22,7 @@ interface ILayeredRenderer {
|
|||||||
* Если следующего слоя нет, вернуть -1, и данный объект
|
* Если следующего слоя нет, вернуть -1, и данный объект
|
||||||
* будет считаться отрисованным.
|
* будет считаться отрисованным.
|
||||||
*/
|
*/
|
||||||
fun renderLayerFromStack(zPos: Int, transform: Matrix4fStack): Int
|
fun renderLayerFromStack(zPos: Int, stack: Matrix4fStack): Int
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Возвращает наибольшее zPos в данной стопке.
|
* Возвращает наибольшее zPos в данной стопке.
|
@ -342,6 +342,7 @@ abstract class IVector2d<T : IVector2d<T>> : IMatrixLike, IMatrixLikeDouble, ISt
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected abstract fun make(x: Double, y: Double): T
|
protected abstract fun make(x: Double, y: Double): T
|
||||||
|
fun toFloatVector(): Vector2f = Vector2f(x.toFloat(), y.toFloat())
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Vector2d(override val x: Double = 0.0, override val y: Double = 0.0) : IVector2d<Vector2d>() {
|
data class Vector2d(override val x: Double = 0.0, override val y: Double = 0.0) : IVector2d<Vector2d>() {
|
||||||
|
@ -4,14 +4,16 @@ import ru.dbotthepony.kstarbound.api.IStruct2d
|
|||||||
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.*
|
import ru.dbotthepony.kstarbound.math.*
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.Entity
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
import kotlin.collections.HashSet
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Представляет из себя класс, который содержит состояние тайла на заданной позиции
|
* Представляет из себя класс, который содержит состояние тайла на заданной позиции
|
||||||
*/
|
*/
|
||||||
data class ChunkTile(val chunk: Chunk.TileLayer, val def: TileDefinition) {
|
data class ChunkTile(val chunk: Chunk<*, *>.TileLayer, val def: TileDefinition) {
|
||||||
var color = 0
|
var color = 0
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
@ -322,7 +324,7 @@ class MutableTileChunkView(
|
|||||||
*
|
*
|
||||||
* Весь игровой мир будет измеряться в Starbound Unit'ах
|
* Весь игровой мир будет измеряться в Starbound Unit'ах
|
||||||
*/
|
*/
|
||||||
open class Chunk(val world: World<*>?, val pos: ChunkPos) {
|
abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType, This>>(val world: WorldType, val pos: ChunkPos) {
|
||||||
/**
|
/**
|
||||||
* Возвращает счётчик изменений чанка
|
* Возвращает счётчик изменений чанка
|
||||||
*/
|
*/
|
||||||
@ -355,8 +357,6 @@ open class Chunk(val world: World<*>?, val pos: ChunkPos) {
|
|||||||
// TODO: https://ru.wikipedia.org/wiki/R-дерево_(структура_данных)
|
// TODO: https://ru.wikipedia.org/wiki/R-дерево_(структура_данных)
|
||||||
private fun bakeCollisions() {
|
private fun bakeCollisions() {
|
||||||
collisionChangeset = changeset
|
collisionChangeset = changeset
|
||||||
val seen = BooleanArray(tiles.size)
|
|
||||||
|
|
||||||
collisionCache.clear()
|
collisionCache.clear()
|
||||||
|
|
||||||
val xAdd = pos.x * CHUNK_SIZEd
|
val xAdd = pos.x * CHUNK_SIZEd
|
||||||
@ -448,6 +448,49 @@ open class Chunk(val world: World<*>?, val pos: ChunkPos) {
|
|||||||
|
|
||||||
val foreground = TileLayer()
|
val foreground = TileLayer()
|
||||||
val background = TileLayer()
|
val background = TileLayer()
|
||||||
|
protected val entities = HashSet<Entity>()
|
||||||
|
val entitiesAccess = Collections.unmodifiableSet(entities)
|
||||||
|
|
||||||
|
protected abstract fun onEntityAdded(entity: Entity)
|
||||||
|
protected abstract fun onEntityTransferedToThis(entity: Entity, otherChunk: This)
|
||||||
|
protected abstract fun onEntityTransferedFromThis(entity: Entity, otherChunk: This)
|
||||||
|
protected abstract fun onEntityRemoved(entity: Entity)
|
||||||
|
|
||||||
|
fun addEntity(entity: Entity) {
|
||||||
|
if (!entities.add(entity)) {
|
||||||
|
throw IllegalArgumentException("Already having having entity $entity")
|
||||||
|
}
|
||||||
|
|
||||||
|
onEntityAdded(entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun transferEntity(entity: Entity, otherChunk: Chunk<*, *>) {
|
||||||
|
if (otherChunk == this)
|
||||||
|
throw IllegalArgumentException("what?")
|
||||||
|
|
||||||
|
if (this::class.java != otherChunk::class.java) {
|
||||||
|
throw IllegalArgumentException("Incompatible types: $this !is $otherChunk")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!entities.add(entity)) {
|
||||||
|
throw IllegalArgumentException("Already containing $entity")
|
||||||
|
}
|
||||||
|
|
||||||
|
onEntityTransferedToThis(entity, otherChunk as This)
|
||||||
|
otherChunk.onEntityTransferedFromThis(entity, this as This)
|
||||||
|
|
||||||
|
if (!otherChunk.entities.remove(entity)) {
|
||||||
|
throw IllegalStateException("Unable to remove $entity from $otherChunk after transfer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeEntity(entity: Entity) {
|
||||||
|
if (!entities.remove(entity)) {
|
||||||
|
throw IllegalArgumentException("Already not having entity $entity")
|
||||||
|
}
|
||||||
|
|
||||||
|
onEntityRemoved(entity)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val EMPTY = object : IMutableTileChunk {
|
val EMPTY = object : IMutableTileChunk {
|
||||||
|
@ -8,41 +8,78 @@ import ru.dbotthepony.kstarbound.world.entities.Entity
|
|||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Возвращает кортеж чанка, который содержит родителя (мир) и соседей (кортежи чанков)
|
* Кортеж чанка, который содержит родителя (мир) и соседей (кортежи чанков)
|
||||||
*/
|
*/
|
||||||
interface IWorldChunkTuple {
|
interface IWorldChunkTuple<WorldType : World<WorldType, ChunkType>, ChunkType : Chunk<WorldType, ChunkType>> {
|
||||||
val world: World<*>
|
val world: WorldType
|
||||||
val chunk: Chunk
|
val chunk: ChunkType
|
||||||
val top: IWorldChunkTuple?
|
val top: IWorldChunkTuple<WorldType, ChunkType>?
|
||||||
val left: IWorldChunkTuple?
|
val left: IWorldChunkTuple<WorldType, ChunkType>?
|
||||||
val right: IWorldChunkTuple?
|
val right: IWorldChunkTuple<WorldType, ChunkType>?
|
||||||
val bottom: IWorldChunkTuple?
|
val bottom: IWorldChunkTuple<WorldType, ChunkType>?
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IMutableWorldChunkTuple : IWorldChunkTuple {
|
interface IMutableWorldChunkTuple<WorldType : World<WorldType, ChunkType>, ChunkType : Chunk<WorldType, ChunkType>> : IWorldChunkTuple<WorldType, ChunkType> {
|
||||||
override var top: IWorldChunkTuple?
|
override var top: IMutableWorldChunkTuple<WorldType, ChunkType>?
|
||||||
override var left: IWorldChunkTuple?
|
override var left: IMutableWorldChunkTuple<WorldType, ChunkType>?
|
||||||
override var right: IWorldChunkTuple?
|
override var right: IMutableWorldChunkTuple<WorldType, ChunkType>?
|
||||||
override var bottom: IWorldChunkTuple?
|
override var bottom: IMutableWorldChunkTuple<WorldType, ChunkType>?
|
||||||
}
|
}
|
||||||
|
|
||||||
data class WorldChunkTuple(
|
class WorldChunkTuple<WorldType : World<WorldType, ChunkType>, ChunkType : Chunk<WorldType, ChunkType>>(
|
||||||
override val world: World<*>,
|
private val parent: IWorldChunkTuple<WorldType, ChunkType>
|
||||||
override val chunk: Chunk,
|
) : IWorldChunkTuple<WorldType, ChunkType> {
|
||||||
override val top: IWorldChunkTuple?,
|
override val world get() = parent.world
|
||||||
override val left: IWorldChunkTuple?,
|
override val chunk get() = parent.chunk
|
||||||
override val right: IWorldChunkTuple?,
|
override val top: IWorldChunkTuple<WorldType, ChunkType>? get() {
|
||||||
override val bottom: IWorldChunkTuple?,
|
val getValue = parent.top
|
||||||
) : IWorldChunkTuple
|
|
||||||
|
|
||||||
open class MutableWorldChunkTuple(
|
if (getValue != null) {
|
||||||
override val world: World<*>,
|
return WorldChunkTuple(getValue)
|
||||||
override val chunk: Chunk,
|
}
|
||||||
override var top: IWorldChunkTuple?,
|
|
||||||
override var left: IWorldChunkTuple?,
|
return null
|
||||||
override var right: IWorldChunkTuple?,
|
}
|
||||||
override var bottom: IWorldChunkTuple?,
|
|
||||||
) : IMutableWorldChunkTuple
|
override val left: IWorldChunkTuple<WorldType, ChunkType>? get() {
|
||||||
|
val getValue = parent.left
|
||||||
|
|
||||||
|
if (getValue != null) {
|
||||||
|
return WorldChunkTuple(getValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override val right: IWorldChunkTuple<WorldType, ChunkType>? get() {
|
||||||
|
val getValue = parent.right
|
||||||
|
|
||||||
|
if (getValue != null) {
|
||||||
|
return WorldChunkTuple(getValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override val bottom: IWorldChunkTuple<WorldType, ChunkType>? get() {
|
||||||
|
val getValue = parent.bottom
|
||||||
|
|
||||||
|
if (getValue != null) {
|
||||||
|
return WorldChunkTuple(getValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open class MutableWorldChunkTuple<WorldType : World<WorldType, ChunkType>, ChunkType : Chunk<WorldType, ChunkType>>(
|
||||||
|
override val world: WorldType,
|
||||||
|
override val chunk: ChunkType,
|
||||||
|
override var top: IMutableWorldChunkTuple<WorldType, ChunkType>?,
|
||||||
|
override var left: IMutableWorldChunkTuple<WorldType, ChunkType>?,
|
||||||
|
override var right: IMutableWorldChunkTuple<WorldType, ChunkType>?,
|
||||||
|
override var bottom: IMutableWorldChunkTuple<WorldType, ChunkType>?,
|
||||||
|
) : IMutableWorldChunkTuple<WorldType, ChunkType>
|
||||||
|
|
||||||
const val EARTH_FREEFALL_ACCELERATION = 9.8312 / METRES_IN_STARBOUND_UNIT
|
const val EARTH_FREEFALL_ACCELERATION = 9.8312 / METRES_IN_STARBOUND_UNIT
|
||||||
|
|
||||||
@ -55,9 +92,9 @@ data class WorldSweepResult(
|
|||||||
|
|
||||||
private const val EPSILON = 0.00001
|
private const val EPSILON = 0.00001
|
||||||
|
|
||||||
abstract class World<T : IMutableWorldChunkTuple>(val seed: Long = 0L) {
|
abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, ChunkType>>(val seed: Long = 0L) {
|
||||||
protected val chunkMap = HashMap<ChunkPos, T>()
|
protected val chunkMap = HashMap<ChunkPos, IMutableWorldChunkTuple<This, ChunkType>>()
|
||||||
protected var lastAccessedChunk: T? = null
|
protected var lastAccessedChunk: IMutableWorldChunkTuple<This, ChunkType>? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Таймер этого мира, в секундах.
|
* Таймер этого мира, в секундах.
|
||||||
@ -106,15 +143,11 @@ abstract class World<T : IMutableWorldChunkTuple>(val seed: Long = 0L) {
|
|||||||
*/
|
*/
|
||||||
var gravity = Vector2d(0.0, -EARTH_FREEFALL_ACCELERATION)
|
var gravity = Vector2d(0.0, -EARTH_FREEFALL_ACCELERATION)
|
||||||
|
|
||||||
protected abstract fun tupleFactory(
|
protected abstract fun chunkFactory(
|
||||||
chunk: Chunk,
|
pos: ChunkPos,
|
||||||
top: IWorldChunkTuple?,
|
): ChunkType
|
||||||
left: IWorldChunkTuple?,
|
|
||||||
right: IWorldChunkTuple?,
|
|
||||||
bottom: IWorldChunkTuple?,
|
|
||||||
): T
|
|
||||||
|
|
||||||
protected fun getChunkInternal(pos: ChunkPos): T? {
|
protected fun getChunkInternal(pos: ChunkPos): IMutableWorldChunkTuple<This, ChunkType>? {
|
||||||
if (lastAccessedChunk?.chunk?.pos == pos) {
|
if (lastAccessedChunk?.chunk?.pos == pos) {
|
||||||
return lastAccessedChunk
|
return lastAccessedChunk
|
||||||
}
|
}
|
||||||
@ -122,36 +155,30 @@ abstract class World<T : IMutableWorldChunkTuple>(val seed: Long = 0L) {
|
|||||||
return chunkMap[pos]
|
return chunkMap[pos]
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun getChunk(pos: ChunkPos): IWorldChunkTuple? {
|
open fun getChunk(pos: ChunkPos): IWorldChunkTuple<This, ChunkType>? {
|
||||||
val getTuple = getChunkInternal(pos)
|
val getTuple = getChunkInternal(pos)
|
||||||
|
|
||||||
if (getTuple != null)
|
if (getTuple != null)
|
||||||
return WorldChunkTuple(
|
return WorldChunkTuple(getTuple)
|
||||||
world = getTuple.world,
|
|
||||||
chunk = getTuple.chunk,
|
|
||||||
top = getTuple.top,
|
|
||||||
left = getTuple.left,
|
|
||||||
right = getTuple.right,
|
|
||||||
bottom = getTuple.bottom,
|
|
||||||
)
|
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun computeIfAbsentInternal(pos: ChunkPos): T {
|
protected open fun computeIfAbsentInternal(pos: ChunkPos): IWorldChunkTuple<This, ChunkType> {
|
||||||
if (lastAccessedChunk?.chunk?.pos == pos) {
|
if (lastAccessedChunk?.chunk?.pos == pos) {
|
||||||
return lastAccessedChunk!!
|
return lastAccessedChunk!!
|
||||||
}
|
}
|
||||||
|
|
||||||
return chunkMap.computeIfAbsent(pos) lazy@{
|
return chunkMap.computeIfAbsent(pos) lazy@{
|
||||||
val chunk = Chunk(this, pos)
|
val chunk = chunkFactory(pos)
|
||||||
|
|
||||||
val top = getChunkInternal(pos.up())
|
val top = getChunkInternal(pos.up())
|
||||||
val left = getChunkInternal(pos.left())
|
val left = getChunkInternal(pos.left())
|
||||||
val right = getChunkInternal(pos.right())
|
val right = getChunkInternal(pos.right())
|
||||||
val bottom = getChunkInternal(pos.down())
|
val bottom = getChunkInternal(pos.down())
|
||||||
|
|
||||||
val tuple = tupleFactory(
|
val tuple = MutableWorldChunkTuple(
|
||||||
|
world = this as This,
|
||||||
chunk = chunk,
|
chunk = chunk,
|
||||||
top = top,
|
top = top,
|
||||||
left = left,
|
left = left,
|
||||||
@ -184,17 +211,8 @@ abstract class World<T : IMutableWorldChunkTuple>(val seed: Long = 0L) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun computeIfAbsent(pos: ChunkPos): IWorldChunkTuple {
|
open fun computeIfAbsent(pos: ChunkPos): IWorldChunkTuple<This, ChunkType> {
|
||||||
val getTuple = computeIfAbsentInternal(pos)
|
return WorldChunkTuple(computeIfAbsentInternal(pos))
|
||||||
|
|
||||||
return WorldChunkTuple(
|
|
||||||
world = getTuple.world,
|
|
||||||
chunk = getTuple.chunk,
|
|
||||||
top = getTuple.top,
|
|
||||||
left = getTuple.left,
|
|
||||||
right = getTuple.right,
|
|
||||||
bottom = getTuple.bottom,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun getForegroundView(pos: ChunkPos): TileChunkView? {
|
open fun getForegroundView(pos: ChunkPos): TileChunkView? {
|
||||||
@ -233,7 +251,7 @@ abstract class World<T : IMutableWorldChunkTuple>(val seed: Long = 0L) {
|
|||||||
return getChunkInternal(ChunkPos.fromTilePosition(pos))?.chunk?.foreground?.get(ChunkPos.normalizeCoordinate(pos.x), ChunkPos.normalizeCoordinate(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<This, ChunkType> {
|
||||||
val chunk = computeIfAbsentInternal(ChunkPos.fromTilePosition(pos))
|
val chunk = computeIfAbsentInternal(ChunkPos.fromTilePosition(pos))
|
||||||
chunk.chunk.foreground[ChunkPos.normalizeCoordinate(pos.x), ChunkPos.normalizeCoordinate(pos.y)] = tile
|
chunk.chunk.foreground[ChunkPos.normalizeCoordinate(pos.x), ChunkPos.normalizeCoordinate(pos.y)] = tile
|
||||||
return chunk
|
return chunk
|
||||||
@ -243,14 +261,14 @@ abstract class World<T : IMutableWorldChunkTuple>(val seed: Long = 0L) {
|
|||||||
return getChunkInternal(ChunkPos.fromTilePosition(pos))?.chunk?.background?.get(ChunkPos.normalizeCoordinate(pos.x), ChunkPos.normalizeCoordinate(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<This, ChunkType> {
|
||||||
val chunk = computeIfAbsentInternal(ChunkPos.fromTilePosition(pos))
|
val chunk = computeIfAbsentInternal(ChunkPos.fromTilePosition(pos))
|
||||||
chunk.chunk.background[ChunkPos.normalizeCoordinate(pos.x), ChunkPos.normalizeCoordinate(pos.y)] = 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> {
|
protected open fun collectInternal(boundingBox: AABBi): List<IMutableWorldChunkTuple<This, ChunkType>> {
|
||||||
val output = ArrayList<T>()
|
val output = ArrayList<IMutableWorldChunkTuple<This, ChunkType>>()
|
||||||
|
|
||||||
for (pos in boundingBox.chunkPositions) {
|
for (pos in boundingBox.chunkPositions) {
|
||||||
val chunk = getChunkInternal(pos)
|
val chunk = getChunkInternal(pos)
|
||||||
@ -266,18 +284,11 @@ abstract class World<T : IMutableWorldChunkTuple>(val seed: Long = 0L) {
|
|||||||
/**
|
/**
|
||||||
* Возвращает все чанки, которые пересекаются с заданным [boundingBox]
|
* Возвращает все чанки, которые пересекаются с заданным [boundingBox]
|
||||||
*/
|
*/
|
||||||
open fun collect(boundingBox: AABBi): List<IWorldChunkTuple> {
|
open fun collect(boundingBox: AABBi): List<IWorldChunkTuple<This, ChunkType>> {
|
||||||
val output = ArrayList<IWorldChunkTuple>()
|
val output = ArrayList<IWorldChunkTuple<This, ChunkType>>()
|
||||||
|
|
||||||
for (chunk in collectInternal(boundingBox)) {
|
for (chunk in collectInternal(boundingBox)) {
|
||||||
output.add(WorldChunkTuple(
|
output.add(WorldChunkTuple(chunk))
|
||||||
world = chunk.world,
|
|
||||||
chunk = chunk.chunk,
|
|
||||||
top = chunk.top,
|
|
||||||
left = chunk.left,
|
|
||||||
right = chunk.right,
|
|
||||||
bottom = chunk.bottom,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
@ -12,7 +12,7 @@ enum class Move {
|
|||||||
MOVE_RIGHT
|
MOVE_RIGHT
|
||||||
}
|
}
|
||||||
|
|
||||||
open class AliveEntity(world: World<*>) : Entity(world) {
|
open class AliveEntity(world: World<*, *>) : Entity(world) {
|
||||||
open var maxHealth = 10.0
|
open var maxHealth = 10.0
|
||||||
open var health = 10.0
|
open var health = 10.0
|
||||||
open val moveDirection = Move.STAND_STILL
|
open val moveDirection = Move.STAND_STILL
|
||||||
@ -20,12 +20,12 @@ open class AliveEntity(world: World<*>) : Entity(world) {
|
|||||||
|
|
||||||
open val aabbDucked get() = aabb
|
open val aabbDucked get() = aabb
|
||||||
|
|
||||||
override val worldaabb: AABB get() {
|
override val currentaabb: AABB get() {
|
||||||
if (isDucked) {
|
if (isDucked) {
|
||||||
return aabbDucked + pos
|
return aabbDucked
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.worldaabb
|
return super.currentaabb
|
||||||
}
|
}
|
||||||
|
|
||||||
var wantsToDuck = false
|
var wantsToDuck = false
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package ru.dbotthepony.kstarbound.world.entities
|
package ru.dbotthepony.kstarbound.world.entities
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
|
import ru.dbotthepony.kstarbound.client.render.EntityRenderer
|
||||||
import ru.dbotthepony.kstarbound.math.AABB
|
import ru.dbotthepony.kstarbound.math.AABB
|
||||||
import ru.dbotthepony.kstarbound.math.Vector2d
|
import ru.dbotthepony.kstarbound.math.Vector2d
|
||||||
import ru.dbotthepony.kstarbound.math.lerp
|
import ru.dbotthepony.kstarbound.math.lerp
|
||||||
@ -18,10 +19,10 @@ enum class CollisionResolution {
|
|||||||
/**
|
/**
|
||||||
* Определяет из себя сущность в мире, которая имеет позицию, скорость и коробку столкновений
|
* Определяет из себя сущность в мире, которая имеет позицию, скорость и коробку столкновений
|
||||||
*/
|
*/
|
||||||
open class Entity(val world: World<*>) {
|
open class Entity(val world: World<*, *>) {
|
||||||
var chunk: Chunk? = null
|
var chunk: Chunk<*, *>? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
if (!spawned) {
|
if (!isSpawned) {
|
||||||
throw IllegalStateException("Trying to set chunk this entity belong to before spawning in world")
|
throw IllegalStateException("Trying to set chunk this entity belong to before spawning in world")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,17 +36,22 @@ open class Entity(val world: World<*>) {
|
|||||||
throw IllegalStateException("Set proper position before setting chunk this Entity belongs to")
|
throw IllegalStateException("Set proper position before setting chunk this Entity belongs to")
|
||||||
}
|
}
|
||||||
|
|
||||||
val oldChunk = chunk
|
val oldChunk = field
|
||||||
field = value
|
field = value
|
||||||
|
|
||||||
if (oldChunk == null && value != null) {
|
if (oldChunk == null && value != null) {
|
||||||
world.orphanedEntities.remove(this)
|
world.orphanedEntities.remove(this)
|
||||||
|
value.addEntity(this)
|
||||||
} else if (oldChunk != null && value == null) {
|
} else if (oldChunk != null && value == null) {
|
||||||
world.orphanedEntities.add(this)
|
world.orphanedEntities.add(this)
|
||||||
|
oldChunk.removeEntity(this)
|
||||||
|
} else if (oldChunk != null && value != null) {
|
||||||
|
value.transferEntity(this, oldChunk)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open val worldaabb: AABB get() = aabb + pos
|
open val currentaabb: AABB get() = aabb
|
||||||
|
open val worldaabb: AABB get() = currentaabb + pos
|
||||||
|
|
||||||
var pos = Vector2d()
|
var pos = Vector2d()
|
||||||
set(value) {
|
set(value) {
|
||||||
@ -55,7 +61,7 @@ open class Entity(val world: World<*>) {
|
|||||||
val old = field
|
val old = field
|
||||||
field = value
|
field = value
|
||||||
|
|
||||||
if (spawned) {
|
if (isSpawned) {
|
||||||
val oldChunkPos = ChunkPos.fromTilePosition(old)
|
val oldChunkPos = ChunkPos.fromTilePosition(old)
|
||||||
val newChunkPos = ChunkPos.fromTilePosition(value)
|
val newChunkPos = ChunkPos.fromTilePosition(value)
|
||||||
|
|
||||||
@ -66,21 +72,36 @@ open class Entity(val world: World<*>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var velocity = Vector2d()
|
var velocity = Vector2d()
|
||||||
private var spawned = false
|
var isSpawned = false
|
||||||
|
private set
|
||||||
|
var isRemoved = false
|
||||||
|
private set
|
||||||
|
|
||||||
fun spawn() {
|
fun spawn() {
|
||||||
if (spawned)
|
if (isSpawned)
|
||||||
throw IllegalStateException("Already spawned")
|
throw IllegalStateException("Already spawned")
|
||||||
|
|
||||||
spawned = true
|
isSpawned = true
|
||||||
world.entities.add(this)
|
world.entities.add(this)
|
||||||
chunk = world.getChunk(ChunkPos.ZERO)?.chunk
|
chunk = world.getChunk(ChunkPos.fromTilePosition(pos))?.chunk
|
||||||
|
|
||||||
if (chunk == null) {
|
if (chunk == null) {
|
||||||
world.orphanedEntities.add(this)
|
world.orphanedEntities.add(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun remove() {
|
||||||
|
if (isRemoved)
|
||||||
|
throw IllegalStateException("Already removed")
|
||||||
|
|
||||||
|
isRemoved = true
|
||||||
|
|
||||||
|
if (isSpawned) {
|
||||||
|
world.entities.remove(this)
|
||||||
|
chunk?.removeEntity(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Касается ли сущность земли
|
* Касается ли сущность земли
|
||||||
*
|
*
|
||||||
@ -170,7 +191,7 @@ open class Entity(val world: World<*>) {
|
|||||||
* Заставляет сущность "думать".
|
* Заставляет сущность "думать".
|
||||||
*/
|
*/
|
||||||
fun think(delta: Double) {
|
fun think(delta: Double) {
|
||||||
if (!spawned) {
|
if (!isSpawned) {
|
||||||
throw IllegalStateException("Tried to think before spawning in world")
|
throw IllegalStateException("Tried to think before spawning in world")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import ru.dbotthepony.kstarbound.world.World
|
|||||||
/**
|
/**
|
||||||
* Физический аватар игрока в мире
|
* Физический аватар игрока в мире
|
||||||
*/
|
*/
|
||||||
open class PlayerEntity(world: World<*>) : AliveEntity(world) {
|
open class PlayerEntity(world: World<*, *>) : AliveEntity(world) {
|
||||||
override val aabb = AABB.rectangle(Vector2d.ZERO, 1.8, 3.7)
|
override val aabb = AABB.rectangle(Vector2d.ZERO, 1.8, 3.7)
|
||||||
override val aabbDucked: AABB = AABB.rectangle(Vector2d.ZERO, 1.8, 1.8) + Vector2d(y = -0.9)
|
override val aabbDucked: AABB = AABB.rectangle(Vector2d.ZERO, 1.8, 1.8) + Vector2d(y = -0.9)
|
||||||
override var moveDirection = Move.STAND_STILL
|
override var moveDirection = Move.STAND_STILL
|
||||||
|
Loading…
Reference in New Issue
Block a user