mirror of
https://github.com/Minestom/Minestom.git
synced 2025-02-24 00:01:56 +01:00
Remove faces and cartesian product (#762)
This commit is contained in:
parent
df64ce9653
commit
9215e33e80
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user