Remove faces and cartesian product (#762)

This commit is contained in:
iam 2022-03-13 19:32:18 -04:00 committed by GitHub
parent df64ce9653
commit 9215e33e80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 109 additions and 151 deletions

View File

@ -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)
* <p>
@ -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);
}

View File

@ -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<Vec, Vec[]> 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<Vec, Vec[]> faces() {
Map<Vec, Vec[]> 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<Vec> a) {
return a.toArray(Vec[]::new);
}
private Vec[] buildSet(Collection<Vec> a, Collection<Vec> b) {
Set<Vec> allFaces = new HashSet<>();
Stream.of(a, b).forEach(allFaces::addAll);
return allFaces.toArray(Vec[]::new);
}
private Vec[] buildSet(Collection<Vec> a, Collection<Vec> b, Collection<Vec> c) {
Set<Vec> allFaces = new HashSet<>();
Stream.of(a, b, c).forEach(allFaces::addAll);
return allFaces.toArray(Vec[]::new);
}
private Map<Vec, Vec[]> 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<Double> stepsX = IntStream.rangeClosed(0, (int) ((maxX - minX))).mapToDouble(x -> x + minX).boxed().collect(Collectors.toCollection(ArrayList<Double>::new));
final List<Double> stepsY = IntStream.rangeClosed(0, (int) ((maxY - minY))).mapToDouble(x -> x + minY).boxed().collect(Collectors.toCollection(ArrayList<Double>::new));
final List<Double> stepsZ = IntStream.rangeClosed(0, (int) ((maxZ - minZ))).mapToDouble(x -> x + minZ).boxed().collect(Collectors.toCollection(ArrayList<Double>::new));
stepsX.add(maxX);
stepsY.add(maxY);
stepsZ.add(maxZ);
final Set<Vec> bottom = new HashSet<>();
final Set<Vec> top = new HashSet<>();
final Set<Vec> left = new HashSet<>();
final Set<Vec> right = new HashSet<>();
final Set<Vec> front = new HashSet<>();
final Set<Vec> 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<Vec, Vec[]>();
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;
}
}

View File

@ -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 <A, B> List<?> product(List<A> a, List<B> 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());
}
}