From 9215e33e8002bc237d35517bee751514951effbc Mon Sep 17 00:00:00 2001 From: iam Date: Sun, 13 Mar 2022 19:32:18 -0400 Subject: [PATCH] Remove faces and cartesian product (#762) --- .../server/collision/BlockCollision.java | 112 ++++++++++++++++- .../server/collision/BoundingBox.java | 118 ------------------ .../server/collision/CartesianProduct.java | 30 ----- 3 files changed, 109 insertions(+), 151 deletions(-) delete mode 100644 src/main/java/net/minestom/server/collision/CartesianProduct.java diff --git a/src/main/java/net/minestom/server/collision/BlockCollision.java b/src/main/java/net/minestom/server/collision/BlockCollision.java index c89c6f58e..a74d116e4 100644 --- a/src/main/java/net/minestom/server/collision/BlockCollision.java +++ b/src/main/java/net/minestom/server/collision/BlockCollision.java @@ -11,6 +11,113 @@ final class BlockCollision { // Minimum move amount, minimum final velocity private static final double MIN_DELTA = 0.001; + private static Vec[] calculateFaces(Vec queryVec, BoundingBox boundingBox) { + // Add 1 because we start at point 0 + int ceilX = (int) Math.ceil(boundingBox.width()) + 1; + int ceilY = (int) Math.ceil(boundingBox.height()) + 1; + int ceilZ = (int) Math.ceil(boundingBox.depth()) + 1; + + int pointCount = 0; + if (queryVec.x() != 0) pointCount += ceilY * ceilZ; + if (queryVec.y() != 0) pointCount += ceilX * ceilZ; + if (queryVec.z() != 0) pointCount += ceilX * ceilY; + + // Three edge reduction + if (queryVec.x() != 0 && queryVec.y() != 0 && queryVec.z() != 0) { + pointCount -= ceilX + ceilY + ceilZ; + + // inclusion exclusion principle + pointCount++; + } else if (queryVec.x() != 0 && queryVec.y() != 0) { // Two edge reduction + pointCount -= ceilZ; + } else if (queryVec.y() != 0 && queryVec.z() != 0) { // Two edge reduction + pointCount -= ceilX; + } else if (queryVec.x() != 0 && queryVec.z() != 0) { // Two edge reduction + pointCount -= ceilY; + } + + Vec[] facePoints = new Vec[pointCount]; + int insertIndex = 0; + + // X -> Y x Z + if (queryVec.x() != 0) { + int startIOffset = 0, endIOffset = 0, startJOffset = 0, endJOffset = 0; + + // Y handles XY edge + if (queryVec.y() < 0) startJOffset = 1; + if (queryVec.y() > 0) endJOffset = 1; + + // Z handles XZ edge + if (queryVec.z() < 0) startIOffset = 1; + if (queryVec.z() > 0) endIOffset = 1; + + for (int i = startIOffset; i <= Math.ceil(boundingBox.depth()) - endIOffset; ++i) + for (int j = startJOffset; j <= Math.ceil(boundingBox.height()) - endJOffset; ++j) { + double cellI = i; + double cellJ = j; + double cellK = queryVec.x() < 0 ? 0 : boundingBox.width(); + + if (i >= boundingBox.depth()) cellI = boundingBox.depth(); + if (j >= boundingBox.height()) cellJ = boundingBox.height(); + + cellI += boundingBox.minZ(); + cellJ += boundingBox.minY(); + cellK += boundingBox.minX(); + + Vec p = new Vec(cellK, cellJ, cellI); + facePoints[insertIndex++] = p; + } + } + + // Y -> X x Z + if (queryVec.y() != 0) { + int startJOffset = 0, endJOffset = 0; + + // Z handles YZ edge + if (queryVec.z() < 0) startJOffset = 1; + if (queryVec.z() > 0) endJOffset = 1; + + for (int i = startJOffset; i <= Math.ceil(boundingBox.depth()) - endJOffset; ++i) + for (int j = 0; j <= Math.ceil(boundingBox.width()); ++j) { + double cellI = i; + double cellJ = j; + double cellK = queryVec.y() < 0 ? 0 : boundingBox.height(); + + if (i >= boundingBox.depth()) cellI = boundingBox.depth(); + if (j >= boundingBox.width()) cellJ = boundingBox.width(); + + cellI += boundingBox.minZ(); + cellJ += boundingBox.minX(); + cellK += boundingBox.minY(); + + Vec p = new Vec(cellJ, cellK, cellI); + facePoints[insertIndex++] = p; + } + } + + // Z -> X x Y + if (queryVec.z() != 0) { + for (int i = 0; i <= Math.ceil(boundingBox.height()); ++i) + for (int j = 0; j <= Math.ceil(boundingBox.width()); ++j) { + double cellI = i; + double cellJ = j; + double cellK = queryVec.z() < 0 ? 0 : boundingBox.depth(); + + if (i >= boundingBox.height()) cellI = boundingBox.height(); + if (j >= boundingBox.width()) cellJ = boundingBox.width(); + + cellI += boundingBox.minY(); + cellJ += boundingBox.minX(); + cellK += boundingBox.minZ(); + + Vec p = new Vec(cellJ, cellI, cellK); + facePoints[insertIndex++] = p; + } + } + + return facePoints; + } + /** * Moves an entity with physics applied (ie checking against blocks) *

@@ -21,7 +128,6 @@ final class BlockCollision { @NotNull Vec entityVelocity, @NotNull Pos entityPosition, @NotNull Block.Getter getter, @Nullable PhysicsResult lastPhysicsResult) { - final var faces = boundingBox.faces(); Vec remainingMove = entityVelocity; // Allocate once and update values @@ -66,7 +172,7 @@ final class BlockCollision { return new PhysicsResult(entityPosition, Vec.ZERO, false, false, false, false, entityVelocity, null, Block.AIR); // Query faces to get the points needed for collision - Vec[] allFaces = faces.get(new Vec(Math.signum(remainingMove.x()), Math.signum(remainingMove.y()), Math.signum(remainingMove.z()))); + Vec[] allFaces = calculateFaces(new Vec(Math.signum(remainingMove.x()), Math.signum(remainingMove.y()), Math.signum(remainingMove.z())), boundingBox); PhysicsResult res = handlePhysics(boundingBox, remainingMove, entityPosition, getter, allFaces, finalResult); @@ -99,7 +205,7 @@ final class BlockCollision { // If the entity isn't moving, break if (res.newVelocity().isZero()) break; - allFaces = faces.get(new Vec(Math.signum(remainingMove.x()), Math.signum(remainingMove.y()), Math.signum(remainingMove.z()))); + allFaces = calculateFaces(new Vec(Math.signum(remainingMove.x()), Math.signum(remainingMove.y()), Math.signum(remainingMove.z())), boundingBox); res = handlePhysics(boundingBox, res.newVelocity(), res.newPosition(), getter, allFaces, finalResult); } diff --git a/src/main/java/net/minestom/server/collision/BoundingBox.java b/src/main/java/net/minestom/server/collision/BoundingBox.java index 44466b18c..829faa652 100644 --- a/src/main/java/net/minestom/server/collision/BoundingBox.java +++ b/src/main/java/net/minestom/server/collision/BoundingBox.java @@ -7,18 +7,12 @@ import net.minestom.server.entity.Entity; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import java.util.stream.Stream; - /** * See https://wiki.vg/Entity_metadata#Mobs_2 */ public final class BoundingBox implements Shape { private final double width, height, depth; private final Point offset; - private Map faces; BoundingBox(double width, double height, double depth, Point offset) { this.width = width; @@ -140,14 +134,6 @@ public final class BoundingBox implements Shape { return depth; } - @NotNull Map faces() { - Map faces = this.faces; - if (faces == null) { - this.faces = faces = retrieveFaces(); - } - return faces; - } - public double minX() { return relativeStart().x(); } @@ -171,108 +157,4 @@ public final class BoundingBox implements Shape { public double maxZ() { return relativeEnd().z(); } - - private Vec[] buildSet(Collection a) { - return a.toArray(Vec[]::new); - } - - private Vec[] buildSet(Collection a, Collection b) { - Set allFaces = new HashSet<>(); - Stream.of(a, b).forEach(allFaces::addAll); - return allFaces.toArray(Vec[]::new); - } - - private Vec[] buildSet(Collection a, Collection b, Collection c) { - Set allFaces = new HashSet<>(); - Stream.of(a, b, c).forEach(allFaces::addAll); - return allFaces.toArray(Vec[]::new); - } - - private Map retrieveFaces() { - double minX = minX(); - double maxX = maxX(); - double minY = minY(); - double maxY = maxY(); - double minZ = minZ(); - double maxZ = maxZ(); - - // Calculate steppings for each axis - // Start at minimum, increase by step size until we reach maximum - // This is done to catch all blocks that are part of that axis - // Since this stops before max point is reached, we add the max point after - final List stepsX = IntStream.rangeClosed(0, (int) ((maxX - minX))).mapToDouble(x -> x + minX).boxed().collect(Collectors.toCollection(ArrayList::new)); - final List stepsY = IntStream.rangeClosed(0, (int) ((maxY - minY))).mapToDouble(x -> x + minY).boxed().collect(Collectors.toCollection(ArrayList::new)); - final List stepsZ = IntStream.rangeClosed(0, (int) ((maxZ - minZ))).mapToDouble(x -> x + minZ).boxed().collect(Collectors.toCollection(ArrayList::new)); - - stepsX.add(maxX); - stepsY.add(maxY); - stepsZ.add(maxZ); - - final Set bottom = new HashSet<>(); - final Set top = new HashSet<>(); - final Set left = new HashSet<>(); - final Set right = new HashSet<>(); - final Set front = new HashSet<>(); - final Set back = new HashSet<>(); - - CartesianProduct.product(stepsX, stepsY).forEach(cross -> { - double i = (double) ((List) cross).get(0); - double j = (double) ((List) cross).get(1); - front.add(new Vec(i, j, minZ)); - back.add(new Vec(i, j, maxZ)); - }); - - CartesianProduct.product(stepsY, stepsZ).forEach(cross -> { - double j = (double) ((List) cross).get(0); - double k = (double) ((List) cross).get(1); - left.add(new Vec(minX, j, k)); - right.add(new Vec(maxX, j, k)); - }); - - CartesianProduct.product(stepsX, stepsZ).forEach(cross -> { - double i = (double) ((List) cross).get(0); - double k = (double) ((List) cross).get(1); - bottom.add(new Vec(i, minY, k)); - top.add(new Vec(i, maxY, k)); - }); - - // X -1 left | 1 right - // Y -1 bottom | 1 top - // Z -1 front | 1 back - var query = new HashMap(); - query.put(new Vec(0, 0, 0), new Vec[0]); - - query.put(new Vec(-1, 0, 0), buildSet(left)); - query.put(new Vec(1, 0, 0), buildSet(right)); - query.put(new Vec(0, -1, 0), buildSet(bottom)); - query.put(new Vec(0, 1, 0), buildSet(top)); - query.put(new Vec(0, 0, -1), buildSet(front)); - query.put(new Vec(0, 0, 1), buildSet(back)); - - query.put(new Vec(0, -1, -1), buildSet(bottom, front)); - query.put(new Vec(0, -1, 1), buildSet(bottom, back)); - query.put(new Vec(0, 1, -1), buildSet(top, front)); - query.put(new Vec(0, 1, 1), buildSet(top, back)); - - query.put(new Vec(-1, -1, 0), buildSet(left, bottom)); - query.put(new Vec(-1, 1, 0), buildSet(left, top)); - query.put(new Vec(1, -1, 0), buildSet(right, bottom)); - query.put(new Vec(1, 1, 0), buildSet(right, top)); - - query.put(new Vec(-1, 0, -1), buildSet(left, front)); - query.put(new Vec(-1, 0, 1), buildSet(left, back)); - query.put(new Vec(1, 0, -1), buildSet(right, front)); - query.put(new Vec(1, 0, 1), buildSet(right, back)); - - query.put(new Vec(1, 1, 1), buildSet(right, top, back)); - query.put(new Vec(1, 1, -1), buildSet(right, top, front)); - query.put(new Vec(1, -1, 1), buildSet(right, bottom, back)); - query.put(new Vec(1, -1, -1), buildSet(right, bottom, front)); - query.put(new Vec(-1, 1, 1), buildSet(left, top, back)); - query.put(new Vec(-1, 1, -1), buildSet(left, top, front)); - query.put(new Vec(-1, -1, 1), buildSet(left, bottom, back)); - query.put(new Vec(-1, -1, -1), buildSet(left, bottom, front)); - - return query; - } } diff --git a/src/main/java/net/minestom/server/collision/CartesianProduct.java b/src/main/java/net/minestom/server/collision/CartesianProduct.java deleted file mode 100644 index a3ccf331e..000000000 --- a/src/main/java/net/minestom/server/collision/CartesianProduct.java +++ /dev/null @@ -1,30 +0,0 @@ -package net.minestom.server.collision; - -import java.util.List; - -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Optional.of; -import static java.util.stream.Collectors.toList; - -// https://rosettacode.org/wiki/Cartesian_product_of_two_or_more_lists#Java -final class CartesianProduct { - public static List product(List... a) { - if (a.length >= 2) { - List product = a[0]; - for (int i = 1; i < a.length; i++) { - product = product(product, a[i]); - } - return product; - } - - return emptyList(); - } - - private static List product(List a, List b) { - return of(a.stream() - .map(e1 -> of(b.stream().map(e2 -> asList(e1, e2)).collect(toList())).orElse(emptyList())) - .flatMap(List::stream) - .collect(toList())).orElse(emptyList()); - } -}