diff --git a/src/main/java/ru/windcorp/progressia/common/util/VectorUtil.java b/src/main/java/ru/windcorp/progressia/common/util/VectorUtil.java index 1d86fce..6bef350 100644 --- a/src/main/java/ru/windcorp/progressia/common/util/VectorUtil.java +++ b/src/main/java/ru/windcorp/progressia/common/util/VectorUtil.java @@ -38,6 +38,40 @@ public class VectorUtil { public static enum Axis { X, Y, Z, W; } + + public static enum SignedAxis { + POS_X(Axis.X, +1), + NEG_X(Axis.X, -1), + POS_Y(Axis.Y, +1), + NEG_Y(Axis.Y, -1), + POS_Z(Axis.Z, +1), + NEG_Z(Axis.Z, -1), + POS_W(Axis.W, +1), + NEG_W(Axis.W, -1); + + private final Axis axis; + private final boolean isPositive; + + private SignedAxis(Axis axis, int sign) { + this.axis = axis; + this.isPositive = (sign == +1 ? true : false); + } + + /** + * @return the axis + */ + public Axis getAxis() { + return axis; + } + + public boolean isPositive() { + return isPositive; + } + + public int getSign() { + return isPositive ? +1 : -1; + } + } public static void iterateCuboid( int x0, diff --git a/src/main/java/ru/windcorp/progressia/common/world/GravityModel.java b/src/main/java/ru/windcorp/progressia/common/world/GravityModel.java index eecfc3b..14f61e9 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/GravityModel.java +++ b/src/main/java/ru/windcorp/progressia/common/world/GravityModel.java @@ -20,15 +20,33 @@ package ru.windcorp.progressia.common.world; import java.util.Objects; import glm.vec._3.Vec3; +import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.common.util.crash.CrashReports; import ru.windcorp.progressia.common.util.namespaces.Namespaced; +import ru.windcorp.progressia.common.world.rels.AbsFace; /** - * Gravity model specifies the gravitational acceleration field. A gravity model - * may be queried for the vector of gravitational acceleration that should - * affect an object. This vector is, generally speaking, a function of space: - * gravity in two different locations may vary. Gravity may also be a zero - * vector. + * Gravity model specifies the gravitational acceleration field, the up + * direction field and the discrete up direction field. + *
+ * A gravity model may be queried for the vector of gravitational acceleration + * that should affect an object. This vector is, generally speaking, a function + * of space: gravity in two different locations may vary. Gravity may also be a + * zero vector. + *
+ * The vector of gravitational acceleration defines the up direction. Up vector + * is defined as the additive inverse of the normalized gravitational + * acceleration vector or {@code (0; 0; 0)} if there is no gravity. + *
+ * Separately from the gravitational acceleration and the up vectors, a
+ * discrete up vector field is specified by a gravity model. This field
+ * is defined for each chunk uniquely and may only take the value of one of the
+ * six {@linkplain AbsFace absolute directions}. This vector specifies the
+ * rotation of blocks, tiles and other objects that may not have a
+ * non-axis-aligned direction. Discrete up vector must be specified even for
+ * chunks that have a zero or an ambiguous up direction. Although discrete up
+ * direction is not technically linked to the up direction, is it expected by
+ * the players that they generally align.
*
* @author javapony
*/
@@ -77,11 +95,54 @@ public abstract class GravityModel extends Namespaced {
*/
public Vec3 getUp(Vec3 pos, Vec3 output) {
output = getGravity(pos, output);
- if (output.any())
+
+ if (output.any()) {
output.normalize().negate();
+ }
+
return output;
}
+ /**
+ * Computes the discrete up vector for the chunk at the specified
+ * coordinates.
+ *
+ * @param chunkPos the coordinates of chunk to compute discrete up at
+ * @return an {@link AbsFace} that corresponds to the up direction in the
+ * specified chunk. Never {@code null}.
+ */
+ public AbsFace getDiscreteUp(Vec3i chunkPos) {
+ Objects.requireNonNull(chunkPos, "chunkPos");
+
+ final AbsFace result;
+
+ try {
+ result = doGetDiscreteUp(chunkPos);
+ } catch (Exception e) {
+ throw CrashReports.report(
+ e,
+ "%s failed to compute discrete up at (%d; %d; %d)",
+ this,
+ chunkPos.x,
+ chunkPos.y,
+ chunkPos.z
+ );
+ }
+
+ if (result == null) {
+ throw CrashReports.report(
+ null,
+ "%s has computed null as the discrete up at (%d; %d; %d). This is forbidden.",
+ this,
+ chunkPos.x,
+ chunkPos.y,
+ chunkPos.z
+ );
+ }
+
+ return result;
+ }
+
/**
* Computes the gravitational acceleration vector at the provided location.
* Actual computation of gravity is delegated to this method by the other
@@ -93,4 +154,16 @@ public abstract class GravityModel extends Namespaced {
*/
protected abstract void doGetGravity(Vec3 pos, Vec3 output);
+ /**
+ * Computes the discrete up vector for the chunk at the specified
+ * coordinates. A direction must be assigned under any circumstances. Actual
+ * computation of discrete up is delegated to this method by the other
+ * methods in this class.
+ *
+ * @param chunkPos the coordinates of chunk to compute discrete up at
+ * @return an {@link AbsFace} that corresponds to the up direction in the
+ * specified chunk. Never {@code null}.
+ */
+ protected abstract AbsFace doGetDiscreteUp(Vec3i chunkPos);
+
}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/rels/AbsFace.java b/src/main/java/ru/windcorp/progressia/common/world/rels/AbsFace.java
index e341ad3..5e9db57 100644
--- a/src/main/java/ru/windcorp/progressia/common/world/rels/AbsFace.java
+++ b/src/main/java/ru/windcorp/progressia/common/world/rels/AbsFace.java
@@ -18,9 +18,12 @@
package ru.windcorp.progressia.common.world.rels;
+import java.util.Objects;
+
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
public final class AbsFace extends AbsRelation {
@@ -87,6 +90,124 @@ public final class AbsFace extends AbsRelation {
.put(POS_Y, posY)
.build();
}
+
+ /**
+ * Rounds the provided vector to one of {@link AbsFace}s. The returned face
+ * is pointing in the same general direction as the provided vector. The
+ * result is undefined for arguments where two largest in absolute values
+ * coordinates are equal (e.g. for {@code (5; -5; 2)}). For a zero vector
+ * the result is {@code null}. Infinite vectors are handled correctly.
+ *
+ * @param vector the vector to round
+ * @return the face most adequately describing the provided vector, or
+ * {@code null} iff {@code vector.x = vector.y = vector.z = 0}
+ * @throws IllegalArgumentException if one of the coordinates is a NaN
+ */
+ public static AbsFace roundToFace(Vec3 vector) {
+ Objects.requireNonNull(vector, "vector");
+ return roundToFace(vector.x, vector.y, vector.z);
+ }
+
+ /**
+ * Rounds the provided vector to one of {@link AbsFace}s. The returned face
+ * is pointing in the same general direction as the provided vector. The
+ * result is undefined for arguments where two largest in absolute values
+ * coordinates are equal (e.g. for {@code (5; -5; 2)}). For a zero vector
+ * the result is {@code null}. Infinite arguments are handled correctly.
+ *
+ * @param x the X coordinate
+ * @param y the Y coordinate
+ * @param z the Z coordinate
+ * @return the face most adequately describing the provided vector, or
+ * {@code null} iff {@code x = y = z = 0}
+ * @throws IllegalArgumentException if one of the coordinates is a NaN
+ */
+ public static AbsFace roundToFace(float x, float y, float z) {
+ if (x == 0 && y == 0 && z == 0) {
+ return null;
+ }
+
+ if (Float.isNaN(x) || Float.isNaN(y) || Float.isNaN(z)) {
+ throw new IllegalArgumentException("Vector contains NaN: (" + x + "; " + y + "; " + z + ")");
+ }
+
+ // The following code handles infinite x, y or z properly
+
+ float absX = Math.abs(x);
+ float absY = Math.abs(y);
+ float absZ = Math.abs(z);
+
+ if (absX > absY) {
+ if (absX > absZ) {
+ return x > 0 ? POS_X : NEG_X;
+ } else {
+ // Z is the answer; exit decision tree
+ }
+ } else {
+ if (absY > absZ) {
+ return y > 0 ? POS_Y : NEG_Y;
+ } else {
+ // Z is the answer; exit decision tree
+ }
+ }
+
+ return z > 0 ? POS_Z : NEG_Z;
+ }
+
+ /**
+ * Rounds the provided vector to one of {@link AbsFace}s. The returned face
+ * is pointing in the same general direction as the provided vector. The
+ * result is undefined for arguments where two largest in absolute values
+ * coordinates are equal (e.g. for {@code (5; -5; 2)}). For a zero vector
+ * the result is {@code null}.
+ *
+ * @param vector the vector to round
+ * @return the face most adequately describing the provided vector, or
+ * {@code null} iff {@code vector.x = vector.y = vector.z = 0}
+ */
+ public static AbsFace roundToFace(Vec3i vector) {
+ Objects.requireNonNull(vector, "vector");
+ return roundToFace(vector.x, vector.y, vector.z);
+ }
+
+ /**
+ * Rounds the provided vector to one of {@link AbsFace}s. The returned face
+ * is pointing in the same general direction as the provided vector. The
+ * result is undefined for arguments where two largest in absolute values
+ * coordinates are equal (e.g. for {@code (5; -5; 2)}). For a zero vector
+ * the result is {@code null}.
+ *
+ * @param x the X coordinate
+ * @param y the Y coordinate
+ * @param z the Z coordinate
+ * @return the face most adequately describing the provided vector, or
+ * {@code null} iff {@code x = y = z = 0}
+ */
+ public static AbsFace roundToFace(int x, int y, int z) {
+ if (x == 0 && y == 0 && z == 0) {
+ return null;
+ }
+
+ int absX = Math.abs(x);
+ int absY = Math.abs(y);
+ int absZ = Math.abs(z);
+
+ if (absX > absY) {
+ if (absX > absZ) {
+ return x > 0 ? POS_X : NEG_X;
+ } else {
+ // Z is the answer; exit decision tree
+ }
+ } else {
+ if (absY > absZ) {
+ return y > 0 ? POS_Y : NEG_Y;
+ } else {
+ // Z is the answer; exit decision tree
+ }
+ }
+
+ return z > 0 ? POS_Z : NEG_Z;
+ }
private static int nextId = 0;
diff --git a/src/main/java/ru/windcorp/progressia/common/world/rels/BlockRelation.java b/src/main/java/ru/windcorp/progressia/common/world/rels/BlockRelation.java
index c55a122..f235fc4 100644
--- a/src/main/java/ru/windcorp/progressia/common/world/rels/BlockRelation.java
+++ b/src/main/java/ru/windcorp/progressia/common/world/rels/BlockRelation.java
@@ -39,6 +39,10 @@ public abstract class BlockRelation {
return resolve(up).getNormalized();
}
+ protected Vec3i getSample() {
+ return getVector(AbsFace.POS_Z);
+ }
+
/**
* Returns the distance between the source and destination blocks, as
* defined by the Euclidean space. Your everyday distance.
@@ -46,7 +50,7 @@ public abstract class BlockRelation {
* @return square root of the sum of the squares of the coordinates
*/
public float getEuclideanDistance() {
- return getVector(AbsFace.POS_Z).length();
+ return getSample().length();
}
/**
@@ -59,7 +63,7 @@ public abstract class BlockRelation {
* @return the sum of the absolute values of the coordinates
*/
public int getManhattanDistance() {
- Vec3i vector = getVector(AbsFace.POS_Z);
+ Vec3i vector = getSample();
return abs(vector.x) + abs(vector.y) + abs(vector.z);
}
@@ -71,7 +75,7 @@ public abstract class BlockRelation {
* @return the maximum of the absolute values of the coordinates
*/
public int getChebyshevDistance() {
- Vec3i vector = getVector(AbsFace.POS_Z);
+ Vec3i vector = getSample();
return max(abs(vector.x), max(abs(vector.y), abs(vector.z)));
}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/rels/RelFace.java b/src/main/java/ru/windcorp/progressia/common/world/rels/RelFace.java
new file mode 100644
index 0000000..9d4b658
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/rels/RelFace.java
@@ -0,0 +1,124 @@
+/*
+ * Progressia
+ * Copyright (C) 2020-2021 Wind Corporation and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see