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(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, 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>() 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() 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() 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() 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(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), ) } }