Большие изменения в рендере
This commit is contained in:
parent
5b62fe3f09
commit
ff6dba143e
@ -10,35 +10,17 @@ import ru.dbotthepony.kstarbound.client.render.Camera
|
|||||||
import ru.dbotthepony.kstarbound.client.render.ChunkRenderer
|
import ru.dbotthepony.kstarbound.client.render.ChunkRenderer
|
||||||
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.defs.TileDefinition
|
||||||
import ru.dbotthepony.kstarbound.util.Color
|
import ru.dbotthepony.kstarbound.util.Color
|
||||||
import ru.dbotthepony.kstarbound.util.formatBytesShort
|
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.ChunkPos
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
private val LOGGER = LogManager.getLogger()
|
private val LOGGER = LogManager.getLogger()
|
||||||
|
|
||||||
var viewportWidth = 800
|
|
||||||
private set
|
|
||||||
|
|
||||||
var viewportHeight = 600
|
|
||||||
private set
|
|
||||||
|
|
||||||
var viewportMatrixGUI = updateViewportMatrixA()
|
|
||||||
private set
|
|
||||||
|
|
||||||
var viewportMatrixGame = updateViewportMatrixB()
|
|
||||||
private set
|
|
||||||
|
|
||||||
private fun updateViewportMatrixA(): Matrix4f {
|
|
||||||
return Matrix4f.ortho(0f, viewportWidth.toFloat(), 0f, viewportHeight.toFloat(), 0.1f, 100f)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateViewportMatrixB(): Matrix4f {
|
|
||||||
return Matrix4f.orthoDirect(0f, viewportWidth.toFloat(), 0f, viewportHeight.toFloat(), 1f, 100f)
|
|
||||||
}
|
|
||||||
|
|
||||||
var window = 0L
|
|
||||||
private set
|
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
LOGGER.info("Running LWJGL ${Version.getVersion()}")
|
LOGGER.info("Running LWJGL ${Version.getVersion()}")
|
||||||
|
|
||||||
@ -54,128 +36,61 @@ fun main() {
|
|||||||
Starbound.terminateLoading = true
|
Starbound.terminateLoading = true
|
||||||
}
|
}
|
||||||
|
|
||||||
while (client.renderFrame()) {
|
var chunkA: Chunk? = null
|
||||||
Starbound.pollCallbacks()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var camera: Camera? = null
|
Starbound.onInitialize {
|
||||||
private val startupTextList = ArrayList<String>()
|
chunkA = client.world!!.computeIfAbsent(ChunkPos(0, 0)).chunk
|
||||||
private var finishStartupRendering = Long.MAX_VALUE
|
val chunkB = client.world!!.computeIfAbsent(ChunkPos(-1, 0)).chunk
|
||||||
|
|
||||||
var frameRenderTime = 1.0
|
|
||||||
private set
|
|
||||||
|
|
||||||
val framesPerSecond get() = 1.0 / frameRenderTime
|
|
||||||
|
|
||||||
private fun loop() {
|
|
||||||
val client = StarboundClient()
|
|
||||||
val state = client.gl
|
|
||||||
startupTextList.add("Initialized OpenGL context")
|
|
||||||
camera = Camera()
|
|
||||||
|
|
||||||
// Set the clear color
|
|
||||||
glClearColor(0.2f, 0.2f, 0.2f, 0.2f)
|
|
||||||
|
|
||||||
state.blend = true
|
|
||||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
|
||||||
|
|
||||||
var chunkRenderer: ChunkRenderer? = null
|
|
||||||
|
|
||||||
/*Starbound.onInitialize {
|
|
||||||
val chunk = Starbound.world.getOrMakeChunk(Vector2i(2, 2))
|
|
||||||
|
|
||||||
var x = 0
|
var x = 0
|
||||||
var y = 0
|
var y = 0
|
||||||
|
|
||||||
for (tile in Starbound.tilesAccess.values) {
|
for (tile in Starbound.tilesAccess.values) {
|
||||||
chunk.background[x, y + 1] = tile
|
chunkA!!.background[x, y + 1] = tile
|
||||||
chunk.background[x++, y] = tile
|
chunkA!!.background[x++, y] = tile
|
||||||
chunk.background[x, y + 1] = tile
|
|
||||||
chunk.background[x++, y] = tile
|
|
||||||
chunk.background[x, y + 1] = tile
|
|
||||||
chunk.background[x++, y] = tile
|
|
||||||
chunk.background[x, y + 1] = tile
|
|
||||||
chunk.background[x++, y] = tile
|
|
||||||
chunk.background[x, y + 1] = tile
|
|
||||||
chunk.background[x++, y] = tile
|
|
||||||
chunk.background[x, y + 1] = tile
|
|
||||||
|
|
||||||
if (x >= 32) {
|
if (x >= 31) {
|
||||||
x = 0
|
x = 0
|
||||||
y += 2
|
y += 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val tile = Starbound.getTileDefinition("glass")
|
x = 0
|
||||||
|
y = 0
|
||||||
|
|
||||||
for (x in 0 .. 32) {
|
for (tile in Starbound.tilesAccess.values) {
|
||||||
for (y in 0 .. 32) {
|
chunkB.foreground[x, y + 1] = tile
|
||||||
chunk.foreground[x, y] = tile
|
chunkB.foreground[x++, y] = tile
|
||||||
|
|
||||||
|
if (x > 31) {
|
||||||
|
x = 0
|
||||||
|
y += 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val tile = Starbound.getTileDefinition("alienrock")
|
||||||
|
|
||||||
|
for (x in 0 .. 31) {
|
||||||
|
for (y in 0 .. 31) {
|
||||||
|
chunkA!!.foreground[x, y] = tile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (x in 4 .. 8) {
|
for (x in 4 .. 8) {
|
||||||
for (y in 4 .. 8) {
|
for (y in 4 .. 8) {
|
||||||
chunk.foreground[x, y] = null as TileDefinition?
|
chunkA!!.foreground[x, y] = null as TileDefinition?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
chunkRenderer = ChunkRenderer(state, chunk, Starbound.world)
|
val rand = Random()
|
||||||
chunkRenderer!!.tesselateStatic()
|
|
||||||
chunkRenderer!!.uploadStatic()
|
|
||||||
}*/
|
|
||||||
|
|
||||||
val runtime = Runtime.getRuntime()
|
while (client.renderFrame()) {
|
||||||
|
|
||||||
// Run the rendering loop until the user has attempted to close
|
|
||||||
// the window or has pressed the ESCAPE key.
|
|
||||||
while (!glfwWindowShouldClose(window)) {
|
|
||||||
val measure = glfwGetTime()
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT) // clear the framebuffer
|
|
||||||
|
|
||||||
state.matrixStack.clear(viewportMatrixGame.toMutableMatrix())
|
|
||||||
camera?.translate(state.matrixStack.last)
|
|
||||||
|
|
||||||
state.matrixStack.push().scale(x = 20f, y = 20f).translateWithScale(0f, 0f)
|
|
||||||
chunkRenderer?.render()
|
|
||||||
|
|
||||||
state.matrixStack.clear(viewportMatrixGUI.toMutableMatrix().translate(z = 2f))
|
|
||||||
|
|
||||||
state.font.render("FPS: %.2f".format(framesPerSecond), scale = 0.4f)
|
|
||||||
state.font.render("Mem: ${formatBytesShort(runtime.totalMemory() - runtime.freeMemory())}", x = viewportWidth.toFloat(), scale = 0.4f, alignX = TextAlignX.RIGHT)
|
|
||||||
|
|
||||||
val thisTime = System.currentTimeMillis()
|
|
||||||
|
|
||||||
if (startupTextList.isNotEmpty() && thisTime <= finishStartupRendering) {
|
|
||||||
var alpha = 1f
|
|
||||||
|
|
||||||
if (finishStartupRendering - thisTime < 1000L) {
|
|
||||||
alpha = (finishStartupRendering - thisTime) / 1000f
|
|
||||||
}
|
|
||||||
|
|
||||||
state.matrixStack.push()
|
|
||||||
state.matrixStack.translateWithScale(y = viewportHeight.toFloat())
|
|
||||||
var shade = 255
|
|
||||||
|
|
||||||
for (i in startupTextList.size - 1 downTo 0) {
|
|
||||||
val size = state.font.render(startupTextList[i], alignY = TextAlignY.BOTTOM, scale = 0.4f, color = Color.SHADES_OF_GRAY[shade].copy(alpha = alpha))
|
|
||||||
state.matrixStack.translateWithScale(y = -size.height * 1.2f)
|
|
||||||
|
|
||||||
if (shade > 120) {
|
|
||||||
shade -= 10
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
state.matrixStack.pop()
|
|
||||||
}
|
|
||||||
|
|
||||||
glfwSwapBuffers(window) // swap the color buffers
|
|
||||||
|
|
||||||
// Poll for window events. The key callback above will only be
|
|
||||||
// invoked during this call.
|
|
||||||
glfwPollEvents()
|
|
||||||
Starbound.pollCallbacks()
|
Starbound.pollCallbacks()
|
||||||
frameRenderTime = glfwGetTime() - measure
|
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,12 @@ 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_UNITf = 0.5f
|
||||||
|
|
||||||
|
const val PIXELS_IN_STARBOUND_UNIT = 8.0
|
||||||
|
const val PIXELS_IN_STARBOUND_UNITf = 8.0f
|
||||||
|
|
||||||
class TileDefLoadingException(message: String, cause: Throwable? = null) : IllegalStateException(message, cause)
|
class TileDefLoadingException(message: String, cause: Throwable? = null) : IllegalStateException(message, cause)
|
||||||
|
|
||||||
object Starbound {
|
object Starbound {
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.client
|
||||||
|
|
||||||
|
data class ClientSettings(
|
||||||
|
/**
|
||||||
|
* Масштаб игрового мира
|
||||||
|
*
|
||||||
|
* Масштаб в единицу означает что один Starbound Unit будет равен 8 пикселям на экране
|
||||||
|
*/
|
||||||
|
var scale: Float = 2f
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.client
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
|
||||||
|
import ru.dbotthepony.kstarbound.api.IStruct2f
|
||||||
|
import ru.dbotthepony.kstarbound.client.render.ChunkRenderer
|
||||||
|
import ru.dbotthepony.kstarbound.world.*
|
||||||
|
|
||||||
|
class ClientWorldChunkTuple(
|
||||||
|
world: World<*>,
|
||||||
|
chunk: Chunk,
|
||||||
|
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,
|
||||||
|
chunk = chunk,
|
||||||
|
top = top,
|
||||||
|
left = left,
|
||||||
|
right = right,
|
||||||
|
bottom = bottom,
|
||||||
|
|
||||||
|
renderer = ChunkRenderer(client.gl, chunk)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Отрисовывает этот мир с точки зрения [pos] в Starbound Units
|
||||||
|
*
|
||||||
|
* Все координаты "местности" сохраняются, поэтому, если отрисовывать слишком далеко от 0, 0
|
||||||
|
* то геометрия может начать искажаться из-за погрешности плавающей запятой
|
||||||
|
*
|
||||||
|
* Обрезает всю заведомо невидимую геометрию на основе аргументов mins и maxs (в пикселях)
|
||||||
|
*/
|
||||||
|
fun render(
|
||||||
|
pos: IStruct2f,
|
||||||
|
scale: Float = 1f,
|
||||||
|
|
||||||
|
mins: IStruct2f,
|
||||||
|
maxs: IStruct2f,
|
||||||
|
) {
|
||||||
|
val determineRenderers = ArrayList<ChunkRenderer>()
|
||||||
|
|
||||||
|
for (chunk in chunkMap.values) {
|
||||||
|
determineRenderers.add(chunk.renderer)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (renderer in determineRenderers) {
|
||||||
|
val (x, y) = renderer.chunk.pos
|
||||||
|
|
||||||
|
client.gl.matrixStack.push().translateWithScale(x = x * CHUNK_SIZE * PIXELS_IN_STARBOUND_UNITf, y = y * CHUNK_SIZE * PIXELS_IN_STARBOUND_UNITf)
|
||||||
|
renderer.bakeAndRender()
|
||||||
|
client.gl.matrixStack.pop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,13 +12,16 @@ 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.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
|
||||||
import ru.dbotthepony.kstarbound.world.World
|
import kotlin.math.cos
|
||||||
|
import kotlin.math.sin
|
||||||
|
|
||||||
class StarboundClient : AutoCloseable {
|
class StarboundClient : AutoCloseable {
|
||||||
val window: Long
|
val window: Long
|
||||||
val camera = Camera()
|
val camera = Camera()
|
||||||
|
var world: ClientWorld? = ClientWorld(this, 0L)
|
||||||
|
|
||||||
var gameTerminated = false
|
var gameTerminated = false
|
||||||
private set
|
private set
|
||||||
@ -140,7 +143,20 @@ class StarboundClient : AutoCloseable {
|
|||||||
|
|
||||||
val framesPerSecond get() = 1.0 / frameRenderTime
|
val framesPerSecond get() = 1.0 / frameRenderTime
|
||||||
|
|
||||||
var world: World? = World()
|
private val frameRenderTimes = DoubleArray(60) { 1.0 }
|
||||||
|
private var frameRenderIndex = 0
|
||||||
|
|
||||||
|
val averageFramesPerSecond: Double get() {
|
||||||
|
var sum = 0.0
|
||||||
|
|
||||||
|
for (value in frameRenderTimes) {
|
||||||
|
sum += value
|
||||||
|
}
|
||||||
|
|
||||||
|
return frameRenderTimes.size / sum
|
||||||
|
}
|
||||||
|
|
||||||
|
val settings = ClientSettings()
|
||||||
|
|
||||||
fun renderFrame(): Boolean {
|
fun renderFrame(): Boolean {
|
||||||
ensureSameThread()
|
ensureSameThread()
|
||||||
@ -153,9 +169,18 @@ class StarboundClient : AutoCloseable {
|
|||||||
val measure = GLFW.glfwGetTime()
|
val measure = GLFW.glfwGetTime()
|
||||||
|
|
||||||
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())
|
||||||
camera.translate(gl.matrixStack.last)
|
|
||||||
|
val mins = Vector2f((-viewportWidth / 2f) / settings.scale, (-viewportHeight / 2f) / settings.scale)
|
||||||
|
val maxs = -mins
|
||||||
|
|
||||||
|
gl.matrixStack.push()
|
||||||
|
.translateWithScale(viewportWidth / 2f - camera.pos.x, viewportHeight / 2f - camera.pos.y) // центр экрана + координаты отрисовки мира
|
||||||
|
.scale(x = settings.scale, y = settings.scale) // масштабируем до нужного размера
|
||||||
|
|
||||||
|
world?.render(Vector2f.ZERO, mins = mins, maxs = maxs)
|
||||||
|
|
||||||
|
gl.matrixStack.pop()
|
||||||
|
|
||||||
gl.matrixStack.clear(viewportMatrixGUI.toMutableMatrix().translate(z = 2f))
|
gl.matrixStack.clear(viewportMatrixGUI.toMutableMatrix().translate(z = 2f))
|
||||||
|
|
||||||
@ -186,13 +211,18 @@ class StarboundClient : AutoCloseable {
|
|||||||
|
|
||||||
val runtime = Runtime.getRuntime()
|
val runtime = Runtime.getRuntime()
|
||||||
|
|
||||||
gl.font.render("FPS: %.2f".format(framesPerSecond), scale = 0.4f)
|
gl.font.render("FPS: %.2f".format(averageFramesPerSecond), scale = 0.4f)
|
||||||
gl.font.render("Mem: ${formatBytesShort(runtime.totalMemory() - runtime.freeMemory())}", x = viewportWidth.toFloat(), scale = 0.4f, alignX = TextAlignX.RIGHT)
|
gl.font.render("Mem: ${formatBytesShort(runtime.totalMemory() - runtime.freeMemory())}", x = viewportWidth.toFloat(), scale = 0.4f, alignX = TextAlignX.RIGHT)
|
||||||
|
|
||||||
GLFW.glfwSwapBuffers(window)
|
GLFW.glfwSwapBuffers(window)
|
||||||
GLFW.glfwPollEvents()
|
GLFW.glfwPollEvents()
|
||||||
|
|
||||||
|
camera.tick(GLFW.glfwGetTime() - measure)
|
||||||
|
|
||||||
|
gl.cleanup()
|
||||||
|
|
||||||
frameRenderTime = GLFW.glfwGetTime() - measure
|
frameRenderTime = GLFW.glfwGetTime() - measure
|
||||||
|
frameRenderTimes[++frameRenderIndex % frameRenderTimes.size] = frameRenderTime
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package ru.dbotthepony.kstarbound.client.gl
|
package ru.dbotthepony.kstarbound.client.gl
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager
|
||||||
import org.lwjgl.opengl.GL
|
import org.lwjgl.opengl.GL
|
||||||
import org.lwjgl.opengl.GL46.*
|
import org.lwjgl.opengl.GL46.*
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
@ -12,6 +13,8 @@ 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
|
||||||
|
import java.lang.ref.Cleaner
|
||||||
|
import java.util.concurrent.ThreadFactory
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
private class GLStateSwitchTracker(private val enum: Int, private var value: Boolean = false) {
|
private class GLStateSwitchTracker(private val enum: Int, private var value: Boolean = false) {
|
||||||
@ -73,6 +76,13 @@ open class GLTransformableColorableProgram(state: GLStateTracker, vararg shaders
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface GLCleanable : Cleaner.Cleanable {
|
||||||
|
/**
|
||||||
|
* Выставляет флаг на то, что объект был удалён вручную и вызывает clean()
|
||||||
|
*/
|
||||||
|
fun cleanManual(): Unit
|
||||||
|
}
|
||||||
|
|
||||||
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
|
||||||
@ -83,6 +93,47 @@ class GLStateTracker {
|
|||||||
GL.createCapabilities()
|
GL.createCapabilities()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var cleanerHits = ArrayList<() -> Unit>()
|
||||||
|
private val cleaner = Cleaner.create(object : ThreadFactory {
|
||||||
|
override fun newThread(r: Runnable): Thread {
|
||||||
|
val thread = Thread(r, "OpenGL Object Cleaner@" + System.identityHashCode(this))
|
||||||
|
thread.priority = 2
|
||||||
|
return thread
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
fun registerCleanable(ref: Any, fn: (Int) -> Unit, name: String, nativeRef: Int): GLCleanable {
|
||||||
|
var cleanManual = false
|
||||||
|
|
||||||
|
val cleanable = cleaner.register(ref) {
|
||||||
|
cleanerHits.add {
|
||||||
|
fn(nativeRef)
|
||||||
|
checkForGLError()
|
||||||
|
|
||||||
|
if (!cleanManual)
|
||||||
|
LOGGER.error("{} with ID {} got leaked.", name, nativeRef)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return object : GLCleanable {
|
||||||
|
override fun cleanManual() {
|
||||||
|
cleanManual = true
|
||||||
|
clean()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clean() = cleanable.clean()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cleanup() {
|
||||||
|
val copy = cleanerHits
|
||||||
|
cleanerHits = ArrayList()
|
||||||
|
|
||||||
|
for (lambda in copy) {
|
||||||
|
lambda.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var blend by GLStateSwitchTracker(GL_BLEND)
|
var blend by GLStateSwitchTracker(GL_BLEND)
|
||||||
var depthTest by GLStateSwitchTracker(GL_DEPTH_TEST)
|
var depthTest by GLStateSwitchTracker(GL_DEPTH_TEST)
|
||||||
|
|
||||||
@ -282,4 +333,8 @@ class GLStateTracker {
|
|||||||
val freeType = FreeType()
|
val freeType = FreeType()
|
||||||
|
|
||||||
val font = Font(this)
|
val font = Font(this)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LOGGER = LogManager.getLogger(GLStateTracker::class.java)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,12 @@ class GLTexturePropertyTracker(private val flag: Int, var value: Int) {
|
|||||||
class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") : AutoCloseable {
|
class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") : AutoCloseable {
|
||||||
val pointer = glGenTextures()
|
val pointer = glGenTextures()
|
||||||
|
|
||||||
|
init {
|
||||||
|
checkForGLError()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val cleanable = state.registerCleanable(this, ::glDeleteTextures, "2D Texture", pointer)
|
||||||
|
|
||||||
var width = 0
|
var width = 0
|
||||||
private set
|
private set
|
||||||
|
|
||||||
@ -154,8 +160,7 @@ class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") : A
|
|||||||
state.texture2D = null
|
state.texture2D = null
|
||||||
}
|
}
|
||||||
|
|
||||||
glDeleteTextures(pointer)
|
cleanable.cleanManual()
|
||||||
checkForGLError()
|
|
||||||
isValid = false
|
isValid = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,12 @@ import java.io.Closeable
|
|||||||
class GLVertexArrayObject(val state: GLStateTracker) : Closeable {
|
class GLVertexArrayObject(val state: GLStateTracker) : Closeable {
|
||||||
val pointer = glGenVertexArrays()
|
val pointer = glGenVertexArrays()
|
||||||
|
|
||||||
|
init {
|
||||||
|
checkForGLError()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val cleanable = state.registerCleanable(this, ::glDeleteVertexArrays, "Vertex Array Object", pointer)
|
||||||
|
|
||||||
fun bind(): GLVertexArrayObject {
|
fun bind(): GLVertexArrayObject {
|
||||||
check(isValid) { "Tried to use NULL GLVertexArrayObject" }
|
check(isValid) { "Tried to use NULL GLVertexArrayObject" }
|
||||||
return state.bind(this)
|
return state.bind(this)
|
||||||
@ -38,13 +44,15 @@ class GLVertexArrayObject(val state: GLStateTracker) : Closeable {
|
|||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
state.ensureSameThread()
|
state.ensureSameThread()
|
||||||
if (isValid) return
|
|
||||||
|
if (!isValid) return
|
||||||
|
|
||||||
if (state.VAO == this) {
|
if (state.VAO == this) {
|
||||||
state.VAO = null
|
state.VAO = null
|
||||||
}
|
}
|
||||||
|
|
||||||
glDeleteVertexArrays(pointer)
|
cleanable.cleanManual()
|
||||||
|
|
||||||
isValid = false
|
isValid = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,12 @@ enum class VBOType(val value: Int) {
|
|||||||
class GLVertexBufferObject(val state: GLStateTracker, val type: VBOType = VBOType.ARRAY) : Closeable {
|
class GLVertexBufferObject(val state: GLStateTracker, val type: VBOType = VBOType.ARRAY) : Closeable {
|
||||||
val pointer = glGenBuffers()
|
val pointer = glGenBuffers()
|
||||||
|
|
||||||
|
init {
|
||||||
|
checkForGLError()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val cleanable = state.registerCleanable(this, ::glDeleteBuffers, "Vertex Buffer Object", pointer)
|
||||||
|
|
||||||
val isArray get() = type == VBOType.ARRAY
|
val isArray get() = type == VBOType.ARRAY
|
||||||
val isElementArray get() = type == VBOType.ELEMENT_ARRAY
|
val isElementArray get() = type == VBOType.ELEMENT_ARRAY
|
||||||
|
|
||||||
@ -72,13 +78,15 @@ class GLVertexBufferObject(val state: GLStateTracker, val type: VBOType = VBOTyp
|
|||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
state.ensureSameThread()
|
state.ensureSameThread()
|
||||||
|
|
||||||
if (!isValid) return
|
if (!isValid) return
|
||||||
|
|
||||||
if (state.VBO == this) {
|
if (state.VBO == this) {
|
||||||
state.VBO = null
|
state.VBO = null
|
||||||
}
|
}
|
||||||
|
|
||||||
glDeleteBuffers(pointer)
|
cleanable.cleanManual()
|
||||||
|
|
||||||
isValid = false
|
isValid = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
package ru.dbotthepony.kstarbound.client.render
|
package ru.dbotthepony.kstarbound.client.render
|
||||||
|
|
||||||
import org.lwjgl.opengl.GL11
|
|
||||||
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.VertexBuilder
|
||||||
import ru.dbotthepony.kstarbound.client.gl.checkForGLError
|
import ru.dbotthepony.kstarbound.client.gl.checkForGLError
|
||||||
import ru.dbotthepony.kstarbound.gl.*
|
|
||||||
import ru.dbotthepony.kstarbound.math.FloatMatrix
|
import ru.dbotthepony.kstarbound.math.FloatMatrix
|
||||||
import ru.dbotthepony.kstarbound.math.Matrix4f
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Служит для быстрой настройки состояния для будущей отрисовки
|
* Служит для быстрой настройки состояния для будущей отрисовки
|
||||||
@ -47,6 +44,7 @@ class BakedStaticMesh(
|
|||||||
val vao: GLVertexArrayObject,
|
val vao: GLVertexArrayObject,
|
||||||
) : AutoCloseable {
|
) : AutoCloseable {
|
||||||
private var onClose = {}
|
private var onClose = {}
|
||||||
|
|
||||||
constructor(programState: BakedProgramState, builder: VertexBuilder) : this(
|
constructor(programState: BakedProgramState, builder: VertexBuilder) : this(
|
||||||
programState,
|
programState,
|
||||||
builder.indexCount,
|
builder.indexCount,
|
||||||
|
@ -1,21 +1,55 @@
|
|||||||
package ru.dbotthepony.kstarbound.client.render
|
package ru.dbotthepony.kstarbound.client.render
|
||||||
|
|
||||||
|
import org.lwjgl.glfw.GLFW.*
|
||||||
import ru.dbotthepony.kstarbound.math.*
|
import ru.dbotthepony.kstarbound.math.*
|
||||||
|
|
||||||
class Camera {
|
class Camera {
|
||||||
|
/**
|
||||||
|
* Позиция этой камеры в Starbound Unit'ах
|
||||||
|
*/
|
||||||
val pos = MutableVector3f()
|
val pos = MutableVector3f()
|
||||||
var zoom = 1f
|
|
||||||
|
|
||||||
fun translate(stack: FloatMatrix<*>) {
|
var pressedLeft = false
|
||||||
stack.translateWithScale(pos)
|
private set
|
||||||
stack.scale(x = zoom, y = zoom)
|
|
||||||
}
|
var pressedRight = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
var pressedUp = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
var pressedDown = false
|
||||||
|
private set
|
||||||
|
|
||||||
fun userInput(key: Int, scancode: Int, action: Int, mods: Int) {
|
fun userInput(key: Int, scancode: Int, action: Int, mods: Int) {
|
||||||
|
when (key) {
|
||||||
|
GLFW_KEY_LEFT -> pressedLeft = action > 0
|
||||||
|
GLFW_KEY_RIGHT -> pressedRight = action > 0
|
||||||
|
GLFW_KEY_UP -> pressedUp = action > 0
|
||||||
|
GLFW_KEY_DOWN -> pressedDown = action > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun tick(delta: Double) {
|
||||||
|
if (pressedLeft) {
|
||||||
|
pos.x -= (delta * FREEVIEW_SENS).toFloat()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pressedRight) {
|
||||||
|
pos.x += (delta * FREEVIEW_SENS).toFloat()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pressedUp) {
|
||||||
|
pos.y += (delta * FREEVIEW_SENS).toFloat()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pressedDown) {
|
||||||
|
pos.y -= (delta * FREEVIEW_SENS).toFloat()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
const val FREEVIEW_SENS = 800.0
|
||||||
const val MAX_ZOOM = 4f
|
const val MAX_ZOOM = 4f
|
||||||
const val MIN_ZOOM = 0.1f
|
const val MIN_ZOOM = 0.1f
|
||||||
const val ZOOM_STEP = 0.1f
|
const val ZOOM_STEP = 0.1f
|
||||||
|
@ -1,19 +1,88 @@
|
|||||||
package ru.dbotthepony.kstarbound.client.render
|
package ru.dbotthepony.kstarbound.client.render
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.client.ClientWorld
|
||||||
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
|
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
|
||||||
import ru.dbotthepony.kstarbound.gl.*
|
|
||||||
import ru.dbotthepony.kstarbound.math.FloatMatrix
|
import ru.dbotthepony.kstarbound.math.FloatMatrix
|
||||||
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 ru.dbotthepony.kstarbound.world.World
|
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
class ChunkRenderer(val state: GLStateTracker, val chunk: Chunk, val world: World? = null) : AutoCloseable {
|
class ChunkRenderer(val state: GLStateTracker, val chunk: Chunk, val world: ClientWorld? = null) : AutoCloseable {
|
||||||
private val foregroundLayers = TileLayerList()
|
private inner class TileLayerRenderer(private val layerChangeset: () -> Int, private val isBackground: Boolean) : AutoCloseable {
|
||||||
private val backgroundLayers = TileLayerList()
|
private val layers = TileLayerList()
|
||||||
private val bakedMeshes = ArrayList<BakedStaticMesh>()
|
private val bakedMeshes = ArrayList<BakedStaticMesh>()
|
||||||
|
private var changeset = -1
|
||||||
|
|
||||||
|
fun tesselateStatic(view: ITileChunk) {
|
||||||
|
if (state.isSameThread()) {
|
||||||
|
for (mesh in bakedMeshes) {
|
||||||
|
mesh.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
bakedMeshes.clear()
|
||||||
|
} else {
|
||||||
|
unloadableBakedMeshes.addAll(bakedMeshes)
|
||||||
|
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) in layers.buildList()) {
|
||||||
|
bakedMeshes.add(BakedStaticMesh(baked, builder))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clear) {
|
||||||
|
layers.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun render(transform: FloatMatrix<*>) {
|
||||||
|
for (mesh in bakedMeshes) {
|
||||||
|
mesh.render(transform)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bakeAndRender(transform: FloatMatrix<*>, provider: () -> ITileChunk) {
|
||||||
|
if (changeset != layerChangeset.invoke()) {
|
||||||
|
this.tesselateStatic(provider.invoke())
|
||||||
|
this.uploadStatic()
|
||||||
|
|
||||||
|
changeset = layerChangeset.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
|
render(transform)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
for (mesh in bakedMeshes) {
|
||||||
|
mesh.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val bakedMeshesForeground = ArrayList<BakedStaticMesh>()
|
||||||
private val unloadableBakedMeshes = ArrayList<BakedStaticMesh>()
|
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 {
|
private fun getForeground(): ITileChunk {
|
||||||
return world?.getForegroundView(chunk.pos) ?: chunk.foreground
|
return world?.getForegroundView(chunk.pos) ?: chunk.foreground
|
||||||
}
|
}
|
||||||
@ -29,39 +98,8 @@ class ChunkRenderer(val state: GLStateTracker, val chunk: Chunk, val world: Worl
|
|||||||
* но только если до этого был вызыван loadRenderers() и геометрия чанка не поменялась
|
* но только если до этого был вызыван loadRenderers() и геометрия чанка не поменялась
|
||||||
*/
|
*/
|
||||||
fun tesselateStatic() {
|
fun tesselateStatic() {
|
||||||
if (state.isSameThread()) {
|
foreground.tesselateStatic(getForeground())
|
||||||
for (mesh in bakedMeshes) {
|
background.tesselateStatic(getBackground())
|
||||||
mesh.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
bakedMeshes.clear()
|
|
||||||
} else {
|
|
||||||
unloadableBakedMeshes.addAll(bakedMeshes)
|
|
||||||
bakedMeshes.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
foregroundLayers.clear()
|
|
||||||
|
|
||||||
val foreground = getForeground()
|
|
||||||
|
|
||||||
// TODO: Синхронизация (ибо обновления игровой логики будут в потоке вне рендер потока)
|
|
||||||
for ((pos, tile) in foreground.posToTile) {
|
|
||||||
if (tile != null) {
|
|
||||||
val renderer = state.tileRenderers.get(tile.def.materialName)
|
|
||||||
renderer.tesselate(foreground, foregroundLayers, pos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
backgroundLayers.clear()
|
|
||||||
|
|
||||||
val background = getBackground()
|
|
||||||
|
|
||||||
for ((pos, tile) in background.posToTile) {
|
|
||||||
if (tile != null) {
|
|
||||||
val renderer = state.tileRenderers.get(tile.def.materialName)
|
|
||||||
renderer.tesselate(background, backgroundLayers, pos, background = true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -72,18 +110,8 @@ class ChunkRenderer(val state: GLStateTracker, val chunk: Chunk, val world: Worl
|
|||||||
fun loadRenderers() {
|
fun loadRenderers() {
|
||||||
unloadUnused()
|
unloadUnused()
|
||||||
|
|
||||||
// TODO: Синхронизация (ибо обновления игровой логики будут в потоке вне рендер потока)
|
foreground.loadRenderers(getForeground())
|
||||||
for ((_, tile) in getForeground().posToTile) {
|
background.loadRenderers(getBackground())
|
||||||
if (tile != null) {
|
|
||||||
state.tileRenderers.get(tile.def.materialName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for ((_, tile) in getBackground().posToTile) {
|
|
||||||
if (tile != null) {
|
|
||||||
state.tileRenderers.get(tile.def.materialName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun unloadUnused() {
|
private fun unloadUnused() {
|
||||||
@ -99,35 +127,26 @@ class ChunkRenderer(val state: GLStateTracker, val chunk: Chunk, val world: Worl
|
|||||||
fun uploadStatic(clear: Boolean = true) {
|
fun uploadStatic(clear: Boolean = true) {
|
||||||
unloadUnused()
|
unloadUnused()
|
||||||
|
|
||||||
for ((baked, builder) in backgroundLayers.buildList()) {
|
foreground.uploadStatic(clear)
|
||||||
bakedMeshes.add(BakedStaticMesh(baked, builder))
|
background.uploadStatic(clear)
|
||||||
}
|
|
||||||
|
|
||||||
for ((baked, builder) in foregroundLayers.buildList()) {
|
|
||||||
bakedMeshes.add(BakedStaticMesh(baked, builder))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clear) {
|
|
||||||
backgroundLayers.clear()
|
|
||||||
foregroundLayers.clear()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun render(transform: FloatMatrix<*> = state.matrixStack.last) {
|
fun render(transform: FloatMatrix<*> = state.matrixStack.last) {
|
||||||
unloadUnused()
|
unloadUnused()
|
||||||
|
|
||||||
for (mesh in bakedMeshes) {
|
background.render(transform)
|
||||||
mesh.render(transform)
|
foreground.render(transform)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun bakeAndRender(transform: FloatMatrix<*> = state.matrixStack.last) {
|
||||||
|
unloadUnused()
|
||||||
|
|
||||||
|
background.bakeAndRender(transform, this::getBackground)
|
||||||
|
foreground.bakeAndRender(transform, this::getForeground)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
for (mesh in bakedMeshes) {
|
background.close()
|
||||||
mesh.close()
|
foreground.close()
|
||||||
}
|
|
||||||
|
|
||||||
for (mesh in unloadableBakedMeshes) {
|
|
||||||
mesh.close()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -253,9 +253,9 @@ class Font(
|
|||||||
val advanceX: Float
|
val advanceX: Float
|
||||||
val advanceY: Float
|
val advanceY: Float
|
||||||
|
|
||||||
private val vbo: GLVertexBufferObject?
|
private val vbo: GLVertexBufferObject? // все три указателя должны хранится во избежание утечки
|
||||||
private val ebo: GLVertexBufferObject?
|
private val ebo: GLVertexBufferObject? // все три указателя должны хранится во избежание утечки
|
||||||
private val vao: GLVertexArrayObject?
|
private val vao: GLVertexArrayObject? // все три указателя должны хранится во избежание утечки
|
||||||
|
|
||||||
private val indexCount: Int
|
private val indexCount: Int
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.client.render
|
|||||||
|
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import org.lwjgl.opengl.GL46.*
|
import org.lwjgl.opengl.GL46.*
|
||||||
|
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.client.gl.*
|
import ru.dbotthepony.kstarbound.client.gl.*
|
||||||
import ru.dbotthepony.kstarbound.defs.TileDefinition
|
import ru.dbotthepony.kstarbound.defs.TileDefinition
|
||||||
@ -169,34 +170,36 @@ class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) {
|
|||||||
if (offset != Vector2i.ZERO) {
|
if (offset != Vector2i.ZERO) {
|
||||||
a += offset.x / BASELINE_TEXTURE_SIZE
|
a += offset.x / BASELINE_TEXTURE_SIZE
|
||||||
|
|
||||||
// в json файлах y указан как положительный вверх
|
// в json файлах y указан как положительный вверх,
|
||||||
|
// что соответствует нашему миру
|
||||||
b += offset.y / BASELINE_TEXTURE_SIZE
|
b += offset.y / BASELINE_TEXTURE_SIZE
|
||||||
|
|
||||||
c += offset.x / BASELINE_TEXTURE_SIZE
|
c += offset.x / BASELINE_TEXTURE_SIZE
|
||||||
d += offset.y / BASELINE_TEXTURE_SIZE
|
d += offset.y / BASELINE_TEXTURE_SIZE
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
if (!notifiedDepth && tile.render.zLevel >= 5900) {
|
|
||||||
LOGGER.warn("Tile {} has out of bounds zLevel of {}", tile.materialName, tile.render.zLevel)
|
|
||||||
notifiedDepth = true
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (tile.render.variants == 0 || piece.texture != null || piece.variantStride == null) {
|
if (tile.render.variants == 0 || piece.texture != null || piece.variantStride == null) {
|
||||||
val (u0, v0) = texture.pixelToUV(piece.texturePosition)
|
val (u0, v0) = texture.pixelToUV(piece.texturePosition)
|
||||||
val (u1, v1) = texture.pixelToUV(piece.texturePosition + piece.textureSize)
|
val (u1, v1) = texture.pixelToUV(piece.texturePosition + piece.textureSize)
|
||||||
|
|
||||||
//builder.quadZ(a, b, c, d, tile.render.zLevel.toFloat() + 200f, VertexTransformers.uv(u0, v1, u1, v0))
|
builder.quadZ(
|
||||||
builder.quadZ(a, b, c, d, Z_LEVEL, VertexTransformers.uv(u0, v1, u1, v0))
|
a * PIXELS_IN_STARBOUND_UNITf,
|
||||||
|
b * PIXELS_IN_STARBOUND_UNITf,
|
||||||
|
c * PIXELS_IN_STARBOUND_UNITf,
|
||||||
|
d * PIXELS_IN_STARBOUND_UNITf,
|
||||||
|
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()
|
||||||
|
|
||||||
val (u0, v0) = texture.pixelToUV(piece.texturePosition + piece.variantStride * variant)
|
val (u0, v0) = texture.pixelToUV(piece.texturePosition + piece.variantStride * variant)
|
||||||
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(a, b, c, d, tile.render.zLevel.toFloat() + 200f, VertexTransformers.uv(u0, v1, u1, v0))
|
builder.quadZ(
|
||||||
builder.quadZ(a, b, c, d, Z_LEVEL, VertexTransformers.uv(u0, v1, u1, v0))
|
a * PIXELS_IN_STARBOUND_UNITf,
|
||||||
|
b * PIXELS_IN_STARBOUND_UNITf,
|
||||||
|
c * PIXELS_IN_STARBOUND_UNITf,
|
||||||
|
d * PIXELS_IN_STARBOUND_UNITf,
|
||||||
|
Z_LEVEL, VertexTransformers.uv(u0, v1, u1, v0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,7 +248,7 @@ class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) {
|
|||||||
*
|
*
|
||||||
* [layers] содержит текущие программы и их билдеры и их zPos
|
* [layers] содержит текущие программы и их билдеры и их zPos
|
||||||
*
|
*
|
||||||
* Тесселирует тайлы в границы -1f .. CHUNK_SIZEf + 1f на основе [pos]
|
* Тесселирует тайлы в нужный VertexBuilder с масштабом согласно константе [PIXELS_IN_STARBOUND_UNITf]
|
||||||
*/
|
*/
|
||||||
fun tesselate(getter: ITileChunk, layers: TileLayerList, pos: Vector2i, background: Boolean = false) {
|
fun tesselate(getter: ITileChunk, layers: TileLayerList, pos: Vector2i, background: Boolean = false) {
|
||||||
// если у нас нет renderTemplate
|
// если у нас нет renderTemplate
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
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.IStruct2i
|
import ru.dbotthepony.kstarbound.api.IStruct2i
|
||||||
import ru.dbotthepony.kstarbound.api.IStruct3f
|
import ru.dbotthepony.kstarbound.api.IStruct3f
|
||||||
import ru.dbotthepony.kstarbound.api.IStruct4f
|
import ru.dbotthepony.kstarbound.api.IStruct4f
|
||||||
@ -15,13 +16,16 @@ abstract class IVector2i<T : IVector2i<T>> : IMatrixLike, IMatrixLikeInt, IStruc
|
|||||||
abstract val y: Int
|
abstract val y: Int
|
||||||
|
|
||||||
operator fun plus(other: IVector2i<*>) = make(x + other.x, y + other.y)
|
operator fun plus(other: IVector2i<*>) = make(x + other.x, y + other.y)
|
||||||
operator fun plus(other: Int) = make(x + other, y + other)
|
|
||||||
operator fun minus(other: IVector2i<*>) = make(x - other.x, y - other.y)
|
operator fun minus(other: IVector2i<*>) = make(x - other.x, y - other.y)
|
||||||
operator fun minus(other: Int) = make(x - other, y - other)
|
|
||||||
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 times(other: Int) = make(x * other, y * other)
|
|
||||||
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 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 minus(other: Int) = make(x - other, y - other)
|
||||||
|
operator fun plus(other: Int) = make(x + other, y + other)
|
||||||
|
|
||||||
|
operator fun unaryMinus() = make(-x, -y)
|
||||||
|
|
||||||
fun left() = make(x - 1, y)
|
fun left() = make(x - 1, y)
|
||||||
fun right() = make(x + 1, y)
|
fun right() = make(x + 1, y)
|
||||||
@ -59,6 +63,89 @@ data class Vector2i(override val x: Int = 0, override val y: Int = 0) : IVector2
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class MutableVector2i(override var x: Int = 0, override var y: Int = 0) : IVector2i<MutableVector2i>() {
|
||||||
|
override fun make(x: Int, y: Int): MutableVector2i {
|
||||||
|
this.x = x
|
||||||
|
this.y = y
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromJson(input: JsonArray): MutableVector2i {
|
||||||
|
return MutableVector2i(input[0].asInt, input[1].asInt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class IVector2f<T : IVector2f<T>> : IMatrixLike, IMatrixLikeFloat, IStruct2f {
|
||||||
|
override val columns = 1
|
||||||
|
override val rows = 2
|
||||||
|
|
||||||
|
abstract val x: Float
|
||||||
|
abstract val y: Float
|
||||||
|
|
||||||
|
operator fun plus(other: IVector2f<*>) = make(x + other.x, y + other.y)
|
||||||
|
operator fun minus(other: IVector2f<*>) = make(x - other.x, y - other.y)
|
||||||
|
operator fun times(other: IVector2f<*>) = make(x * other.x, y * other.y)
|
||||||
|
operator fun div(other: IVector2f<*>) = make(x / other.x, y / other.y)
|
||||||
|
|
||||||
|
operator fun plus(other: Float) = make(x + other, y + other)
|
||||||
|
operator fun minus(other: Float) = make(x - other, y - other)
|
||||||
|
operator fun times(other: Float) = make(x * other, y * other)
|
||||||
|
operator fun div(other: Float) = make(x / other, y / other)
|
||||||
|
|
||||||
|
operator fun unaryMinus() = make(-x, -y)
|
||||||
|
|
||||||
|
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): Float {
|
||||||
|
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: Float, y: Float): T
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Vector2f(override val x: Float = 0f, override val y: Float = 0f) : IVector2f<Vector2f>() {
|
||||||
|
override fun make(x: Float, y: Float) = Vector2f(x, y)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromJson(input: JsonArray): Vector2f {
|
||||||
|
return Vector2f(input[0].asFloat, input[1].asFloat)
|
||||||
|
}
|
||||||
|
|
||||||
|
val ZERO = Vector2f()
|
||||||
|
val LEFT = Vector2f().left()
|
||||||
|
val RIGHT = Vector2f().right()
|
||||||
|
val UP = Vector2f().up()
|
||||||
|
val DOWN = Vector2f().down()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class MutableVector2f(override var x: Float = 0f, override var y: Float = 0f) : IVector2f<MutableVector2f>() {
|
||||||
|
override fun make(x: Float, y: Float): MutableVector2f {
|
||||||
|
this.x = x
|
||||||
|
this.y = y
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromJson(input: JsonArray): MutableVector2f {
|
||||||
|
return MutableVector2f(input[0].asFloat, input[1].asFloat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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
|
||||||
@ -77,6 +164,8 @@ abstract class IVector3f<T : IVector3f<T>> : IMatrixLike, IMatrixLikeFloat, IStr
|
|||||||
operator fun times(other: Float) = make(x * other, y * other, z * other)
|
operator fun times(other: Float) = make(x * other, y * other, z * other)
|
||||||
operator fun div(other: Float) = make(x / other, y / other, z / other)
|
operator fun div(other: Float) = make(x / other, y / other, z / other)
|
||||||
|
|
||||||
|
operator fun unaryMinus() = make(-x, -y, -z)
|
||||||
|
|
||||||
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)")
|
||||||
@ -190,6 +279,8 @@ abstract class IVector4f<T : IVector4f<T>> : IMatrixLike, IMatrixLikeFloat, IStr
|
|||||||
operator fun times(other: Float) = make(x * other, y * other, z * other, w * other)
|
operator fun times(other: Float) = make(x * other, y * other, z * other, w * other)
|
||||||
operator fun div(other: Float) = make(x / other, y / other, z / other, w / other)
|
operator fun div(other: Float) = make(x / other, y / other, z / other, w / other)
|
||||||
|
|
||||||
|
operator fun unaryMinus() = make(-x, -y, -z, -w)
|
||||||
|
|
||||||
override val columns = 1
|
override val columns = 1
|
||||||
override val rows = 4
|
override val rows = 4
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package ru.dbotthepony.kstarbound.world
|
package ru.dbotthepony.kstarbound.world
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.api.IStruct2i
|
||||||
import ru.dbotthepony.kstarbound.defs.TileDefinition
|
import ru.dbotthepony.kstarbound.defs.TileDefinition
|
||||||
import ru.dbotthepony.kstarbound.math.IVector2i
|
import ru.dbotthepony.kstarbound.math.IVector2i
|
||||||
import ru.dbotthepony.kstarbound.math.Vector2i
|
import ru.dbotthepony.kstarbound.math.Vector2i
|
||||||
@ -7,9 +8,18 @@ import ru.dbotthepony.kstarbound.math.Vector2i
|
|||||||
/**
|
/**
|
||||||
* Представляет из себя класс, который содержит состояние тайла на заданной позиции
|
* Представляет из себя класс, который содержит состояние тайла на заданной позиции
|
||||||
*/
|
*/
|
||||||
data class ChunkTile(val def: TileDefinition) {
|
data class ChunkTile(val chunk: Chunk.TileLayer, val def: TileDefinition) {
|
||||||
var color = -1
|
var color = 0
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
chunk.incChangeset()
|
||||||
|
}
|
||||||
|
|
||||||
var forceVariant = -1
|
var forceVariant = -1
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
chunk.incChangeset()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ITileMap {
|
interface ITileMap {
|
||||||
@ -134,16 +144,23 @@ interface ITileGetterSetter : ITileGetter, ITileSetter
|
|||||||
interface ITileChunk : ITileGetter, IChunkPositionable
|
interface ITileChunk : ITileGetter, IChunkPositionable
|
||||||
interface IMutableTileChunk : ITileChunk, ITileSetter
|
interface IMutableTileChunk : ITileChunk, ITileSetter
|
||||||
|
|
||||||
const val CHUNK_SHIFT = 6
|
const val CHUNK_SHIFT = 5
|
||||||
const val CHUNK_SIZE = 1 shl CHUNK_SHIFT // 64
|
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
|
||||||
|
|
||||||
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: Vector2i) : this(pos.x shr CHUNK_SHIFT, pos.y shr CHUNK_SHIFT)
|
constructor(pos: IStruct2i) : this(pos.component1(), pos.component2())
|
||||||
override fun make(x: Int, y: Int) = ChunkPos(x, y)
|
override fun make(x: Int, y: Int) = ChunkPos(x, y)
|
||||||
|
|
||||||
val firstBlock get() = Vector2i(x shl CHUNK_SHIFT, y shl CHUNK_SHIFT)
|
val firstBlock get() = Vector2i(x shl CHUNK_SHIFT, y shl CHUNK_SHIFT)
|
||||||
val lastBlock get() = Vector2i(((x + 1) shl CHUNK_SHIFT) - 1, ((y + 1) shl CHUNK_SHIFT) - 1)
|
val lastBlock get() = Vector2i(((x + 1) shl CHUNK_SHIFT) - 1, ((y + 1) shl CHUNK_SHIFT) - 1)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromTilePosition(input: IStruct2i): ChunkPos {
|
||||||
|
val (x, y) = input
|
||||||
|
return ChunkPos(x shr CHUNK_SHIFT, y shr CHUNK_SHIFT)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -256,8 +273,39 @@ class MutableTileChunkView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open class Chunk(val world: World, val pos: ChunkPos) {
|
/**
|
||||||
inner class ChunkTileLayer : IMutableTileChunk {
|
* Чанк мира
|
||||||
|
*
|
||||||
|
* Хранит в себе тайлы и ентити внутри себя
|
||||||
|
*
|
||||||
|
* Считается, что один тайл имеет форму квадрата и сторона квадрата примерно равна полуметру,
|
||||||
|
* что будет называться Starbound Unit
|
||||||
|
*
|
||||||
|
* Весь игровой мир будет измеряться в Starbound Unit'ах
|
||||||
|
*/
|
||||||
|
open class Chunk(val world: World<*>?, val pos: ChunkPos) {
|
||||||
|
/**
|
||||||
|
* Возвращает счётчик изменений чанка
|
||||||
|
*/
|
||||||
|
var changeset = 0
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun incChangeset() {
|
||||||
|
changeset++
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class TileLayer : IMutableTileChunk {
|
||||||
|
/**
|
||||||
|
* Возвращает счётчик изменений этого слоя
|
||||||
|
*/
|
||||||
|
var changeset = 0
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun incChangeset() {
|
||||||
|
changeset++
|
||||||
|
this@Chunk.changeset++
|
||||||
|
}
|
||||||
|
|
||||||
override val pos: ChunkPos
|
override val pos: ChunkPos
|
||||||
get() = this@Chunk.pos
|
get() = this@Chunk.pos
|
||||||
|
|
||||||
@ -277,6 +325,7 @@ open class Chunk(val world: World, val pos: ChunkPos) {
|
|||||||
if (isOutside(x, y))
|
if (isOutside(x, y))
|
||||||
throw IndexOutOfBoundsException("Trying to set tile ${tile?.def?.materialName} at $x $y, but that is outside of chunk's range")
|
throw IndexOutOfBoundsException("Trying to set tile ${tile?.def?.materialName} at $x $y, but that is outside of chunk's range")
|
||||||
|
|
||||||
|
changeset++
|
||||||
tiles[x or (y shl CHUNK_SHIFT)] = tile
|
tiles[x or (y shl CHUNK_SHIFT)] = tile
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,18 +333,19 @@ open class Chunk(val world: World, val pos: ChunkPos) {
|
|||||||
if (isOutside(x, y))
|
if (isOutside(x, y))
|
||||||
throw IndexOutOfBoundsException("Trying to set tile ${tile?.materialName} at $x $y, but that is outside of chunk's range")
|
throw IndexOutOfBoundsException("Trying to set tile ${tile?.materialName} at $x $y, but that is outside of chunk's range")
|
||||||
|
|
||||||
val chunkTile = if (tile != null) ChunkTile(tile) else null
|
val chunkTile = if (tile != null) ChunkTile(this, tile) else null
|
||||||
this[x, y] = chunkTile
|
this[x, y] = chunkTile
|
||||||
|
changeset++
|
||||||
return chunkTile
|
return chunkTile
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun randomLongFor(x: Int, y: Int): Long {
|
override fun randomLongFor(x: Int, y: Int): Long {
|
||||||
return super.randomLongFor(x, y) xor world.seed
|
return super.randomLongFor(x, y) xor (world?.seed ?: 0L)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val foreground = ChunkTileLayer()
|
val foreground = TileLayer()
|
||||||
val background = ChunkTileLayer()
|
val background = TileLayer()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val EMPTY = object : IMutableTileChunk {
|
val EMPTY = object : IMutableTileChunk {
|
||||||
|
@ -3,139 +3,174 @@ package ru.dbotthepony.kstarbound.world
|
|||||||
import ru.dbotthepony.kstarbound.defs.TileDefinition
|
import ru.dbotthepony.kstarbound.defs.TileDefinition
|
||||||
import ru.dbotthepony.kstarbound.math.Vector2i
|
import ru.dbotthepony.kstarbound.math.Vector2i
|
||||||
|
|
||||||
class World(val seed: Long = 0L) {
|
/**
|
||||||
private val chunkMap = ArrayList<Pair<ChunkPos, Chunk>>()
|
* Возвращает кортеж чанка, который содержит родителя (мир) и соседей (кортежи чанков)
|
||||||
private var lastAccessedChunk: Chunk? = null
|
*/
|
||||||
|
interface IWorldChunkTuple {
|
||||||
|
val world: World<*>
|
||||||
|
val chunk: Chunk
|
||||||
|
val top: IWorldChunkTuple?
|
||||||
|
val left: IWorldChunkTuple?
|
||||||
|
val right: IWorldChunkTuple?
|
||||||
|
val bottom: IWorldChunkTuple?
|
||||||
|
}
|
||||||
|
|
||||||
fun getChunk(pos: ChunkPos): Chunk? {
|
interface IMutableWorldChunkTuple : IWorldChunkTuple {
|
||||||
if (lastAccessedChunk?.pos == pos) {
|
override var top: IWorldChunkTuple?
|
||||||
|
override var left: IWorldChunkTuple?
|
||||||
|
override var right: IWorldChunkTuple?
|
||||||
|
override var bottom: IWorldChunkTuple?
|
||||||
|
}
|
||||||
|
|
||||||
|
data class WorldChunkTuple(
|
||||||
|
override val world: World<*>,
|
||||||
|
override val chunk: Chunk,
|
||||||
|
override val top: IWorldChunkTuple?,
|
||||||
|
override val left: IWorldChunkTuple?,
|
||||||
|
override val right: IWorldChunkTuple?,
|
||||||
|
override val bottom: IWorldChunkTuple?,
|
||||||
|
) : IWorldChunkTuple
|
||||||
|
|
||||||
|
open class MutableWorldChunkTuple(
|
||||||
|
override val world: World<*>,
|
||||||
|
override val chunk: Chunk,
|
||||||
|
override var top: IWorldChunkTuple?,
|
||||||
|
override var left: IWorldChunkTuple?,
|
||||||
|
override var right: IWorldChunkTuple?,
|
||||||
|
override var bottom: IWorldChunkTuple?,
|
||||||
|
) : IMutableWorldChunkTuple
|
||||||
|
|
||||||
|
@Suppress("WeakerAccess")
|
||||||
|
abstract class World<T : IMutableWorldChunkTuple>(val seed: Long = 0L) {
|
||||||
|
protected val chunkMap = HashMap<ChunkPos, T>()
|
||||||
|
protected var lastAccessedChunk: T? = null
|
||||||
|
|
||||||
|
protected abstract fun tupleFactory(
|
||||||
|
chunk: Chunk,
|
||||||
|
top: IWorldChunkTuple?,
|
||||||
|
left: IWorldChunkTuple?,
|
||||||
|
right: IWorldChunkTuple?,
|
||||||
|
bottom: IWorldChunkTuple?,
|
||||||
|
): T
|
||||||
|
|
||||||
|
protected fun getChunkInternal(pos: ChunkPos): T? {
|
||||||
|
if (lastAccessedChunk?.chunk?.pos == pos) {
|
||||||
return lastAccessedChunk
|
return lastAccessedChunk
|
||||||
}
|
}
|
||||||
|
|
||||||
for ((k, v) in chunkMap) {
|
return chunkMap[pos]
|
||||||
if (k == pos) {
|
}
|
||||||
lastAccessedChunk = v
|
|
||||||
return v
|
open fun getChunk(pos: ChunkPos): IWorldChunkTuple? {
|
||||||
}
|
val getTuple = getChunkInternal(pos)
|
||||||
}
|
|
||||||
|
if (getTuple != null)
|
||||||
|
return WorldChunkTuple(
|
||||||
|
world = getTuple.world,
|
||||||
|
chunk = getTuple.chunk,
|
||||||
|
top = getTuple.top,
|
||||||
|
left = getTuple.left,
|
||||||
|
right = getTuple.right,
|
||||||
|
bottom = getTuple.bottom,
|
||||||
|
)
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getOrMakeChunk(pos: ChunkPos): Chunk {
|
protected open fun computeIfAbsentInternal(pos: ChunkPos): T {
|
||||||
if (lastAccessedChunk?.pos == pos) {
|
if (lastAccessedChunk?.chunk?.pos == pos) {
|
||||||
return lastAccessedChunk!!
|
return lastAccessedChunk!!
|
||||||
}
|
}
|
||||||
|
|
||||||
for ((k, v) in chunkMap) {
|
return chunkMap.computeIfAbsent(pos) lazy@{
|
||||||
if (k == pos) {
|
val chunk = Chunk(this, pos)
|
||||||
return v
|
|
||||||
}
|
val top = getChunkInternal(pos.up())
|
||||||
|
val left = getChunkInternal(pos.left())
|
||||||
|
val right = getChunkInternal(pos.right())
|
||||||
|
val bottom = getChunkInternal(pos.down())
|
||||||
|
|
||||||
|
val tuple = tupleFactory(
|
||||||
|
chunk = chunk,
|
||||||
|
top = top,
|
||||||
|
left = left,
|
||||||
|
right = right,
|
||||||
|
bottom = bottom,
|
||||||
|
)
|
||||||
|
|
||||||
|
top?.bottom = tuple
|
||||||
|
left?.right = tuple
|
||||||
|
right?.left = tuple
|
||||||
|
bottom?.top = tuple
|
||||||
|
|
||||||
|
lastAccessedChunk = tuple
|
||||||
|
|
||||||
|
return@lazy tuple
|
||||||
}
|
}
|
||||||
|
|
||||||
val chunk = Chunk(this, pos)
|
|
||||||
lastAccessedChunk = chunk
|
|
||||||
chunkMap.add(pos to chunk)
|
|
||||||
return chunk
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getForegroundView(pos: ChunkPos): TileChunkView? {
|
open fun computeIfAbsent(pos: ChunkPos): IWorldChunkTuple {
|
||||||
val get = getChunk(pos) ?: return null
|
val getTuple = computeIfAbsentInternal(pos)
|
||||||
|
|
||||||
return TileChunkView(
|
return WorldChunkTuple(
|
||||||
center = get.foreground,
|
world = getTuple.world,
|
||||||
left = getChunk(pos.left())?.foreground,
|
chunk = getTuple.chunk,
|
||||||
top = getChunk(pos.up())?.foreground,
|
top = getTuple.top,
|
||||||
topLeft = getChunk(pos.up().left())?.foreground,
|
left = getTuple.left,
|
||||||
topRight = getChunk(pos.up().right())?.foreground,
|
right = getTuple.right,
|
||||||
right = getChunk(pos.right())?.foreground,
|
bottom = getTuple.bottom,
|
||||||
bottom = getChunk(pos.down())?.foreground,
|
|
||||||
bottomLeft = getChunk(pos.down().left())?.foreground,
|
|
||||||
bottomRight = getChunk(pos.down().right())?.foreground,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getBackgroundView(pos: ChunkPos): TileChunkView? {
|
open fun getForegroundView(pos: ChunkPos): TileChunkView? {
|
||||||
val get = getChunk(pos) ?: return null
|
val get = getChunkInternal(pos) ?: return null
|
||||||
|
|
||||||
return TileChunkView(
|
return TileChunkView(
|
||||||
center = get.background,
|
center = get.chunk.foreground,
|
||||||
left = getChunk(pos.left())?.background,
|
left = get.left?.chunk?.foreground,
|
||||||
top = getChunk(pos.up())?.background,
|
top = get.top?.chunk?.foreground,
|
||||||
topLeft = getChunk(pos.up().left())?.background,
|
topLeft = getChunkInternal(pos.up().left())?.chunk?.foreground,
|
||||||
topRight = getChunk(pos.up().right())?.background,
|
topRight = getChunkInternal(pos.up().right())?.chunk?.foreground,
|
||||||
right = getChunk(pos.right())?.background,
|
right = get.right?.chunk?.foreground,
|
||||||
bottom = getChunk(pos.down())?.background,
|
bottom = get.bottom?.chunk?.foreground,
|
||||||
bottomLeft = getChunk(pos.down().left())?.background,
|
bottomLeft = getChunkInternal(pos.down().left())?.chunk?.foreground,
|
||||||
bottomRight = getChunk(pos.down().right())?.background,
|
bottomRight = getChunkInternal(pos.down().right())?.chunk?.foreground,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
open fun getBackgroundView(pos: ChunkPos): TileChunkView? {
|
||||||
* Считается, что [pos] это абсолютные координаты ТАЙЛА в мире, поэтому они
|
val get = getChunkInternal(pos) ?: return null
|
||||||
* трансформируются в координаты чанка
|
|
||||||
*/
|
|
||||||
fun getChunk(pos: Vector2i) = getChunk(ChunkPos(pos))
|
|
||||||
|
|
||||||
/**
|
return TileChunkView(
|
||||||
* Считается, что [pos] это абсолютные координаты ТАЙЛА в мире, поэтому они
|
center = get.chunk.background,
|
||||||
* трансформируются в координаты чанка
|
left = get.left?.chunk?.background,
|
||||||
*/
|
top = get.top?.chunk?.background,
|
||||||
fun getOrMakeChunk(pos: Vector2i) = getOrMakeChunk(ChunkPos(pos))
|
topLeft = getChunkInternal(pos.up().left())?.chunk?.background,
|
||||||
|
topRight = getChunkInternal(pos.up().right())?.chunk?.background,
|
||||||
/**
|
right = get.right?.chunk?.background,
|
||||||
* Считается, что [pos] это абсолютные координаты ТАЙЛА в мире, поэтому они
|
bottom = get.bottom?.chunk?.background,
|
||||||
* трансформируются в координаты чанка
|
bottomLeft = getChunkInternal(pos.down().left())?.chunk?.background,
|
||||||
*/
|
bottomRight = getChunkInternal(pos.down().right())?.chunk?.background,
|
||||||
fun getForegroundView(pos: Vector2i) = getForegroundView(ChunkPos(pos))
|
)
|
||||||
|
}
|
||||||
/**
|
|
||||||
* Считается, что [pos] это абсолютные координаты ТАЙЛА в мире, поэтому они
|
|
||||||
* трансформируются в координаты чанка
|
|
||||||
*/
|
|
||||||
fun getBackgroundView(pos: Vector2i) = getBackgroundView(ChunkPos(pos))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Считается, что [x] и [y] это абсолютные координаты ЧАНКА в мире, поэтому они
|
|
||||||
* НЕ трансформируются в координаты чанка, а используются напрямую
|
|
||||||
*/
|
|
||||||
fun getChunk(x: Int, y: Int) = getChunk(ChunkPos(x, y))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Считается, что [x] и [y] это абсолютные координаты ЧАНКА в мире, поэтому они
|
|
||||||
* НЕ трансформируются в координаты чанка, а используются напрямую
|
|
||||||
*/
|
|
||||||
fun getOrMakeChunk(x: Int, y: Int) = getOrMakeChunk(ChunkPos(x, y))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Считается, что [x] и [y] это абсолютные координаты ЧАНКА в мире, поэтому они
|
|
||||||
* НЕ трансформируются в координаты чанка, а используются напрямую
|
|
||||||
*/
|
|
||||||
fun getForegroundView(x: Int, y: Int) = getForegroundView(ChunkPos(x, y))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Считается, что [x] и [y] это абсолютные координаты ЧАНКА в мире, поэтому они
|
|
||||||
* НЕ трансформируются в координаты чанка, а используются напрямую
|
|
||||||
*/
|
|
||||||
fun getBackgroundView(x: Int, y: Int) = getBackgroundView(ChunkPos(x, y))
|
|
||||||
|
|
||||||
fun getTile(pos: Vector2i): ChunkTile? {
|
fun getTile(pos: Vector2i): ChunkTile? {
|
||||||
return getChunk(pos)?.foreground?.get(pos.x, pos.y)
|
return getChunkInternal(ChunkPos.fromTilePosition(pos))?.chunk?.foreground?.get(pos.x, pos.y)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setTile(pos: Vector2i, tile: TileDefinition?): Chunk {
|
fun setTile(pos: Vector2i, tile: TileDefinition?): IWorldChunkTuple {
|
||||||
val chunk = getOrMakeChunk(pos)
|
val chunk = computeIfAbsentInternal(ChunkPos.fromTilePosition(pos))
|
||||||
chunk.foreground[pos.x and CHUNK_SIZE_FF, pos.y and CHUNK_SIZE_FF] = tile
|
chunk.chunk.foreground[pos.x and CHUNK_SIZE_FF, pos.y and CHUNK_SIZE_FF] = tile
|
||||||
return chunk
|
return chunk
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getBackgroundTile(pos: Vector2i): ChunkTile? {
|
fun getBackgroundTile(pos: Vector2i): ChunkTile? {
|
||||||
return getChunk(pos)?.background?.get(pos.x, pos.y)
|
return getChunkInternal(ChunkPos.fromTilePosition(pos))?.chunk?.background?.get(pos.x, pos.y)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setBackgroundTile(pos: Vector2i, tile: TileDefinition?): Chunk {
|
fun setBackgroundTile(pos: Vector2i, tile: TileDefinition?): IWorldChunkTuple {
|
||||||
val chunk = getOrMakeChunk(pos)
|
val chunk = computeIfAbsentInternal(ChunkPos.fromTilePosition(pos))
|
||||||
chunk.background[pos.x and CHUNK_SIZE_FF, pos.y and CHUNK_SIZE_FF] = tile
|
chunk.chunk.background[pos.x and CHUNK_SIZE_FF, pos.y and CHUNK_SIZE_FF] = tile
|
||||||
return chunk
|
return chunk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user