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 ru.dbotthepony.kstarbound.client.render.ConfiguredStaticMesh import ru.dbotthepony.kstarbound.client.render.LayeredRenderer import ru.dbotthepony.kstarbound.client.render.TileLayerList import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity import ru.dbotthepony.kstarbound.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.kvector.api.IStruct2i import ru.dbotthepony.kvector.util2d.AABB import ru.dbotthepony.kvector.vector.Vector2d import ru.dbotthepony.kvector.vector.Vector2f import ru.dbotthepony.kvector.vector.Vector2i import java.util.function.Function import kotlin.collections.ArrayList import kotlin.math.PI import kotlin.math.cos import kotlin.math.sin 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 16 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) 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 private val layers = TileLayerList() val bakedMeshes = ArrayList>() var isDirty = true fun bake() { if (!isDirty) return isDirty = false if (state.isSameThread()) { for (mesh in bakedMeshes) { mesh.first.close() } } bakedMeshes.clear() layers.clear() 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.getTileRenderer(material.materialName).tesselate(tile, view, layers, Vector2i(x, y), background = isBackground) } val modifier = tile.modifier if (modifier != null) { client.tileRenderers.getModifierRenderer(modifier.modName).tesselate(tile, view, layers, Vector2i(x, y), background = isBackground, isModifier = true) } } } if (layers.isNotEmpty) { for (mesh in bakedMeshes) { mesh.first.close() } bakedMeshes.clear() for ((baked, builder, zLevel) in layers.buildSortedLayerList()) { bakedMeshes.add(ConfiguredStaticMesh(baked, builder) to zLevel) } layers.clear() } } } 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() for ((baked, zLevel) in background.bakedMeshes) { layers.add(zLevel + Z_LEVEL_BACKGROUND) { it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y) baked.renderStacked(it) it.pop() } } for ((baked, zLevel) in foreground.bakedMeshes) { layers.add(zLevel) { it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y) baked.renderStacked(it) it.pop() } } /*for (renderer in entityRenderers.values) { layers.add(renderer.layer) { val relative = renderer.renderPos - posVector2d it.push().last().translateWithMultiplication(renderOrigin.x + relative.x.toFloat(), renderOrigin.y + relative.y.toFloat()) renderer.render(it) it.pop() } }*/ /*val types = getLiquidTypes() if (types.isNotEmpty()) { layers.add(Z_LEVEL_LIQUID) { it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y) val program = state.programs.liquid program.use() program.transform = it.last() val builder = program.builder for (type in types) { builder.builder.begin() for (x in 0 until CHUNK_SIZE) { for (y in 0 until CHUNK_SIZE) { val state = getCell(x, y) if (state.liquid.def === type) { builder.builder.quad(x.toFloat(), y.toFloat(), x + 1f, y + state.liquid.level) } } } program.baselineColor = type.color builder.upload() builder.draw() } it.pop() } }*/ } } val renderRegions = Long2ObjectOpenHashMap() /** * 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[x.toLong() shl 32 or y.toLong()]?.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 = ix.toLong() shl 32 or iy.toLong() if (seen.add(index)) { renderRegions[index]?.let(action) } } } else { val ix = pos.component1() / renderRegionWidth val iy = pos.component2() / renderRegionHeight renderRegions[ix.toLong() shl 32 or iy.toLong()]?.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) { val renderer = renderRegions.computeIfAbsent(x.toLong() shl 32 or y.toLong(), 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), ) } }