From df329f7087589525734464a72cc26110a96e1686 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Fri, 19 Apr 2024 14:18:05 +0700 Subject: [PATCH] WorldGeometry.split, little more work on world bindings --- .../dbotthepony/kstarbound/lua/Functions.kt | 64 +++++++++++++-- .../kstarbound/lua/bindings/WorldBindings.kt | 45 +++++++++++ .../ru/dbotthepony/kstarbound/math/Line2d.kt | 9 +++ .../kstarbound/server/world/ServerChunk.kt | 4 +- .../kstarbound/world/CoordinateMapper.kt | 21 ++++- .../kstarbound/world/WorldGeometry.kt | 79 ++++++++++++++++++- .../kstarbound/world/physics/CollisionType.kt | 16 ++-- .../kstarbound/world/physics/Poly.kt | 8 +- .../dbotthepony/kstarbound/test/MathTests.kt | 3 + 9 files changed, 221 insertions(+), 28 deletions(-) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Functions.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Functions.kt index a222af64..5111d088 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Functions.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Functions.kt @@ -154,7 +154,13 @@ fun luaFunctionN(name: String, callable: ExecutionContext.(ArgumentIterator) -> } override fun invoke(context: ExecutionContext, args: Array) { - 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) { - 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) -> Unit): LuaF } override fun invoke(context: ExecutionContext, args: Array) { - 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 luaFunction(callable: ExecutionContext.(T) -> Unit): LuaFunction 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 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 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) + } } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/WorldBindings.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/WorldBindings.kt index 2b8f9c6e..31447bb8 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/WorldBindings.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/WorldBindings.kt @@ -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) + } + } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/math/Line2d.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/math/Line2d.kt index c9f4f2ee..1bbd380d 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/math/Line2d.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/math/Line2d.kt @@ -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 diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerChunk.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerChunk.kt index dd081b4a..be28110f 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerChunk.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerChunk.kt @@ -898,7 +898,7 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk() + var badNodes = HashSet(1024) val candidateNodes = HashSet() // 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 { - TODO() + val lines = ObjectArrayList>>(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() + split.add(poly) + + for ((line, corrections) in lines) { + val (correctionIfLeft, correctionIfRight) = corrections + val itr = split.listIterator() + + for (tpoly in itr) { + val leftPoints = ObjectArrayList(tpoly.edges.size) + val rightPoints = ObjectArrayList(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 { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/physics/CollisionType.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/physics/CollisionType.kt index dfa7f036..66312e0c 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/physics/CollisionType.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/physics/CollisionType.kt @@ -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) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/physics/Poly.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/physics/Poly.kt index a3621f4a..1110bc2b 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/physics/Poly.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/physics/Poly.kt @@ -71,10 +71,6 @@ private fun calculateEdges(points: List): Pair, } } -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, 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, 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 diff --git a/src/test/kotlin/ru/dbotthepony/kstarbound/test/MathTests.kt b/src/test/kotlin/ru/dbotthepony/kstarbound/test/MathTests.kt index c4a3bcf2..5967b15f 100644 --- a/src/test/kotlin/ru/dbotthepony/kstarbound/test/MathTests.kt +++ b/src/test/kotlin/ru/dbotthepony/kstarbound/test/MathTests.kt @@ -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