diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt index 9fde86b2..0ac6a09b 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt @@ -113,6 +113,8 @@ fun main() { } } + chunkA!!.foreground[18, 14] = tile + /*val rand = Random() for (i in 0 .. 400) { @@ -172,7 +174,7 @@ fun main() { client.camera.pos.y = 10f client.gl.box2dRenderer.drawShapes = true - client.gl.box2dRenderer.drawPairs = false + client.gl.box2dRenderer.drawPairs = true client.gl.box2dRenderer.drawAABB = false client.gl.box2dRenderer.drawJoints = false @@ -186,8 +188,8 @@ fun main() { Starbound.pollCallbacks() //ent.think(client.frameRenderTime) - client.camera.pos.x = ent.position.x.toFloat() - client.camera.pos.y = ent.position.y.toFloat() + //client.camera.pos.x = ent.position.x.toFloat() + //client.camera.pos.y = ent.position.y.toFloat() //println(client.camera.velocity.toDoubleVector() * client.frameRenderTime * 0.1) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt index 2ffb00bc..2b76a594 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt @@ -336,6 +336,389 @@ class MutableTileChunkView( } } +private data class TileExposure( + var pos: Vector2d, + val left: Boolean, + val right: Boolean, + val up: Boolean, + val down: Boolean, +) + +private class TileFlooder( + private val tiles: Array, + private val seen: BooleanArray, + rootx: Int, + rooty: Int +) { + /** + * Tile positions which have at least one face free of neighbours + */ + val exposed = ArrayList() + + private fun get(x: Int, y: Int): Boolean { + if (x < 0 || x > CHUNK_SIZE_FF) { + return false + } + + if (y < 0 || y > CHUNK_SIZE_FF) { + return false + } + + return tiles[x or (y shl CHUNK_SHIFT)] != null + } + + private fun visit(x: Int, y: Int) { + if (seen[x or (y shl CHUNK_SHIFT)]) + return + + seen[x or (y shl CHUNK_SHIFT)] = true + + val left = get(x - 1, y) + val right = get(x + 1, y) + val up = get(x, y + 1) + val down = get(x, y - 1) + + if (!left || !right || !up || !down) { + exposed.add(TileExposure(Vector2d(x.toDouble() + 0.5, y.toDouble() + 0.5), !left, !right, !up, !down)) + } + + if (left) { + visit(x - 1, y) + } + + if (right) { + visit(x + 1, y) + } + + if (up) { + visit(x, y + 1) + } + + if (down) { + visit(x, y - 1) + } + } + + init { + visit(rootx, rooty) + } +} + +private class RectTileFlooderDepthFirst( + private val tiles: Array, + private val seen: BooleanArray, + rootx: Int, + rooty: Int +) { + val mins: Vector2i + val maxs: Vector2i + + private fun filled(x: Int, y: Int): Boolean { + if (x < 0 || x > CHUNK_SIZE_FF) { + return false + } + + if (y < 0 || y > CHUNK_SIZE_FF) { + return false + } + + return !seen[x or (y shl CHUNK_SHIFT)] && tiles[x or (y shl CHUNK_SHIFT)] != null + } + + init { + // expand wide + var widthPositive = 1 + + while (true) { + if (!filled(rootx + widthPositive, rooty)) { + break + } + + widthPositive++ + } + + var widthNegative = 1 + + while (true) { + if (!filled(rootx - widthNegative, rooty)) { + break + } + + widthNegative++ + } + + // expand tall + var heightPositive = 1 + + while (true) { + if (!filled(rootx, rooty + heightPositive)) { + break + } + + heightPositive++ + } + + var heightNegative = 1 + + while (true) { + if (!filled(rootx, rooty - heightNegative)) { + break + } + + heightNegative++ + } + + widthPositive -= 1 + widthNegative -= 1 + + heightNegative -= 1 + heightPositive -= 1 + + if (heightPositive + heightNegative > widthPositive + widthNegative) { + // height is bigger + // try to expand wide + widthPositive = 0 + widthNegative = 0 + + while (true) { + var escape = false + + for (i in -heightNegative .. heightPositive) { + if (!filled(rootx + widthPositive, rooty + i)) { + escape = true + break + } + } + + if (escape) { + break + } + + widthPositive++ + } + + while (true) { + var escape = false + + for (i in -heightNegative .. heightPositive) { + if (!filled(rootx - widthNegative, rooty + i)) { + escape = true + break + } + } + + if (escape) { + break + } + + widthNegative++ + } + + widthNegative -= 1 + widthPositive -= 1 + } else { + // height is equal or lesser than width + // expand high + heightNegative = 0 + heightPositive = 0 + + while (true) { + var escape = false + + for (i in -widthNegative .. widthPositive) { + if (!filled(rootx + i, rooty + heightPositive)) { + escape = true + break + } + } + + if (escape) { + break + } + + heightPositive++ + } + + while (true) { + var escape = false + + for (i in -widthNegative .. widthPositive) { + if (!filled(rootx + i, rooty - heightNegative)) { + escape = true + break + } + } + + if (escape) { + break + } + + heightNegative++ + } + + heightNegative -= 1 + heightPositive -= 1 + } + + mins = Vector2i(rootx - widthNegative, rooty - heightNegative) + maxs = Vector2i(rootx + widthPositive, rooty + heightPositive) + } + + fun markSeen() { + for (x in mins.x .. maxs.x) { + for (y in mins.y .. maxs.y) { + seen[x or (y shl CHUNK_SHIFT)] = true + } + } + } +} + +private class RectTileFlooderSizeFirst( + private val tiles: Array, + private val seen: BooleanArray, + private val rootx: Int, + private val rooty: Int +) { + val mins: Vector2i + val maxs: Vector2i + + private fun filled(x: Int, y: Int): Boolean { + if (x < 0 || x > CHUNK_SIZE_FF) { + return false + } + + if (y < 0 || y > CHUNK_SIZE_FF) { + return false + } + + return !seen[x or (y shl CHUNK_SHIFT)] && tiles[x or (y shl CHUNK_SHIFT)] != null + } + + private var widthPositive = 0 + private var widthNegative = 0 + private var heightPositive = 0 + private var heightNegative = 0 + + private fun checkLeft(): Boolean { + for (i in -heightNegative .. heightPositive) { + if (!filled(rootx - widthNegative, rooty + i)) { + return false + } + } + + return true + } + + private fun checkRight(): Boolean { + for (i in -heightNegative .. heightPositive) { + if (!filled(rootx + widthPositive, rooty + i)) { + return false + } + } + + return true + } + + private fun checkUp(): Boolean { + for (i in -widthNegative .. widthPositive) { + if (!filled(rootx + i, rooty + heightPositive)) { + return false + } + } + + return true + } + + private fun checkDown(): Boolean { + for (i in -widthNegative .. widthPositive) { + if (!filled(rootx + i, rooty - heightNegative)) { + return false + } + } + + return true + } + + init { + var expanded = true + var hitLeft = false + var hitRight = false + var hitUp = false + var hitDown = false + + while (expanded) { + expanded = false + + // expand left + if (!hitLeft) { + widthNegative++ + + if (!checkLeft()) { + widthNegative-- + hitLeft = true + } else { + expanded = true + } + } + + // expand up + if (!hitUp) { + heightPositive++ + + if (!checkUp()) { + heightPositive-- + hitUp = true + } else { + expanded = true + } + } + + // expand right + if (!hitRight) { + widthPositive++ + + if (!checkRight()) { + widthPositive-- + hitRight = true + } else { + expanded = true + } + } + + // expand down + if (!hitDown) { + heightNegative++ + + if (!checkDown()) { + heightNegative-- + hitDown = true + } else { + expanded = true + } + } + } + + mins = Vector2i(rootx - widthNegative, rooty - heightNegative) + maxs = Vector2i(rootx + widthPositive, rooty + heightPositive) + } + + fun markSeen() { + for (x in mins.x .. maxs.x) { + for (y in mins.y .. maxs.y) { + seen[x or (y shl CHUNK_SHIFT)] = true + } + } + } +} + +private fun ccwSortScore(point: Vector2d, axis: Vector2d): Double { + if (point.x > 0.0) { + return point.dot(axis) + } else { + return -2 - point.dot(axis) + } +} + /** * Чанк мира * @@ -393,7 +776,7 @@ abstract class Chunk, This : Chunk() + private val collisionChains = ArrayList() fun bakeCollisions() { if (collisionChangeset == changeset) @@ -402,59 +785,138 @@ abstract class Chunk, This : Chunk xSpanSize + ySpanSize) { + depthFirst.markSeen() + aabb = AABB(depthFirst.mins.toDoubleVector(), depthFirst.maxs.toDoubleVector() + Vector2d.POSITIVE_XY) + } else { + sizeFirst.markSeen() + aabb = AABB(sizeFirst.mins.toDoubleVector(), sizeFirst.maxs.toDoubleVector() + Vector2d.POSITIVE_XY) } - last = x - } else { - if (first != null) { - val aabb = AABB( - Vector2d(x = xAdd + first.toDouble(), y = y.toDouble() + yAdd), - Vector2d(x = xAdd + last.toDouble() + 1.0, y = y.toDouble() + 1.0 + yAdd), - ) + collisionChains.add(body.createFixture(FixtureDef( + shape = PolygonShape().also { it.setAsBox(aabb.width / 2.0, aabb.height / 2.0, aabb.centre, 0.0) }, + friction = 0.4, + ))) - collisionCache.add(aabb) + collisionCache.add(aabb + xyAdd) + } + } + } - body.createFixture(FixtureDef( - shape = PolygonShape().also { it.setAsBox(aabb.width / 2.0, aabb.height / 2.0, aabb.centre - pos.firstBlock.toDoubleVector(), 0.0) }, + /* + val seen = BooleanArray(CHUNK_SIZE * CHUNK_SIZE) + + for (y in 0 .. CHUNK_SIZE_FF) { + for (x in 0 .. CHUNK_SIZE_FF) { + if (!seen[x or (y shl CHUNK_SHIFT)] && tiles[x or (y shl CHUNK_SHIFT)] != null) { + val exposed = TileFlooder(tiles, seen, x, y).exposed + + if (exposed.size > 0) { + val vertices = ArrayList() + var centroid = Vector2d.ZERO + + for (pos in exposed) { + centroid += pos.pos + } + + centroid /= exposed.size.toDouble() + + for (pos in exposed) { + if (pos.left) { + vertices.addU(pos.pos + Vector2d(-0.5, -0.5)) + vertices.addU(pos.pos + Vector2d(-0.5, 0.5)) + } + + if (pos.up) { + vertices.addU(pos.pos + Vector2d(-0.5, 0.5)) + vertices.addU(pos.pos + Vector2d(0.5, 0.5)) + } + + if (pos.right) { + vertices.addU(pos.pos + Vector2d(0.5, 0.5)) + vertices.addU(pos.pos + Vector2d(0.5, -0.5)) + } + + if (pos.down) { + vertices.addU(pos.pos + Vector2d(0.5, -0.5)) + vertices.addU(pos.pos + Vector2d(-0.5, -0.5)) + } + } + + for (i in vertices.indices) { + vertices[i] -= centroid + } + + vertices.sortWith { a, b -> + val scoreA = ccwSortScore(a.normalized, Vector2d.POSITIVE_Y) + val scoreB = ccwSortScore(b.normalized, Vector2d.POSITIVE_Y) + + if (scoreA == scoreB) { + return@sortWith 0 + } else if (scoreA > scoreB) { + return@sortWith 1 + } else { + return@sortWith -1 + } + } + + for (i in vertices.indices) { + vertices[i] += centroid + } + + val deflated = ArrayList() + var edge = vertices[0] + deflated.add(edge) + + for (i in 1 until vertices.size) { + if (vertices[i].x == edge.x) { + // expand along X axis + continue + } else if (vertices[i].y == edge.y) { + // expand along Y axis + continue + } + + // completely different direction + + if (deflated.indexOf(vertices[i - 1]) == -1) + deflated.add(vertices[i - 1]) + + if (deflated.indexOf(vertices[i]) == -1) + deflated.add(vertices[i]) + + edge = vertices[i] + } + + collisionChains.add(body.createFixture(FixtureDef( + shape = ChainShape().also { it.createLoop(vertices) }, friction = 0.4, - )) - - first = null + ))) } } } - - if (first != null) { - val aabb = AABB( - Vector2d(x = first.toDouble() + xAdd, y = y.toDouble() + yAdd), - Vector2d(x = last.toDouble() + 1.0 + xAdd, y = y.toDouble() + 1.0 + yAdd), - ) - - collisionCache.add(aabb) - - body.createFixture(FixtureDef( - shape = PolygonShape().also { it.setAsBox(aabb.width / 2.0, aabb.height / 2.0, aabb.centre - pos.firstBlock.toDoubleVector(), 0.0) }, - friction = 0.4, - )) - } - } + }*/ } /** @@ -575,3 +1037,9 @@ abstract class Chunk, This : Chunk AbstractList.addU(e: E) { + if (indexOf(e) == -1) { + add(e) + } +}