431 lines
12 KiB
Kotlin
431 lines
12 KiB
Kotlin
package ru.dbotthepony.kstarbound.client
|
|
|
|
import it.unimi.dsi.fastutil.longs.Long2ObjectFunction
|
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
|
|
import it.unimi.dsi.fastutil.longs.LongArraySet
|
|
import it.unimi.dsi.fastutil.objects.ReferenceArraySet
|
|
import ru.dbotthepony.kstarbound.client.render.ConfiguredMesh
|
|
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
|
|
import ru.dbotthepony.kstarbound.client.render.Mesh
|
|
import ru.dbotthepony.kstarbound.client.render.MultiMeshBuilder
|
|
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
|
import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
|
|
import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity
|
|
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE
|
|
import ru.dbotthepony.kstarbound.world.ChunkPos
|
|
import ru.dbotthepony.kstarbound.world.World
|
|
import ru.dbotthepony.kstarbound.world.api.ITileAccess
|
|
import ru.dbotthepony.kstarbound.world.api.OffsetCellAccess
|
|
import ru.dbotthepony.kstarbound.world.api.TileView
|
|
import ru.dbotthepony.kstarbound.world.entities.Entity
|
|
import ru.dbotthepony.kstarbound.world.positiveModulo
|
|
import ru.dbotthepony.kvector.api.IStruct2i
|
|
import ru.dbotthepony.kvector.util2d.AABB
|
|
import ru.dbotthepony.kvector.vector.RGBAColor
|
|
import ru.dbotthepony.kvector.vector.Vector2f
|
|
import ru.dbotthepony.kvector.vector.Vector2i
|
|
|
|
class ClientWorld(
|
|
val client: StarboundClient,
|
|
seed: Long,
|
|
size: Vector2i? = null,
|
|
loopX: Boolean = false,
|
|
loopY: Boolean = false
|
|
) : World<ClientWorld, ClientChunk>(seed, size, loopX, loopY) {
|
|
init {
|
|
physics.debugDraw = client.gl.box2dRenderer
|
|
}
|
|
|
|
private fun determineChunkSize(cells: Int): Int {
|
|
for (i in 32 downTo 1) {
|
|
if (cells % i == 0) {
|
|
return i
|
|
}
|
|
}
|
|
|
|
throw RuntimeException("unreachable code")
|
|
}
|
|
|
|
val renderRegionWidth = if (size == null) 16 else determineChunkSize(size.x)
|
|
val renderRegionHeight = if (size == null) 16 else determineChunkSize(size.y)
|
|
val renderRegionsX = if (size == null) 0 else size.x / renderRegionWidth
|
|
val renderRegionsY = if (size == null) 0 else size.y / renderRegionHeight
|
|
|
|
fun isValidRenderRegionX(value: Int): Boolean {
|
|
if (size == null || loopX) {
|
|
return true
|
|
} else {
|
|
return value in 0 .. renderRegionsX
|
|
}
|
|
}
|
|
|
|
fun isValidRenderRegionY(value: Int): Boolean {
|
|
if (size == null || loopY) {
|
|
return true
|
|
} else {
|
|
return value in 0 .. renderRegionsY
|
|
}
|
|
}
|
|
|
|
inner class RenderRegion(val x: Int, val y: Int) {
|
|
inner class Layer(private val view: ITileAccess, private val isBackground: Boolean) {
|
|
private val state get() = client.gl
|
|
val bakedMeshes = ArrayList<Pair<ConfiguredMesh<*>, Int>>()
|
|
var isDirty = true
|
|
|
|
fun bake() {
|
|
if (!isDirty) return
|
|
isDirty = false
|
|
|
|
bakedMeshes.clear()
|
|
|
|
val meshes = MultiMeshBuilder()
|
|
|
|
for (x in 0 until renderRegionWidth) {
|
|
for (y in 0 until renderRegionHeight) {
|
|
if (!inBounds(x, y)) continue
|
|
|
|
val tile = view.getTile(x, y) ?: continue
|
|
val material = tile.material
|
|
|
|
if (material != null) {
|
|
client.tileRenderers.getMaterialRenderer(material.materialName).tesselate(tile, view, meshes, Vector2i(x, y), background = isBackground)
|
|
}
|
|
|
|
val modifier = tile.modifier
|
|
|
|
if (modifier != null) {
|
|
client.tileRenderers.getModifierRenderer(modifier.modName).tesselate(tile, view, meshes, Vector2i(x, y), background = isBackground, isModifier = true)
|
|
}
|
|
}
|
|
}
|
|
|
|
for ((baked, builder, zLevel) in meshes.meshes()) {
|
|
bakedMeshes.add(ConfiguredMesh(baked, Mesh(state, builder)) to zLevel)
|
|
}
|
|
|
|
meshes.clear()
|
|
}
|
|
}
|
|
|
|
private val liquidMesh = ArrayList<Pair<Mesh, RGBAColor>>()
|
|
var liquidIsDirty = true
|
|
|
|
val view = OffsetCellAccess(this@ClientWorld, x * renderRegionWidth, y * renderRegionHeight)
|
|
|
|
val background = Layer(TileView.Background(view), true)
|
|
val foreground = Layer(TileView.Foreground(view), false)
|
|
|
|
fun addLayers(layers: LayeredRenderer, renderOrigin: Vector2f) {
|
|
background.bake()
|
|
foreground.bake()
|
|
|
|
if (liquidIsDirty) {
|
|
liquidIsDirty = false
|
|
liquidMesh.clear()
|
|
|
|
val liquidTypes = ReferenceArraySet<LiquidDefinition>()
|
|
|
|
for (x in 0 until renderRegionWidth) {
|
|
for (y in 0 until renderRegionHeight) {
|
|
view.getCell(x, y)?.liquid?.def?.let { liquidTypes.add(it) }
|
|
}
|
|
}
|
|
|
|
for (type in liquidTypes) {
|
|
val builder = client.gl.programs.liquid.builder.builder
|
|
|
|
builder.begin()
|
|
|
|
for (x in 0 until renderRegionWidth) {
|
|
for (y in 0 until renderRegionHeight) {
|
|
val state = view.getCell(x, y)
|
|
|
|
if (state?.liquid?.def === type) {
|
|
builder.quad(x.toFloat(), y.toFloat(), x + 1f, y + state!!.liquid.level)
|
|
}
|
|
}
|
|
}
|
|
|
|
liquidMesh.add(Mesh(client.gl, builder) to type.color)
|
|
}
|
|
}
|
|
|
|
for ((baked, zLevel) in background.bakedMeshes) {
|
|
layers.add(zLevel + Z_LEVEL_BACKGROUND) {
|
|
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
|
|
baked.render(it.last())
|
|
it.pop()
|
|
}
|
|
}
|
|
|
|
for ((baked, zLevel) in foreground.bakedMeshes) {
|
|
layers.add(zLevel) {
|
|
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
|
|
baked.render(it.last())
|
|
it.pop()
|
|
}
|
|
}
|
|
|
|
if (liquidMesh.isNotEmpty()) {
|
|
layers.add(Z_LEVEL_LIQUID) {
|
|
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
|
|
|
|
val program = client.gl.programs.liquid
|
|
|
|
program.use()
|
|
program.transform = it.last()
|
|
|
|
for ((mesh, color) in liquidMesh) {
|
|
program.baselineColor = color
|
|
mesh.render()
|
|
}
|
|
|
|
it.pop()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
val renderRegions = Long2ObjectOpenHashMap<RenderRegion>()
|
|
|
|
fun renderRegionKey(x: Int, y: Int): Long {
|
|
if (size == null) {
|
|
return x.toLong() shl 32 or y.toLong()
|
|
} else {
|
|
return positiveModulo(x, renderRegionsX).toLong() shl 32 or positiveModulo(y, renderRegionsY).toLong()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* all intersecting chunks
|
|
*/
|
|
inline fun forEachRenderRegion(pos: ChunkPos, action: (RenderRegion) -> Unit) {
|
|
var (ix, iy) = pos.tile
|
|
ix /= renderRegionWidth
|
|
iy /= renderRegionHeight
|
|
|
|
for (x in ix .. ix + CHUNK_SIZE / renderRegionWidth) {
|
|
for (y in iy .. iy + CHUNK_SIZE / renderRegionWidth) {
|
|
renderRegions[renderRegionKey(x, y)]?.let(action)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* cell and cells around
|
|
*/
|
|
inline fun forEachRenderRegion(pos: IStruct2i, action: (RenderRegion) -> Unit) {
|
|
val dx = pos.component1() % renderRegionWidth
|
|
val dy = pos.component2() % renderRegionHeight
|
|
|
|
if (dx == 1 || dx == renderRegionWidth - 1 || dy == 1 || dy == renderRegionHeight - 1) {
|
|
val seen = LongArraySet(8)
|
|
|
|
for ((x, y) in ring) {
|
|
val ix = (pos.component1() + x) / renderRegionWidth
|
|
val iy = (pos.component2() + y) / renderRegionHeight
|
|
val index = renderRegionKey(ix, iy)
|
|
|
|
if (seen.add(index)) {
|
|
renderRegions[index]?.let(action)
|
|
}
|
|
}
|
|
} else {
|
|
val ix = pos.component1() / renderRegionWidth
|
|
val iy = pos.component2() / renderRegionHeight
|
|
renderRegions[renderRegionKey(ix, iy)]?.let(action)
|
|
}
|
|
}
|
|
|
|
override fun chunkFactory(pos: ChunkPos): ClientChunk {
|
|
return ClientChunk(this, pos)
|
|
}
|
|
|
|
fun addLayers(
|
|
size: AABB,
|
|
layers: LayeredRenderer
|
|
) {
|
|
val rx = roundTowardsNegativeInfinity(size.mins.x) / renderRegionWidth - 1
|
|
val ry = roundTowardsNegativeInfinity(size.mins.y) / renderRegionHeight - 1
|
|
|
|
val dx = roundTowardsPositiveInfinity(size.maxs.x - size.mins.x) / renderRegionWidth + 2
|
|
val dy = roundTowardsPositiveInfinity(size.maxs.y - size.mins.y) / renderRegionHeight + 2
|
|
|
|
for (x in rx .. rx + dx) {
|
|
for (y in ry .. ry + dy) {
|
|
if (!isValidRenderRegionX(x) || !isValidRenderRegionY(y)) continue
|
|
val renderer = renderRegions.computeIfAbsent(renderRegionKey(x, y), Long2ObjectFunction {
|
|
RenderRegion((it ushr 32).toInt(), it.toInt())
|
|
})
|
|
|
|
renderer.addLayers(layers, Vector2f(x * renderRegionWidth.toFloat(), y * renderRegionHeight.toFloat()))
|
|
}
|
|
}
|
|
|
|
val pos = client.screenToWorld(client.mouseCoordinatesF).toDoubleVector()
|
|
|
|
/*layers.add(-999999) {
|
|
val lightsize = 16
|
|
|
|
val lightmap = floodLight(
|
|
Vector2i(pos.x.roundToInt(), pos.y.roundToInt()), lightsize
|
|
)
|
|
|
|
client.gl.quadWireframe {
|
|
for (column in 0 until lightmap.columns) {
|
|
for (row in 0 until lightmap.rows) {
|
|
if (lightmap[column, row] > 0) {
|
|
it.quad(pos.x.roundToInt() + column.toFloat() - lightsize, pos.y.roundToInt() + row.toFloat() - lightsize, pos.x.roundToInt() + column + 1f - lightsize, pos.y.roundToInt() + row + 1f - lightsize)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}*/
|
|
|
|
|
|
/*layers.add(-999999) {
|
|
val rayFan = ArrayList<Vector2d>()
|
|
|
|
for (i in 0 .. 359) {
|
|
rayFan.add(Vector2d(cos(i / 180.0 * PI), sin(i / 180.0 * PI)))
|
|
}
|
|
|
|
for (ray in rayFan) {
|
|
val trace = castRayNaive(pos, ray, 16.0)
|
|
|
|
client.gl.quadWireframe {
|
|
for ((tpos, tile) in trace.traversedTiles) {
|
|
if (tile.foreground.material != null)
|
|
it.quad(
|
|
tpos.x.toFloat(),
|
|
tpos.y.toFloat(),
|
|
tpos.x + 1f,
|
|
tpos.y + 1f
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}*/
|
|
|
|
//rayLightCircleNaive(pos, 48.0, falloffByTravel = 1.0, falloffByTile = 3.0)
|
|
|
|
/*
|
|
val result = rayLightCircleNaive(pos, 48.0, falloffByTravel = 1.0, falloffByTile = 3.0)
|
|
val result2 = rayLightCircleNaive(pos + Vector2d(-8.0), 24.0, falloffByTravel = 1.0, falloffByTile = 3.0)
|
|
val frame = GLFrameBuffer(client.gl)
|
|
frame.attachTexture(client.viewportWidth, client.viewportHeight)
|
|
|
|
frame.bind()
|
|
|
|
client.gl.clearColor = Color.BLACK
|
|
glClear(GL_COLOR_BUFFER_BIT)
|
|
|
|
client.gl.blendFunc = BlendFunc.ADDITIVE*/
|
|
|
|
/*client.gl.quadColor {
|
|
for (row in 0 until result.rows) {
|
|
for (column in 0 until result.columns) {
|
|
if (result[column, row] > 0.05) {
|
|
val color = result[column, row].toFloat() * 1.5f
|
|
|
|
it.quad(
|
|
pos.x.roundToInt() - result.rows.toFloat() / 2f + row.toFloat(),
|
|
pos.y.roundToInt() - result.columns.toFloat() / 2f + column.toFloat(),
|
|
pos.x.roundToInt() - result.rows.toFloat() / 2f + row + 1f,
|
|
pos.y.roundToInt() - result.columns.toFloat() / 2f + column + 1f
|
|
) { a, b -> a.pushVec4f(color, color, color, 1f) }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
client.gl.quadColor {
|
|
for (row in 0 until result2.rows) {
|
|
for (column in 0 until result2.columns) {
|
|
if (result2[column, row] > 0.05) {
|
|
val color = result2[column, row].toFloat() * 1.5f
|
|
|
|
it.quad(
|
|
pos.x.roundToInt() - 8f - result2.rows.toFloat() / 2f + row.toFloat(),
|
|
pos.y.roundToInt() - result2.columns.toFloat() / 2f + column.toFloat(),
|
|
pos.x.roundToInt() - 8f - result2.rows.toFloat() / 2f + row + 1f,
|
|
pos.y.roundToInt() - result2.columns.toFloat() / 2f + column + 1f
|
|
) { a, b -> a.pushVec4f(color, 0f, 0f, 1f) }
|
|
}
|
|
}
|
|
}
|
|
}*/
|
|
|
|
/*val lightTextureWidth = (client.viewportWidth / PIXELS_IN_STARBOUND_UNIT).roundToInt()
|
|
val lightTextureHeight = (client.viewportHeight / PIXELS_IN_STARBOUND_UNIT).roundToInt()
|
|
|
|
val textureBuffer = ByteBuffer.allocateDirect(lightTextureWidth * lightTextureHeight * 3)
|
|
textureBuffer.order(ByteOrder.LITTLE_ENDIAN)
|
|
|
|
for (x in 0 until result.columns.coerceAtMost(lightTextureWidth)) {
|
|
for (y in 0 until result.rows.coerceAtMost(lightTextureHeight)) {
|
|
textureBuffer.position(x * 3 + y * lightTextureWidth * 3)
|
|
|
|
if (result[x, y] > 0.05) {
|
|
val color = result[x, y].toFloat() * 1.5f
|
|
|
|
textureBuffer.put((color * 255).toInt().coerceAtMost(255).toByte())
|
|
textureBuffer.put((color * 255).toInt().coerceAtMost(255).toByte())
|
|
textureBuffer.put((color * 255).toInt().coerceAtMost(255).toByte())
|
|
}
|
|
}
|
|
}*/
|
|
|
|
//frame.unbind()
|
|
|
|
// val texture = GLTexture2D(client.gl)
|
|
|
|
// textureBuffer.position(0)
|
|
// texture.upload(GL_RGB, lightTextureWidth, lightTextureHeight, GL_RGB, GL_UNSIGNED_BYTE, textureBuffer)
|
|
|
|
// texture.textureMinFilter = GL_LINEAR
|
|
// texture.textureMagFilter = GL_LINEAR
|
|
|
|
// client.gl.blendFunc = BlendFunc.MULTIPLY_BY_SRC
|
|
|
|
// client.gl.activeTexture = 0
|
|
// client.gl.texture2D = texture
|
|
// client.gl.programs.viewTextureQuad.run()
|
|
|
|
// client.gl.blendFunc = old
|
|
|
|
//frame.close()
|
|
//texture.close()
|
|
|
|
/*for (renderer in determineRenderers) {
|
|
renderer.renderDebug()
|
|
}*/
|
|
}
|
|
|
|
override fun thinkInner(delta: Double) {
|
|
val copy = arrayOfNulls<Entity>(entities.size)
|
|
var i = 0
|
|
|
|
for (ent in entities) {
|
|
copy[i++] = ent
|
|
}
|
|
|
|
for (ent in copy) {
|
|
ent!!.think(delta)
|
|
}
|
|
}
|
|
|
|
companion object {
|
|
val ring = listOf(
|
|
Vector2i(0, 0),
|
|
Vector2i(1, 0),
|
|
Vector2i(1, 1),
|
|
Vector2i(1, -1),
|
|
Vector2i(-1, 0),
|
|
Vector2i(-1, 1),
|
|
Vector2i(-1, -1),
|
|
)
|
|
}
|
|
}
|