package com.hypixel.hytale.math.hitdetection;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.math.matrix.Matrix4d;
import com.hypixel.hytale.math.shape.Quad4d;
import com.hypixel.hytale.math.shape.Triangle4d;
import com.hypixel.hytale.math.vector.Vector4d;
import java.util.Arrays;
import java.util.logging.Level;
import javax.annotation.Nonnull;
public class HitDetectionExecutor {
public static final HytaleLogger log = HytaleLogger.forEnclosingClass();
private static final Vector4d[] VERTEX_POINTS = new Vector4d[]{Vector4d.newPosition(0.0, 1.0, 1.0), Vector4d.newPosition(0.0, 1.0, 0.0), Vector4d.newPosition(1.0, 1.0, 1.0), Vector4d.newPosition(1.0, 1.0, 0.0), Vector4d.newPosition(0.0, 0.0, 1.0), Vector4d.newPosition(0.0, 0.0, 0.0), Vector4d.newPosition(1.0, 0.0, 1.0), Vector4d.newPosition(1.0, 0.0, 0.0)};
public static final Quad4d[] CUBE_QUADS;
@Nonnull
private final Matrix4d pvmMatrix = new Matrix4d();
@Nonnull
private final Matrix4d invPvMatrix = new Matrix4d();
@Nonnull
private final Vector4d origin = new Vector4d();
@Nonnull
private final HitDetectionBuffer buffer = new HitDetectionBuffer();
private MatrixProvider projectionProvider;
private MatrixProvider viewProvider;
private LineOfSightProvider losProvider;
private int maxRayTests;
public HitDetectionExecutor() {
this.losProvider = LineOfSightProvider.DEFAULT_TRUE;
this.maxRayTests = 10;
}
public Vector4d getHitLocation() {
return this.buffer.hitPosition;
}
@Nonnull
public HitDetectionExecutor setProjectionProvider(MatrixProvider provider) {
this.projectionProvider = provider;
return this;
}
@Nonnull
public HitDetectionExecutor setViewProvider(MatrixProvider provider) {
this.viewProvider = provider;
return this;
}
@Nonnull
public HitDetectionExecutor setLineOfSightProvider(LineOfSightProvider losProvider) {
this.losProvider = losProvider;
return this;
}
@Nonnull
public HitDetectionExecutor setMaxRayTests(int maxRayTests) {
this.maxRayTests = maxRayTests;
return this;
}
@Nonnull
public HitDetectionExecutor setOrigin(double x, double y, double z) {
this.origin.assign(x, y, z, 1.0);
return this;
}
private void setupMatrices(@Nonnull Matrix4d modelMatrix) {
Matrix4d projectionMatrix = this.projectionProvider.getMatrix();
Matrix4d viewMatrix = this.viewProvider.getMatrix();
this.pvmMatrix.assign(projectionMatrix).multiply(viewMatrix);
this.invPvMatrix.assign(this.pvmMatrix).invert();
this.pvmMatrix.multiply(modelMatrix);
}
public boolean test(@Nonnull Vector4d point, @Nonnull Matrix4d modelMatrix) {
this.setupMatrices(modelMatrix);
return this.testPoint(point);
}
public boolean test(@Nonnull Quad4d[] model, @Nonnull Matrix4d modelMatrix) {
try {
this.setupMatrices(modelMatrix);
return this.testModel(model);
} catch (Throwable t) {
((HytaleLogger.Api)log.at(Level.SEVERE).withCause(t)).log("Error occured during Hit Detection execution. Dumping parameters!");
log.at(Level.SEVERE).log("this = %s", this);
log.at(Level.SEVERE).log("model = %s", Arrays.toString(model));
log.at(Level.SEVERE).log("modelMatrix = %s", modelMatrix);
log.at(Level.SEVERE).log("thread = %s", Thread.currentThread().getName());
throw t;
}
}
private boolean testPoint(@Nonnull Vector4d point) {
this.pvmMatrix.multiply(point, this.buffer.transformedPoint);
if (!this.buffer.transformedPoint.isInsideFrustum()) {
return false;
} else {
Vector4d hit = this.buffer.transformedPoint;
this.invPvMatrix.multiply(hit);
hit.perspectiveTransform();
return this.losProvider.test(this.origin.x, this.origin.y, this.origin.z, hit.x, hit.y, hit.z);
}
}
private boolean testModel(@Nonnull Quad4d[] model) {
int testsDone = 0;
double minDistanceSquared = 1.0 / 0.0;
for(Quad4d quad : model) {
if (testsDone++ == this.maxRayTests) {
return false;
}
quad.multiply(this.pvmMatrix, this.buffer.transformedQuad);
if (this.insideFrustum()) {
Vector4d hit = this.buffer.tempHitPosition;
if (this.buffer.containsFully) {
this.buffer.transformedQuad.getRandom(this.buffer.random, hit);
} else {
this.buffer.visibleTriangle.getRandom(this.buffer.random, hit);
}
this.invPvMatrix.multiply(hit);
hit.perspectiveTransform();
double dx = this.origin.x - hit.x;
double dy = this.origin.y - hit.y;
double dz = this.origin.z - hit.z;
double distanceSquared = dx * dx + dy * dy + dz * dz;
if (!(distanceSquared >= minDistanceSquared) && this.losProvider.test(this.origin.x, this.origin.y, this.origin.z, hit.x, hit.y, hit.z)) {
minDistanceSquared = distanceSquared;
this.buffer.hitPosition.assign(hit);
}
}
}
return minDistanceSquared != 1.0 / 0.0;
}
protected boolean insideFrustum() {
Quad4d quad = this.buffer.transformedQuad;
if (quad.isFullyInsideFrustum()) {
this.buffer.containsFully = true;
return true;
} else {
this.buffer.containsFully = false;
Vector4dBufferList vertices = this.buffer.vertexList1;
Vector4dBufferList auxillaryList = this.buffer.vertexList2;
vertices.clear();
auxillaryList.clear();
vertices.next().assign(quad.getA());
vertices.next().assign(quad.getB());
vertices.next().assign(quad.getC());
vertices.next().assign(quad.getD());
if (this.clipPolygonAxis(0) && this.clipPolygonAxis(1) && this.clipPolygonAxis(2)) {
Vector4d initialVertex = vertices.get(0);
int i = 1;
if (i < vertices.size() - 1) {
Triangle4d triangle = this.buffer.visibleTriangle;
triangle.assign(initialVertex, vertices.get(i), vertices.get(i + 1));
return true;
}
}
return false;
}
}
private boolean clipPolygonAxis(int componentIndex) {
clipPolygonComponent(this.buffer.vertexList1, componentIndex, 1.0, this.buffer.vertexList2);
this.buffer.vertexList1.clear();
if (this.buffer.vertexList2.isEmpty()) {
return false;
} else {
clipPolygonComponent(this.buffer.vertexList2, componentIndex, -1.0, this.buffer.vertexList1);
this.buffer.vertexList2.clear();
return !this.buffer.vertexList1.isEmpty();
}
}
private static void clipPolygonComponent(@Nonnull Vector4dBufferList vertices, int componentIndex, double componentFactor, @Nonnull Vector4dBufferList result) {
Vector4d previousVertex = vertices.get(vertices.size() - 1);
double previousComponent = previousVertex.get(componentIndex) * componentFactor;
boolean previousInside = previousComponent <= previousVertex.w;
for(int i = 0; i < vertices.size(); ++i) {
Vector4d vertex = vertices.get(i);
double component = vertex.get(componentIndex) * componentFactor;
boolean inside = component <= vertex.w;
if (inside ^ previousInside) {
double lerp = (previousVertex.w - previousComponent) / (previousVertex.w - previousComponent - (vertex.w - component));
previousVertex.lerp(vertex, lerp, result.next());
}
if (inside) {
result.next().assign(vertex);
}
previousVertex = vertex;
previousComponent = component;
previousInside = inside;
}
}
@Nonnull
public String toString() {
String var10000 = String.valueOf(this.pvmMatrix);
return "HitDetectionExecutor{pvmMatrix=" + var10000 + ", invPvMatrix=" + String.valueOf(this.invPvMatrix) + ", origin=" + String.valueOf(this.origin) + ", buffer=" + String.valueOf(this.buffer) + ", projectionProvider=" + String.valueOf(this.projectionProvider) + ", viewProvider=" + String.valueOf(this.viewProvider) + ", losProvider=" + String.valueOf(this.losProvider) + ", maxRayTests=" + this.maxRayTests + "}";
}
static {
CUBE_QUADS = new Quad4d[]{new Quad4d(VERTEX_POINTS, 0, 1, 3, 2), new Quad4d(VERTEX_POINTS, 0, 4, 5, 1), new Quad4d(VERTEX_POINTS, 4, 5, 7, 6), new Quad4d(VERTEX_POINTS, 2, 3, 7, 6), new Quad4d(VERTEX_POINTS, 1, 3, 7, 5), new Quad4d(VERTEX_POINTS, 0, 2, 6, 4)};
}
}