Merge pull request #356 from Kebab11noel/fix/update-collision

Collision update
This commit is contained in:
TheMode 2021-07-07 01:25:54 +02:00 committed by GitHub
commit b00131c525
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 173 additions and 179 deletions

View File

@ -2,8 +2,8 @@ package net.minestom.server.collision;
import net.minestom.server.entity.Entity; import net.minestom.server.entity.Entity;
import net.minestom.server.utils.BlockPosition; import net.minestom.server.utils.BlockPosition;
import net.minestom.server.utils.Vector;
import net.minestom.server.utils.coordinate.Point; import net.minestom.server.utils.coordinate.Point;
import net.minestom.server.utils.coordinate.Vec;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
/** /**
@ -199,92 +199,92 @@ public class BoundingBox {
} }
/** /**
* Gets an array of {@link Vector} representing the points at the bottom of the {@link BoundingBox}. * Gets an array of {@link Vec} representing the points at the bottom of the {@link BoundingBox}.
* *
* @return the points at the bottom of the {@link BoundingBox} * @return the points at the bottom of the {@link BoundingBox}
*/ */
@NotNull @NotNull
public Vector[] getBottomFace() { public Vec[] getBottomFace() {
return new Vector[]{ return new Vec[]{
new Vector(getMinX(), getMinY(), getMinZ()), new Vec(getMinX(), getMinY(), getMinZ()),
new Vector(getMaxX(), getMinY(), getMinZ()), new Vec(getMaxX(), getMinY(), getMinZ()),
new Vector(getMaxX(), getMinY(), getMaxZ()), new Vec(getMaxX(), getMinY(), getMaxZ()),
new Vector(getMinX(), getMinY(), getMaxZ()), new Vec(getMinX(), getMinY(), getMaxZ()),
}; };
} }
/** /**
* Gets an array of {@link Vector} representing the points at the top of the {@link BoundingBox}. * Gets an array of {@link Vec} representing the points at the top of the {@link BoundingBox}.
* *
* @return the points at the top of the {@link BoundingBox} * @return the points at the top of the {@link BoundingBox}
*/ */
@NotNull @NotNull
public Vector[] getTopFace() { public Vec[] getTopFace() {
return new Vector[]{ return new Vec[]{
new Vector(getMinX(), getMaxY(), getMinZ()), new Vec(getMinX(), getMaxY(), getMinZ()),
new Vector(getMaxX(), getMaxY(), getMinZ()), new Vec(getMaxX(), getMaxY(), getMinZ()),
new Vector(getMaxX(), getMaxY(), getMaxZ()), new Vec(getMaxX(), getMaxY(), getMaxZ()),
new Vector(getMinX(), getMaxY(), getMaxZ()), new Vec(getMinX(), getMaxY(), getMaxZ()),
}; };
} }
/** /**
* Gets an array of {@link Vector} representing the points on the left face of the {@link BoundingBox}. * Gets an array of {@link Vec} representing the points on the left face of the {@link BoundingBox}.
* *
* @return the points on the left face of the {@link BoundingBox} * @return the points on the left face of the {@link BoundingBox}
*/ */
@NotNull @NotNull
public Vector[] getLeftFace() { public Vec[] getLeftFace() {
return new Vector[]{ return new Vec[]{
new Vector(getMinX(), getMinY(), getMinZ()), new Vec(getMinX(), getMinY(), getMinZ()),
new Vector(getMinX(), getMaxY(), getMinZ()), new Vec(getMinX(), getMaxY(), getMinZ()),
new Vector(getMinX(), getMaxY(), getMaxZ()), new Vec(getMinX(), getMaxY(), getMaxZ()),
new Vector(getMinX(), getMinY(), getMaxZ()), new Vec(getMinX(), getMinY(), getMaxZ()),
}; };
} }
/** /**
* Gets an array of {@link Vector} representing the points on the right face of the {@link BoundingBox}. * Gets an array of {@link Vec} representing the points on the right face of the {@link BoundingBox}.
* *
* @return the points on the right face of the {@link BoundingBox} * @return the points on the right face of the {@link BoundingBox}
*/ */
@NotNull @NotNull
public Vector[] getRightFace() { public Vec[] getRightFace() {
return new Vector[]{ return new Vec[]{
new Vector(getMaxX(), getMinY(), getMinZ()), new Vec(getMaxX(), getMinY(), getMinZ()),
new Vector(getMaxX(), getMaxY(), getMinZ()), new Vec(getMaxX(), getMaxY(), getMinZ()),
new Vector(getMaxX(), getMaxY(), getMaxZ()), new Vec(getMaxX(), getMaxY(), getMaxZ()),
new Vector(getMaxX(), getMinY(), getMaxZ()), new Vec(getMaxX(), getMinY(), getMaxZ()),
}; };
} }
/** /**
* Gets an array of {@link Vector} representing the points at the front of the {@link BoundingBox}. * Gets an array of {@link Vec} representing the points at the front of the {@link BoundingBox}.
* *
* @return the points at the front of the {@link BoundingBox} * @return the points at the front of the {@link BoundingBox}
*/ */
@NotNull @NotNull
public Vector[] getFrontFace() { public Vec[] getFrontFace() {
return new Vector[]{ return new Vec[]{
new Vector(getMinX(), getMinY(), getMinZ()), new Vec(getMinX(), getMinY(), getMinZ()),
new Vector(getMaxX(), getMinY(), getMinZ()), new Vec(getMaxX(), getMinY(), getMinZ()),
new Vector(getMaxX(), getMaxY(), getMinZ()), new Vec(getMaxX(), getMaxY(), getMinZ()),
new Vector(getMinX(), getMaxY(), getMinZ()), new Vec(getMinX(), getMaxY(), getMinZ()),
}; };
} }
/** /**
* Gets an array of {@link Vector} representing the points at the back of the {@link BoundingBox}. * Gets an array of {@link Vec} representing the points at the back of the {@link BoundingBox}.
* *
* @return the points at the back of the {@link BoundingBox} * @return the points at the back of the {@link BoundingBox}
*/ */
@NotNull @NotNull
public Vector[] getBackFace() { public Vec[] getBackFace() {
return new Vector[]{ return new Vec[]{
new Vector(getMinX(), getMinY(), getMaxZ()), new Vec(getMinX(), getMinY(), getMaxZ()),
new Vector(getMaxX(), getMinY(), getMaxZ()), new Vec(getMaxX(), getMinY(), getMaxZ()),
new Vector(getMaxX(), getMaxY(), getMaxZ()), new Vec(getMaxX(), getMaxY(), getMaxZ()),
new Vector(getMinX(), getMaxY(), getMaxZ()), new Vec(getMinX(), getMaxY(), getMaxZ()),
}; };
} }

View File

@ -5,67 +5,46 @@ import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.Instance; import net.minestom.server.instance.Instance;
import net.minestom.server.instance.WorldBorder; import net.minestom.server.instance.WorldBorder;
import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.Block;
import net.minestom.server.utils.BlockPosition;
import net.minestom.server.utils.Position;
import net.minestom.server.utils.Vector;
import net.minestom.server.utils.chunk.ChunkUtils; import net.minestom.server.utils.chunk.ChunkUtils;
import net.minestom.server.utils.coordinate.Point; import net.minestom.server.utils.coordinate.Point;
import net.minestom.server.utils.coordinate.Pos;
import net.minestom.server.utils.coordinate.Vec; import net.minestom.server.utils.coordinate.Vec;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
public class CollisionUtils { public class CollisionUtils {
private static final Vector Y_AXIS = new Vector(0, 1, 0); private static final Vec Y_AXIS = new Vec(0, 1, 0);
private static final Vector X_AXIS = new Vector(1, 0, 0); private static final Vec X_AXIS = new Vec(1, 0, 0);
private static final Vector Z_AXIS = new Vector(0, 0, 1); private static final Vec Z_AXIS = new Vec(0, 0, 1);
/** /**
* Moves an entity with physics applied (ie checking against blocks) * Moves an entity with physics applied (ie checking against blocks)
* *
* @param entity the entity to move * @param entity the entity to move
* @param deltaPosition * @return the result of physics simulation
* @param positionOut the Position object in which the new position will be saved
* @param velocityOut the Vector object in which the new velocity will be saved
* @return whether this entity is on the ground
*/ */
public static boolean handlePhysics(@NotNull Entity entity, public static PhysicsResult handlePhysics(@NotNull Entity entity, @NotNull Vec deltaPosition) {
@NotNull Vector deltaPosition,
@NotNull Position positionOut,
@NotNull Vector velocityOut) {
// TODO handle collisions with nearby entities (should it be done here?) // TODO handle collisions with nearby entities (should it be done here?)
final Instance instance = entity.getInstance(); final Instance instance = entity.getInstance();
final Chunk originChunk = entity.getChunk(); final Chunk originChunk = entity.getChunk();
final Position currentPosition = entity.getPosition(); final Pos currentPosition = entity.getPosition();
final BoundingBox boundingBox = entity.getBoundingBox(); final BoundingBox boundingBox = entity.getBoundingBox();
Vector intermediaryPosition = new Vector(); final StepResult yCollision = stepAxis(instance, originChunk, currentPosition.asVec(), Y_AXIS, deltaPosition.y(),
boolean yCollision = stepAxis(instance, originChunk, currentPosition.toVector(), Y_AXIS, deltaPosition.getY(), deltaPosition.y() > 0 ? boundingBox.getTopFace() : boundingBox.getBottomFace());
intermediaryPosition,
deltaPosition.getY() > 0 ? boundingBox.getTopFace() : boundingBox.getBottomFace());
boolean xCollision = stepAxis(instance, originChunk, intermediaryPosition, X_AXIS, deltaPosition.getX(), final StepResult xCollision = stepAxis(instance, originChunk, yCollision.newPosition, X_AXIS, deltaPosition.x(),
intermediaryPosition, deltaPosition.x() < 0 ? boundingBox.getLeftFace() : boundingBox.getRightFace());
deltaPosition.getX() < 0 ? boundingBox.getLeftFace() : boundingBox.getRightFace());
boolean zCollision = stepAxis(instance, originChunk, intermediaryPosition, Z_AXIS, deltaPosition.getZ(), final StepResult zCollision = stepAxis(instance, originChunk, xCollision.newPosition, Z_AXIS, deltaPosition.z(),
intermediaryPosition, deltaPosition.z() > 0 ? boundingBox.getBackFace() : boundingBox.getFrontFace());
deltaPosition.getZ() > 0 ? boundingBox.getBackFace() : boundingBox.getFrontFace());
positionOut.setX(intermediaryPosition.getX()); return new PhysicsResult(currentPosition.withCoord(zCollision.newPosition),
positionOut.setY(intermediaryPosition.getY()); deltaPosition.with(((x, y, z) -> new Vec(
positionOut.setZ(intermediaryPosition.getZ()); xCollision.foundCollision ? 0 : x,
velocityOut.copy(deltaPosition); yCollision.foundCollision ? 0 : y,
if (xCollision) { zCollision.foundCollision ? 0 : z
velocityOut.setX(0f); ))), yCollision.foundCollision && deltaPosition.y() < 0);
}
if (yCollision) {
velocityOut.setY(0f);
}
if (zCollision) {
velocityOut.setZ(0f);
}
return yCollision && deltaPosition.getY() < 0;
} }
/** /**
@ -76,111 +55,82 @@ public class CollisionUtils {
* @param startPosition starting position for stepping, can be intermediary position from last step * @param startPosition starting position for stepping, can be intermediary position from last step
* @param axis step direction. Works best if unit vector and aligned to an axis * @param axis step direction. Works best if unit vector and aligned to an axis
* @param stepAmount how much to step in the direction (in blocks) * @param stepAmount how much to step in the direction (in blocks)
* @param positionOut the vector in which to store the new position
* @param corners the corners to check against * @param corners the corners to check against
* @return true if a collision has been found * @return result of the step
*/ */
private static boolean stepAxis(Instance instance, private static StepResult stepAxis(Instance instance, Chunk originChunk, Vec startPosition, Vec axis, double stepAmount, Vec... corners) {
Chunk originChunk,
Vector startPosition, Vector axis,
double stepAmount, Vector positionOut,
Vector... corners) {
positionOut.copy(startPosition);
if (corners.length == 0) if (corners.length == 0)
return false; // avoid degeneracy in following computations return new StepResult(startPosition, false); // avoid degeneracy in following computations
// perform copies to allow in place modifications
// prevents making a lot of new objects. Well at least it reduces the count
BlockPosition[] cornerPositions = new BlockPosition[corners.length];
Vector[] cornersCopy = new Vector[corners.length];
for (int i = 0; i < corners.length; i++) {
cornersCopy[i] = corners[i].clone();
cornerPositions[i] = new BlockPosition(corners[i]);
}
final Vec[] originalCorners = corners.clone();
final double sign = Math.signum(stepAmount); final double sign = Math.signum(stepAmount);
final int blockLength = (int) stepAmount; final int blockLength = (int) stepAmount;
final double remainingLength = stepAmount - blockLength; final double remainingLength = stepAmount - blockLength;
// used to determine if 'remainingLength' should be used // used to determine if 'remainingLength' should be used
boolean collisionFound = false; boolean collisionFound = false;
for (int i = 0; i < Math.abs(blockLength); i++) { for (int i = 0; i < Math.abs(blockLength); i++) {
if (!stepOnce(instance, originChunk, axis, sign, cornersCopy, cornerPositions)) { final OneStepResult oneStepResult = stepOnce(instance, originChunk, axis, sign, corners);
collisionFound = true; corners = oneStepResult.newCorners;
} if (collisionFound = oneStepResult.foundCollision) break;
if (collisionFound) {
break;
}
} }
// add remainingLength // add remainingLength
if (!collisionFound) { if (!collisionFound) {
Vector direction = new Vector(); final OneStepResult oneStepResult = stepOnce(instance, originChunk, axis, remainingLength, corners);
direction.copy(axis); corners = oneStepResult.newCorners;
collisionFound = !stepOnce(instance, originChunk, direction, remainingLength, cornersCopy, cornerPositions); collisionFound = oneStepResult.foundCollision;
} }
// find the corner which moved the least // find the corner which moved the least
double smallestDisplacement = Double.POSITIVE_INFINITY; double smallestDisplacement = Double.POSITIVE_INFINITY;
for (int i = 0; i < corners.length; i++) { for (int i = 0; i < corners.length; i++) {
final double displacement = corners[i].distance(cornersCopy[i]); final double displacement = originalCorners[i].distance(corners[i]);
if (displacement < smallestDisplacement) { if (displacement < smallestDisplacement) {
smallestDisplacement = displacement; smallestDisplacement = displacement;
} }
} }
positionOut.copy(startPosition); return new StepResult(startPosition.add(new Vec(smallestDisplacement).mul(axis).mul(sign)), collisionFound);
positionOut.add(smallestDisplacement * axis.getX() * sign, smallestDisplacement * axis.getY() * sign, smallestDisplacement * axis.getZ() * sign);
return collisionFound;
} }
/** /**
* Steps once (by a length of 1 block) on the given axis. * Steps once (by a length of 1 block) on the given axis.
* *
* @param instance instance to get blocks from * @param instance instance to get blocks from
* @param axis the axis to move along * @param axis the axis to move along
* @param amount * @param corners the corners of the bounding box to consider
* @param cornersCopy the corners of the bounding box to consider (mutable) * @return the result of one step
* @param cornerPositions the corners, converted to BlockPosition (mutable)
* @return false if this method encountered a collision
*/ */
private static boolean stepOnce(Instance instance, private static OneStepResult stepOnce(Instance instance, Chunk originChunk, Vec axis, double amount, Vec[] corners) {
Chunk originChunk,
Vector axis, double amount, Vector[] cornersCopy, BlockPosition[] cornerPositions) {
final double sign = Math.signum(amount); final double sign = Math.signum(amount);
for (int cornerIndex = 0; cornerIndex < cornersCopy.length; cornerIndex++) { Vec[] newCorners = new Vec[corners.length];
Vector corner = cornersCopy[cornerIndex]; for (int cornerIndex = 0; cornerIndex < corners.length; cornerIndex++) {
BlockPosition blockPos = cornerPositions[cornerIndex]; final Vec originalCorner = corners[cornerIndex];
corner.add(axis.getX() * amount, axis.getY() * amount, axis.getZ() * amount); final Vec corner = originalCorner.add(axis.mul(amount));
blockPos.setX((int) Math.floor(corner.getX()));
blockPos.setY((int) Math.floor(corner.getY()));
blockPos.setZ((int) Math.floor(corner.getZ()));
Chunk chunk = ChunkUtils.retrieve(instance, originChunk, blockPos); Chunk chunk = ChunkUtils.retrieve(instance, originChunk, corner);
if (!ChunkUtils.isLoaded(chunk)) { if (!ChunkUtils.isLoaded(chunk)) {
// Collision at chunk border // Collision at chunk border
return false; return new OneStepResult(corners, true);
} }
final Block block = chunk.getBlock(blockPos); final Block block = chunk.getBlock(corner);
// TODO: block collision boxes // TODO: block collision boxes
// TODO: for the moment, always consider a full block // TODO: for the moment, always consider a full block
if (block.isSolid()) { if (block.isSolid()) {
corner.subtract(axis.getX() * amount, axis.getY() * amount, axis.getZ() * amount); newCorners[cornerIndex] = originalCorner.with(((x, y, z) -> new Vec(
Math.abs(axis.x()) > 10e-16 ? originalCorner.blockX() - axis.x() * sign : x,
Math.abs(axis.y()) > 10e-16 ? originalCorner.blockY() - axis.y() * sign : y,
Math.abs(axis.z()) > 10e-16 ? originalCorner.blockZ() - axis.z() * sign : z
)));
if (Math.abs(axis.getX()) > 10e-16) { return new OneStepResult(newCorners, true);
corner.setX(blockPos.getX() - axis.getX() * sign);
}
if (Math.abs(axis.getY()) > 10e-16) {
corner.setY(blockPos.getY() - axis.getY() * sign);
}
if (Math.abs(axis.getZ()) > 10e-16) {
corner.setZ(blockPos.getZ() - axis.getZ() * sign);
}
return false;
} }
newCorners[cornerIndex] = corner;
} }
return true; return new OneStepResult(newCorners, false);
} }
/** /**
@ -213,4 +163,47 @@ public class CollisionUtils {
throw new IllegalStateException("Something weird happened..."); throw new IllegalStateException("Something weird happened...");
} }
public static class PhysicsResult {
private final Pos newPosition;
private final Vec newVelocity;
private final boolean isOnGround;
public PhysicsResult(Pos newPosition, Vec newVelocity, boolean isOnGround) {
this.newPosition = newPosition;
this.newVelocity = newVelocity;
this.isOnGround = isOnGround;
}
public Pos newPosition() {
return newPosition;
}
public Vec newVelocity() {
return newVelocity;
}
public boolean isOnGround() {
return isOnGround;
}
}
private static class StepResult {
private final Vec newPosition;
private final boolean foundCollision;
public StepResult(Vec newPosition, boolean foundCollision) {
this.newPosition = newPosition;
this.foundCollision = foundCollision;
}
}
private static class OneStepResult {
private final Vec[] newCorners;
private final boolean foundCollision;
public OneStepResult(Vec[] newCorners, boolean foundCollision) {
this.newCorners = newCorners;
this.foundCollision = foundCollision;
}
}
} }

View File

@ -38,7 +38,6 @@ import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagHandler; import net.minestom.server.tag.TagHandler;
import net.minestom.server.thread.ThreadProvider; import net.minestom.server.thread.ThreadProvider;
import net.minestom.server.utils.Position; import net.minestom.server.utils.Position;
import net.minestom.server.utils.Vector;
import net.minestom.server.utils.callback.OptionalCallback; import net.minestom.server.utils.callback.OptionalCallback;
import net.minestom.server.utils.chunk.ChunkCallback; import net.minestom.server.utils.chunk.ChunkCallback;
import net.minestom.server.utils.chunk.ChunkUtils; import net.minestom.server.utils.chunk.ChunkUtils;
@ -508,26 +507,30 @@ public class Entity implements Viewable, Tickable, EventHandler<EntityEvent>, Da
if (applyVelocity) { if (applyVelocity) {
final float tps = MinecraftServer.TICK_PER_SECOND; final float tps = MinecraftServer.TICK_PER_SECOND;
final double newX = position.x() + velocity.x() / tps; final Pos newPosition;
final double newY = position.y() + velocity.y() / tps; final Vec newVelocity;
final double newZ = position.z() + velocity.z() / tps;
Position newPosition = new Position(newX, newY, newZ);
Vector newVelocityOut = new Vector();
// Gravity force // Gravity force
final double gravityY = hasNoGravity() ? 0 : gravityAcceleration; final double gravityY = hasNoGravity() ? 0 : gravityAcceleration;
final Vector deltaPos = new Vector( final Vec deltaPos = new Vec(
getVelocity().getX() / tps, getVelocity().x() / tps,
getVelocity().getY() / tps - gravityY, getVelocity().y() / tps - gravityY,
getVelocity().getZ() / tps getVelocity().z() / tps
); );
if (this.hasPhysics) { if (this.hasPhysics) {
this.onGround = CollisionUtils.handlePhysics(this, deltaPos, newPosition, newVelocityOut); final CollisionUtils.PhysicsResult physicsResult = CollisionUtils.handlePhysics(this, deltaPos);
this.onGround = physicsResult.isOnGround();
newPosition = physicsResult.newPosition();
newVelocity = physicsResult.newVelocity();
} else { } else {
newVelocityOut = deltaPos; newVelocity = deltaPos;
newPosition = new Pos(
position.x() + velocity.x() / tps,
position.y() + velocity.y() / tps,
position.z() + velocity.z() / tps
);
} }
// World border collision // World border collision
@ -545,27 +548,23 @@ public class Entity implements Viewable, Tickable, EventHandler<EntityEvent>, Da
} }
// Update velocity // Update velocity
if (hasVelocity() || !newVelocityOut.isZero()) { if (hasVelocity() || !newVelocity.equals(Vec.ZERO)) {
this.velocity.copy(newVelocityOut); velocity = newVelocity.mul(tps); // Convert from blocks/tick to blocks/sec
this.velocity.multiply(tps);
final Block block = finalChunk.getBlock(position); final Block block = finalChunk.getBlock(position);
final double drag = block.registry().friction(); final double drag = block.registry().friction();
if (onGround) { if (onGround) {
// Stop player velocity // Stop player velocity
if (isNettyClient) { if (isNettyClient) {
this.velocity.zero(); velocity = Vec.ZERO;
} }
} }
this.velocity.setX(velocity.getX() * drag); velocity = velocity.with((x, y, z) -> new Vec(
this.velocity.setZ(velocity.getZ() * drag); x * drag,
if (!hasNoGravity()) !hasNoGravity() ? y * (1 - gravityDragPerTick) : y,
this.velocity.setY(velocity.getY() * (1 - gravityDragPerTick)); z * drag
)).with(Vec.Operator.EPSILON);
if (velocity.equals(new Vector())) {
this.velocity.zero();
}
} }
// Synchronization and packets... // Synchronization and packets...

View File

@ -9,8 +9,6 @@ import net.minestom.server.entity.LivingEntity;
import net.minestom.server.instance.Chunk; import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.Instance; import net.minestom.server.instance.Instance;
import net.minestom.server.instance.WorldBorder; import net.minestom.server.instance.WorldBorder;
import net.minestom.server.utils.Position;
import net.minestom.server.utils.Vector;
import net.minestom.server.utils.chunk.ChunkUtils; import net.minestom.server.utils.chunk.ChunkUtils;
import net.minestom.server.utils.coordinate.Point; import net.minestom.server.utils.coordinate.Point;
import net.minestom.server.utils.coordinate.Pos; import net.minestom.server.utils.coordinate.Pos;
@ -75,16 +73,11 @@ public class Navigator {
// Update 'position' view // Update 'position' view
PositionUtils.lookAlong(position, dx, direction.y(), dz); PositionUtils.lookAlong(position, dx, direction.y(), dz);
Position newPosition = new Position();
Vector newVelocityOut = new Vector();
// Prevent ghosting // Prevent ghosting
CollisionUtils.handlePhysics(entity, final CollisionUtils.PhysicsResult physicsResult = CollisionUtils.handlePhysics(entity, new Vec(speedX, speedY, speedZ));
new Vector(speedX, speedY, speedZ),
newPosition, newVelocityOut);
// Will move the entity during Entity#tick // Will move the entity during Entity#tick
position.copyCoordinates(newPosition); entity.teleport(physicsResult.newPosition());
} }
public void jump(float height) { public void jump(float height) {

View File

@ -392,6 +392,15 @@ public final class Vec implements Point {
@FunctionalInterface @FunctionalInterface
public interface Operator { public interface Operator {
/**
* Checks each axis' value, if it's below {@code 1E-6} then it gets replaced with {@code 0}
*/
Operator EPSILON = (x, y, z) -> new Vec(
Math.abs(x) < 1E-6 ? 0 : x,
Math.abs(y) < 1E-6 ? 0 : y,
Math.abs(z) < 1E-6 ? 0 : z
);
@NotNull Vec apply(double x, double y, double z); @NotNull Vec apply(double x, double y, double z);
} }