From 10d271059c9f197655bdd776505ef2c4c64f691f Mon Sep 17 00:00:00 2001 From: OLEGSHA Date: Tue, 2 Feb 2021 18:49:55 +0300 Subject: [PATCH] Added RelRelation and RelFace; added discrete up vector to GravityModel --- .../progressia/common/util/VectorUtil.java | 34 +++++ .../progressia/common/world/GravityModel.java | 85 ++++++++++- .../progressia/common/world/rels/AbsFace.java | 121 +++++++++++++++ .../common/world/rels/BlockRelation.java | 10 +- .../progressia/common/world/rels/RelFace.java | 124 ++++++++++++++++ .../common/world/rels/RelRelation.java | 140 ++++++++++++++++++ .../progressia/test/gen/TestGravityModel.java | 8 + 7 files changed, 513 insertions(+), 9 deletions(-) create mode 100644 src/main/java/ru/windcorp/progressia/common/world/rels/RelFace.java create mode 100644 src/main/java/ru/windcorp/progressia/common/world/rels/RelRelation.java 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 . + */ +package ru.windcorp.progressia.common.world.rels; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +import glm.vec._3.i.Vec3i; + +public class RelFace extends RelRelation { + + // @formatter:off + public static final RelFace + UP = new RelFace( 0, 0, +1, "UP"), + DOWN = new RelFace( 0, 0, -1, "DOWN"), + NORTH = new RelFace(+1, 0, 0, "NORTH"), + SOUTH = new RelFace(-1, 0, 0, "SOUTH"), + WEST = new RelFace( 0, +1, 0, "WEST"), + EAST = new RelFace( 0, -1, 0, "EAST"); + // @formatter:on + + private static final ImmutableList ALL_FACES = ImmutableList.of(UP, DOWN, NORTH, SOUTH, WEST, EAST); + + static { + link(UP, DOWN); + link(NORTH, SOUTH); + link(WEST, EAST); + } + + public static ImmutableList getFaces() { + return ALL_FACES; + } + + private static void link(RelFace a, RelFace b) { + a.counterFace = b; + b.counterFace = a; + } + + public static ImmutableMap mapToFaces( + E up, + E down, + E north, + E south, + E west, + E east + ) { + return ImmutableMap.builderWithExpectedSize(6) + .put(UP, up) + .put(DOWN, down) + .put(NORTH, north) + .put(SOUTH, south) + .put(WEST, west) + .put(EAST, east) + .build(); + } + + private static int nextId = 0; + + private final int id; + private final String name; + private RelFace counterFace; + + private RelFace(int x, int y, int z, String name) { + super(x, y, z, true); + this.id = nextId++; + this.name = name; + } + + public String getName() { + return name; + } + + /** + * @return the id + */ + public int getId() { + return id; + } + + public RelFace getCounter() { + return counterFace; + } + + public RelFace getCounterAndMoveCursor(Vec3i cursor) { + cursor.add(getVector()); + return counterFace; + } + + @Override + public float getEuclideanDistance() { + return 1.0f; + } + + @Override + public int getChebyshevDistance() { + return 1; + } + + @Override + public int getManhattanDistance() { + return 1; + } + + @Override + public String toString() { + return getName(); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/rels/RelRelation.java b/src/main/java/ru/windcorp/progressia/common/world/rels/RelRelation.java new file mode 100644 index 0000000..a4d23d4 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/rels/RelRelation.java @@ -0,0 +1,140 @@ +/* + * 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 . + */ +package ru.windcorp.progressia.common.world.rels; + +import java.util.Map; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.util.VectorUtil; +import ru.windcorp.progressia.common.util.VectorUtil.SignedAxis; + +import static ru.windcorp.progressia.common.util.VectorUtil.SignedAxis.*; + +/** + * Name stands for Relative Relation + */ +public class RelRelation extends BlockRelation { + + private static class Rotation { + private final SignedAxis northDestination; + private final SignedAxis westDestination; + private final SignedAxis upDestination; + + public Rotation(SignedAxis northDestination, SignedAxis westDestination, SignedAxis upDestination) { + this.northDestination = northDestination; + this.westDestination = westDestination; + this.upDestination = upDestination; + } + + public Vec3i apply(Vec3i output, Vec3i input) { + if (output == null) { + return output; + } + + set(output, input.x, northDestination); + set(output, input.y, westDestination); + set(output, input.z, upDestination); + + return output; + } + + private static void set(Vec3i output, int value, SignedAxis axis) { + VectorUtil.set(output, axis.getAxis(), axis.isPositive() ? +value : -value); + } + } + + private final static Map TRANSFORMATIONS = AbsFace.mapToFaces( + new Rotation(POS_X, POS_Y, POS_Z), + new Rotation(POS_X, NEG_Y, NEG_Z), + new Rotation(POS_Z, NEG_Y, POS_X), + new Rotation(POS_Z, POS_Y, NEG_X), + new Rotation(POS_Z, NEG_X, NEG_Y), + new Rotation(POS_Z, POS_X, POS_Y) + ); + + private final Vec3i vector = new Vec3i(); + private AbsRelation[] resolved = null; + + public RelRelation(int north, int west, int up) { + this(north, west, up, false); + } + + protected RelRelation(int north, int west, int up, boolean precomputeAllResolutions) { + vector.set(north, west, up); + + if (precomputeAllResolutions) { + for (AbsFace face : AbsFace.getFaces()) { + resolve(face); + } + } + } + + /** + * @return the relative vector (northward, westward, upward) + */ + public Vec3i getVector() { + return vector; + } + + public int getNorthward() { + return vector.x; + } + + public int getWestward() { + return vector.y; + } + + public int getUpward() { + return vector.z; + } + + public int getSouthward() { + return -getNorthward(); + } + + public int getEastward() { + return -getWestward(); + } + + public int getDownward() { + return -getUpward(); + } + + @Override + public AbsRelation resolve(AbsFace up) { + if (resolved == null) { + resolved = new AbsRelation[AbsFace.BLOCK_FACE_COUNT]; + } + + if (resolved[up.getId()] == null) { + resolved[up.getId()] = computeResolution(up); + } + + return resolved[up.getId()]; + } + + private AbsRelation computeResolution(AbsFace up) { + return new AbsRelation(TRANSFORMATIONS.get(up).apply(new Vec3i(), vector)); + } + + @Override + protected Vec3i getSample() { + return getVector(); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/gen/TestGravityModel.java b/src/main/java/ru/windcorp/progressia/test/gen/TestGravityModel.java index 39e889e..24dbf2b 100644 --- a/src/main/java/ru/windcorp/progressia/test/gen/TestGravityModel.java +++ b/src/main/java/ru/windcorp/progressia/test/gen/TestGravityModel.java @@ -18,7 +18,9 @@ package ru.windcorp.progressia.test.gen; import glm.vec._3.Vec3; +import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.common.world.GravityModel; +import ru.windcorp.progressia.common.world.rels.AbsFace; public class TestGravityModel extends GravityModel { @@ -37,5 +39,11 @@ public class TestGravityModel extends GravityModel { output.normalize().mul(-9.8f); } + + @Override + protected AbsFace doGetDiscreteUp(Vec3i chunkPos) { + AbsFace rounded = AbsFace.roundToFace(chunkPos.x, chunkPos.y, chunkPos.z - 54); + return rounded == null ? AbsFace.POS_Z : rounded; + } }