Added RelRelation and RelFace; added discrete up vector to GravityModel

This commit is contained in:
OLEGSHA 2021-02-02 18:49:55 +03:00
parent acef9d32df
commit 10d271059c
Signed by: OLEGSHA
GPG Key ID: E57A4B08D64AFF7A
7 changed files with 513 additions and 9 deletions

View File

@ -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,

View File

@ -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.
* <p>
* 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.
* <p>
* 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.
* <p>
* Separately from the gravitational acceleration and the up vectors, a
* <em>discrete up</em> 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);
}

View File

@ -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;

View File

@ -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)));
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<RelFace> ALL_FACES = ImmutableList.of(UP, DOWN, NORTH, SOUTH, WEST, EAST);
static {
link(UP, DOWN);
link(NORTH, SOUTH);
link(WEST, EAST);
}
public static ImmutableList<RelFace> getFaces() {
return ALL_FACES;
}
private static void link(RelFace a, RelFace b) {
a.counterFace = b;
b.counterFace = a;
}
public static <E> ImmutableMap<RelFace, E> mapToFaces(
E up,
E down,
E north,
E south,
E west,
E east
) {
return ImmutableMap.<RelFace, E>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();
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<AbsFace, Rotation> 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();
}
}

View File

@ -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;
}
}