WorldGeometry.split, little more work on world bindings

This commit is contained in:
DBotThePony 2024-04-19 14:18:05 +07:00
parent 3b11fcf792
commit df329f7087
Signed by: DBot
GPG Key ID: DCC23B5715498507
9 changed files with 221 additions and 28 deletions

View File

@ -154,7 +154,13 @@ fun luaFunctionN(name: String, callable: ExecutionContext.(ArgumentIterator) ->
}
override fun invoke(context: ExecutionContext, args: Array<out Any>) {
callable.invoke(context, ArgumentIterator.of(context, name, args))
try {
callable.invoke(context, ArgumentIterator.of(context, name, args))
} catch (err: ClassCastException) {
throw LuaRuntimeException(err)
} catch (err: NullPointerException) {
throw LuaRuntimeException(err)
}
}
}
}
@ -166,7 +172,13 @@ fun luaFunctionNS(name: String, callable: ExecutionContext.(ArgumentIterator) ->
}
override fun invoke(context: ExecutionContext, args: Array<out Any>) {
callable.invoke(context, ArgumentIterator.of(context, name, args)).run(context)
try {
callable.invoke(context, ArgumentIterator.of(context, name, args)).run(context)
} catch (err: ClassCastException) {
throw LuaRuntimeException(err)
} catch (err: NullPointerException) {
throw LuaRuntimeException(err)
}
}
}
}
@ -178,7 +190,13 @@ fun luaFunctionArray(callable: ExecutionContext.(Array<out Any?>) -> Unit): LuaF
}
override fun invoke(context: ExecutionContext, args: Array<out Any?>) {
callable.invoke(context, args)
try {
callable.invoke(context, args)
} catch (err: ClassCastException) {
throw LuaRuntimeException(err)
} catch (err: NullPointerException) {
throw LuaRuntimeException(err)
}
}
}
}
@ -190,7 +208,13 @@ fun luaFunction(callable: ExecutionContext.() -> Unit): LuaFunction<*, *, *, *,
}
override fun invoke(context: ExecutionContext) {
callable.invoke(context)
try {
callable.invoke(context)
} catch (err: ClassCastException) {
throw LuaRuntimeException(err)
} catch (err: NullPointerException) {
throw LuaRuntimeException(err)
}
}
}
}
@ -202,7 +226,13 @@ fun <T> luaFunction(callable: ExecutionContext.(T) -> Unit): LuaFunction<T, *, *
}
override fun invoke(context: ExecutionContext, arg1: T) {
callable.invoke(context, arg1)
try {
callable.invoke(context, arg1)
} catch (err: ClassCastException) {
throw LuaRuntimeException(err)
} catch (err: NullPointerException) {
throw LuaRuntimeException(err)
}
}
}
}
@ -214,7 +244,13 @@ fun <T, T2> luaFunction(callable: ExecutionContext.(T, T2) -> Unit): LuaFunction
}
override fun invoke(context: ExecutionContext, arg1: T, arg2: T2) {
callable.invoke(context, arg1, arg2)
try {
callable.invoke(context, arg1, arg2)
} catch (err: ClassCastException) {
throw LuaRuntimeException(err)
} catch (err: NullPointerException) {
throw LuaRuntimeException(err)
}
}
}
}
@ -226,7 +262,13 @@ fun <T, T2, T3> luaFunction(callable: ExecutionContext.(T, T2, T3) -> Unit): Lua
}
override fun invoke(context: ExecutionContext, arg1: T, arg2: T2, arg3: T3) {
callable.invoke(context, arg1, arg2, arg3)
try {
callable.invoke(context, arg1, arg2, arg3)
} catch (err: ClassCastException) {
throw LuaRuntimeException(err)
} catch (err: NullPointerException) {
throw LuaRuntimeException(err)
}
}
}
}
@ -238,7 +280,13 @@ fun <T, T2, T3, T4> luaFunction(callable: ExecutionContext.(T, T2, T3, T4) -> Un
}
override fun invoke(context: ExecutionContext, arg1: T, arg2: T2, arg3: T3, arg4: T4) {
callable.invoke(context, arg1, arg2, arg3, arg4)
try {
callable.invoke(context, arg1, arg2, arg3, arg4)
} catch (err: ClassCastException) {
throw LuaRuntimeException(err)
} catch (err: NullPointerException) {
throw LuaRuntimeException(err)
}
}
}
}

View File

@ -1,14 +1,24 @@
package ru.dbotthepony.kstarbound.lua.bindings
import org.classdump.luna.ByteString
import org.classdump.luna.Table
import org.classdump.luna.runtime.ExecutionContext
import ru.dbotthepony.kommons.collect.map
import ru.dbotthepony.kommons.collect.toList
import ru.dbotthepony.kommons.vector.Vector2d
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
import ru.dbotthepony.kstarbound.lua.from
import ru.dbotthepony.kstarbound.lua.get
import ru.dbotthepony.kstarbound.lua.iterator
import ru.dbotthepony.kstarbound.lua.luaFunction
import ru.dbotthepony.kstarbound.lua.set
import ru.dbotthepony.kstarbound.lua.toPoly
import ru.dbotthepony.kstarbound.lua.toVector2d
import ru.dbotthepony.kstarbound.util.valueOf
import ru.dbotthepony.kstarbound.world.World
import ru.dbotthepony.kstarbound.world.physics.CollisionType
import java.util.EnumSet
import java.util.function.Predicate
fun provideWorldBindings(self: World<*, *>, lua: LuaEnvironment) {
val callbacks = lua.newTable()
@ -29,4 +39,39 @@ fun provideWorldBindings(self: World<*, *>, lua: LuaEnvironment) {
callbacks["polyContains"] = luaFunction { poly: Table, position: Table ->
returnBuffer.setTo(self.geometry.polyContains(toPoly(poly), toVector2d(position)))
}
callbacks["xwrap"] = luaFunction { position: Any ->
if (position is Number) {
returnBuffer.setTo(self.geometry.x.cell(position.toDouble()))
} else {
returnBuffer.setTo(from(self.geometry.wrap(toVector2d(position))))
}
}
callbacks["ywrap"] = luaFunction { position: Any ->
if (position is Number) {
returnBuffer.setTo(self.geometry.y.cell(position.toDouble()))
} else {
returnBuffer.setTo(from(self.geometry.wrap(toVector2d(position))))
}
}
callbacks["nearestTo"] = luaFunction { pos0: Any, pos1: Any ->
if (pos1 is Number) {
val source = if (pos0 is Number) pos0.toDouble() else toVector2d(pos0).x
returnBuffer.setTo(self.geometry.x.nearestTo(source, pos1.toDouble()))
} else {
val source = if (pos0 is Number) Vector2d(pos0.toDouble()) else toVector2d(pos0)
returnBuffer.setTo(self.geometry.nearestTo(source, toVector2d(pos1)))
}
}
callbacks["rectCollision"] = luaFunction { rect: Table, collisions: Table? ->
if (collisions == null) {
returnBuffer.setTo(self.collide(toPoly(rect), Predicate { it.type.isSolidCollision }).findAny().isPresent)
} else {
val actualCollisions = EnumSet.copyOf(collisions.iterator().map { CollisionType.entries.valueOf((it.value as ByteString).decode()) }.toList())
returnBuffer.setTo(self.collide(toPoly(rect), Predicate { it.type in actualCollisions }).findAny().isPresent)
}
}
}

View File

@ -68,6 +68,15 @@ data class Line2d(val p0: Vector2d, val p1: Vector2d) {
stream.writeStruct2d(p1, isLegacy)
}
/**
* * if > 0.0 - left side
* * if < 0.0 - right side
* * if = 0.0 - rests upon
*/
fun isLeft(p2: IStruct2d): Double {
return (p1.component1() - p0.component1()) * (p2.component2() - p0.component2()) - (p2.component1() - p0.component1()) * (p1.component2() - p0.component2())
}
// original source of this intersection algorithm:
// https://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
// article "Intersection of two lines in three-space" by Ronald Goldman, published in Graphics Gems, page 304

View File

@ -898,7 +898,7 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
// is trying to achieve, mostly because the style it is written in
// suggests it was written WAY early in game development
var badNodes = HashSet<Vector2i>()
var badNodes = HashSet<Vector2i>(1024)
val candidateNodes = HashSet<Vector2i>()
// this gives a rectangle of this chunk plus all neighbours BUT the very last border cell
@ -974,7 +974,7 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
while (badNodes.isNotEmpty()) {
val oldNodes = badNodes
badNodes = HashSet()
badNodes = HashSet(1024)
// TODO: same as above
for (node in oldNodes) {

View File

@ -1,8 +1,11 @@
package ru.dbotthepony.kstarbound.world
import com.google.common.collect.ImmutableList
import ru.dbotthepony.kommons.vector.Vector2d
import ru.dbotthepony.kommons.vector.Vector2i
import ru.dbotthepony.kstarbound.math.Line2d
import ru.dbotthepony.kstarbound.math.divideUp
import ru.dbotthepony.kstarbound.world.physics.Poly
import kotlin.math.absoluteValue
import kotlin.math.pow
@ -28,6 +31,8 @@ fun positiveModulo(a: Float, b: Int): Float {
abstract class CoordinateMapper {
abstract val chunks: Int
abstract val cells: Int
abstract val cellsD: Double
abstract fun cell(value: Int): Int
abstract fun cell(value: Double): Double
@ -72,9 +77,11 @@ abstract class CoordinateMapper {
abstract fun nearestTo(source: Int, target: Int): Int
abstract fun nearestTo(source: Double, target: Double): Double
class Wrapper(private val cells: Int) : CoordinateMapper() {
abstract val isSplitting: Boolean
class Wrapper(override val cells: Int) : CoordinateMapper() {
override val chunks = divideUp(cells, CHUNK_SIZE)
private val cellsD = cells.toDouble()
override val cellsD = cells.toDouble()
// try to approach reasonable "edge" value, which is just little less than `cells`
private var cellsEdge = cellsD
@ -95,6 +102,9 @@ abstract class CoordinateMapper {
override fun isValidCellIndex(value: Int) = true
override fun isValidChunkIndex(value: Int) = true
override val isSplitting: Boolean
get() = true
@Suppress("NAME_SHADOWING")
override fun diff(a: Int, b: Int): Int {
val a = cell(a)
@ -221,9 +231,9 @@ abstract class CoordinateMapper {
}
}
class Clamper(private val cells: Int) : CoordinateMapper() {
class Clamper(override val cells: Int) : CoordinateMapper() {
override val chunks = divideUp(cells, CHUNK_SIZE)
private val cellsD = cells.toDouble()
override val cellsD = cells.toDouble()
private val cellsF = cells.toFloat()
// try to approach reasonable "edge" value, which is just little less than `cells`
@ -277,6 +287,9 @@ abstract class CoordinateMapper {
return SplitResultDouble(Vector2d(cell(first), cell(last)), null, 0.0)
}
override val isSplitting: Boolean
get() = false
override fun cell(value: Int): Int {
return value.coerceIn(0, cells - 1)
}

View File

@ -12,6 +12,7 @@ import ru.dbotthepony.kommons.util.IStruct2i
import ru.dbotthepony.kommons.vector.Vector2d
import ru.dbotthepony.kommons.vector.Vector2i
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.math.Line2d
import ru.dbotthepony.kstarbound.world.physics.Poly
import java.io.DataInputStream
import java.io.DataOutputStream
@ -242,7 +243,83 @@ data class WorldGeometry(val size: Vector2i, val loopX: Boolean = true, val loop
}
fun split(poly: Poly): List<Poly> {
TODO()
val lines = ObjectArrayList<Pair<Line2d, Pair<Vector2d, Vector2d>>>(4)
if (x.isSplitting) {
lines.add(Line2d(Vector2d.ZERO, Vector2d.POSITIVE_Y) to (Vector2d(x.cellsD, 0.0) to Vector2d.ZERO))
lines.add(Line2d(Vector2d(x.cellsD, 0.0), Vector2d(x.cellsD, 1.0)) to (Vector2d.ZERO to Vector2d(-x.cellsD, 0.0)))
}
if (y.isSplitting) {
lines.add(Line2d(Vector2d.ZERO, Vector2d.POSITIVE_X) to (Vector2d.ZERO to Vector2d(0.0, y.cellsD)))
lines.add(Line2d(Vector2d(0.0, y.cellsD), Vector2d(1.0, y.cellsD)) to (Vector2d(0.0, -y.cellsD) to Vector2d.ZERO))
}
if (lines.isEmpty) {
return listOf(poly)
}
val split = ObjectArrayList<Poly>()
split.add(poly)
for ((line, corrections) in lines) {
val (correctionIfLeft, correctionIfRight) = corrections
val itr = split.listIterator()
for (tpoly in itr) {
val leftPoints = ObjectArrayList<Vector2d>(tpoly.edges.size)
val rightPoints = ObjectArrayList<Vector2d>(tpoly.edges.size)
for (vedge in tpoly.edges) {
val left0 = line.isLeft(vedge.p0)
val left1 = line.isLeft(vedge.p1)
if (left0 >= 0.0 && left1 >= 0.0) {
leftPoints.add(vedge.p0 + correctionIfLeft)
leftPoints.add(vedge.p1 + correctionIfLeft)
} else if (left0 < 0.0 && left1 < 0.0) {
rightPoints.add(vedge.p0 + correctionIfRight)
rightPoints.add(vedge.p1 + correctionIfRight)
} else {
// edge gets split in half by our line
val result = line.intersect(vedge, true)
// check if it is an actual split, if poly points just rest on that line consider they rest on left side
if (result.intersects) {
val point = line.intersect(vedge, true).point.orThrow { RuntimeException() }
if (left0 < 0.0 && left1 >= 0.0) {
leftPoints.add(vedge.p1 + correctionIfLeft)
rightPoints.add(vedge.p0 + correctionIfRight)
} else {
leftPoints.add(vedge.p0 + correctionIfLeft)
rightPoints.add(vedge.p1 + correctionIfRight)
}
leftPoints.add(point + correctionIfLeft)
rightPoints.add(point + correctionIfRight)
} else {
// dang it
leftPoints.add(vedge.p0 + correctionIfLeft)
leftPoints.add(vedge.p1 + correctionIfLeft)
}
}
}
itr.remove()
if (leftPoints.isEmpty) {
itr.add(Poly.quickhull(rightPoints))
} else if (rightPoints.isEmpty) {
itr.add(Poly.quickhull(leftPoints))
} else {
itr.add(Poly.quickhull(leftPoints))
itr.add(Poly.quickhull(rightPoints))
}
}
}
return split
}
fun polyContains(poly: Poly, point: IStruct2d): Boolean {

View File

@ -1,15 +1,17 @@
package ru.dbotthepony.kstarbound.world.physics
enum class CollisionType(val isEmpty: Boolean, val isSolidCollision: Boolean, val isTileCollision: Boolean) {
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
enum class CollisionType(override val jsonName: String, val isEmpty: Boolean, val isSolidCollision: Boolean, val isTileCollision: Boolean) : IStringSerializable {
// not loaded, block collisions by default
NULL(true, true, false),
NULL("Null", true, true, false),
// air
NONE(true, false, false),
NONE("None", true, false, false),
// including stairs made of platforms
PLATFORM(false, false, false),
DYNAMIC(false, true, false),
SLIPPERY(false, true, true),
BLOCK(false, true, true);
PLATFORM("Platform", false, false, false),
DYNAMIC("Dynamic", false, true, false),
SLIPPERY("Slippery", false, true, true),
BLOCK("Block", false, true, true);
fun maxOf(other: CollisionType): CollisionType {
if (this === NULL || other === NULL)

View File

@ -71,10 +71,6 @@ private fun calculateEdges(points: List<Vector2d>): Pair<ImmutableList<Line2d>,
}
}
private fun isLeft(p0: IStruct2d, p1: IStruct2d, p2: IStruct2d): Double {
return (p1.component1() - p0.component1()) * (p2.component2() - p0.component2()) - (p2.component1() - p0.component1()) * (p1.component2() - p0.component2())
}
private fun rotate(point: Vector2d, sin: Double, cos: Double): Vector2d {
return Vector2d(
point.x * cos + point.y * sin,
@ -230,7 +226,7 @@ class Poly private constructor(val edges: ImmutableList<Line2d>, val vertices: I
if (edge.p0.y <= y) {
if (edge.p1.y > y) {
// an upward crossing
if (isLeft(edge.p0, edge.p1, point) > 0.0) {
if (edge.isLeft(point) > 0.0) {
// p left of edge
// have a valid up intersect
++wn
@ -240,7 +236,7 @@ class Poly private constructor(val edges: ImmutableList<Line2d>, val vertices: I
// start y > p[1] (no test needed)
if (edge.p1.y <= y) {
// a downward crossing
if (isLeft(edge.p0, edge.p1, point) < 0.0) {
if (edge.isLeft(point) < 0.0) {
// p right of edge
// have a valid down intersect
--wn

View File

@ -3,12 +3,15 @@ package ru.dbotthepony.kstarbound.test
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import ru.dbotthepony.kommons.util.AABB
import ru.dbotthepony.kommons.vector.Vector2d
import ru.dbotthepony.kommons.vector.Vector2i
import ru.dbotthepony.kstarbound.math.*
import ru.dbotthepony.kstarbound.util.random.random
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_FF
import ru.dbotthepony.kstarbound.world.ChunkPos
import ru.dbotthepony.kstarbound.world.WorldGeometry
import ru.dbotthepony.kstarbound.world.physics.Poly
import kotlin.math.PI
import kotlin.math.cos