Chunk collision improvement test
This commit is contained in:
parent
4a02a0e0de
commit
d9c4b0aee2
@ -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)
|
||||
|
||||
|
@ -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<ChunkTile?>,
|
||||
private val seen: BooleanArray,
|
||||
rootx: Int,
|
||||
rooty: Int
|
||||
) {
|
||||
/**
|
||||
* Tile positions which have at least one face free of neighbours
|
||||
*/
|
||||
val exposed = ArrayList<TileExposure>()
|
||||
|
||||
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<ChunkTile?>,
|
||||
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<ChunkTile?>,
|
||||
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<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
||||
position = pos.firstBlock.toDoubleVector(),
|
||||
))
|
||||
|
||||
private val collisionBoxes = ArrayList<B2Fixture>()
|
||||
private val collisionChains = ArrayList<B2Fixture>()
|
||||
|
||||
fun bakeCollisions() {
|
||||
if (collisionChangeset == changeset)
|
||||
@ -402,59 +785,138 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
||||
collisionChangeset = changeset
|
||||
collisionCache.clear()
|
||||
|
||||
for (box in collisionBoxes) {
|
||||
for (box in collisionChains) {
|
||||
body.destroyFixture(box)
|
||||
}
|
||||
|
||||
collisionBoxes.clear()
|
||||
collisionChains.clear()
|
||||
|
||||
val xAdd = pos.x * CHUNK_SIZEd
|
||||
val yAdd = pos.y * CHUNK_SIZEd
|
||||
val xyAdd = Vector2d(pos.x * CHUNK_SIZEd, pos.y * CHUNK_SIZEd)
|
||||
val seen = BooleanArray(CHUNK_SIZE * CHUNK_SIZE)
|
||||
|
||||
for (y in 0 .. CHUNK_SIZE_FF) {
|
||||
var first: Int? = null
|
||||
var last = 0
|
||||
for (x in 0..CHUNK_SIZE_FF) {
|
||||
if (!seen[x or (y shl CHUNK_SHIFT)] && tiles[x or (y shl CHUNK_SHIFT)] != null) {
|
||||
val depthFirst = RectTileFlooderDepthFirst(tiles, seen, x, y)
|
||||
val sizeFirst = RectTileFlooderSizeFirst(tiles, seen, x, y)
|
||||
val xSpanDepth = depthFirst.maxs.x - depthFirst.mins.x
|
||||
val ySpanDepth = depthFirst.maxs.y - depthFirst.mins.y
|
||||
val xSpanSize = sizeFirst.maxs.x - sizeFirst.mins.x
|
||||
val ySpanSize = sizeFirst.maxs.y - sizeFirst.mins.y
|
||||
|
||||
for (x in 0 .. CHUNK_SIZE_FF) {
|
||||
if (tiles[x or (y shl CHUNK_SHIFT)] != null) {
|
||||
if (first == null) {
|
||||
first = x
|
||||
val aabb: AABB
|
||||
|
||||
if (xSpanDepth + ySpanDepth > 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<Vector2d>()
|
||||
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<Vector2d>()
|
||||
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<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <E> AbstractList<E>.addU(e: E) {
|
||||
if (indexOf(e) == -1) {
|
||||
add(e)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user