Added a cubic gravity model and fixed some stuff

- Added TestPlanetGenerator and a corresponding gravity model
- Fixed gravity-triggered camera rotation
This commit is contained in:
OLEGSHA 2021-02-22 15:38:14 +03:00
parent d438d2aa14
commit 2d55d4db51
Signed by: OLEGSHA
GPG Key ID: E57A4B08D64AFF7A
9 changed files with 422 additions and 85 deletions

View File

@ -122,46 +122,51 @@ class AABBoidCollider {
return output; return output;
} }
// @formatter:off
/* /*
* Here we determine whether a collision has actually happened, and if it * Here we determine whether a collision has actually happened, and if it did, at what moment.
* did, at what moment. *
* The basic idea is to compute the moment of collision and impact * The basic idea is to compute the moment of collision and impact coordinates in wall coordinate space.
* coordinates in wall coordinate space. * Then, we can check impact coordinates to determine if we actually hit the wall or flew by and then
* Then, we can check impact coordinates to determine if we actually hit the * check time to make sure the collision is not too far in the future and not in the past.
* wall or flew by and then *
* check time to make sure the collision is not too far in the future and
* not in the past.
* DETAILED EXPLANATION: * DETAILED EXPLANATION:
* Consider a surface defined by an origin r_wall and two noncollinear *
* nonzero vectors w and h. * Consider a surface defined by an origin r_wall and two noncollinear nonzero vectors w and h.
* Consider a line defined by an origin r_line and a nonzero vector v. * Consider a line defined by an origin r_line and a nonzero vector v.
*
* Then, a collision occurs if there exist x, y and t such that * Then, a collision occurs if there exist x, y and t such that
* ______ _ * ______ _
* r_line + v * t * r_line + v * t
*
* and * and
* ______ _ _ * ______ _ _
* r_wall + w * x + h * y * r_wall + w * x + h * y
* describe the same location (indeed, this corresponds to a collision at *
* moment t0 + t * describe the same location (indeed, this corresponds to a collision at moment t0 + t
* with a point on the wall with coordinates (x; y) in (w; h) coordinate * with a point on the wall with coordinates (x; y) in (w; h) coordinate system).
* system). *
* Therefore, * Therefore,
* ______ _ ______ _ _ * ______ _ ______ _ _
* r_line + v*t = r_wall + w*x + h*y; * r_line + v*t = r_wall + w*x + h*y;
* _ w_x h_x -v_x x _ ______ ______ *
* r = w_y h_y -v_y * y, where r = r_line - r_wall; * _ w_x h_x -v_x x _ ______ ______
* w_z h_z -v_z t * r = w_y h_y -v_y * y, where r = r_line - r_wall;
* x w_x h_x -v_x -1 _ * w_z h_z -v_z t
* y = w_y h_y -v_y * r, if the matrix is invertible. *
* t w_z h_z -v_z * x w_x h_x -v_x -1 _
* y = w_y h_y -v_y * r, if the matrix is invertible.
* t w_z h_z -v_z
*
* Then, one only needs to ensure that: * Then, one only needs to ensure that:
* 0 < x < 1, * 0 < x < 1,
* 0 < y < 1, and * 0 < y < 1, and
* 0 < t < T, where T is remaining tick time. * 0 < t < T, where T is remaining tick time.
* If the matrix is not invertible or any of the conditions are not met, no *
* collision happened. * If the matrix is not invertible or any of the conditions are not met, no collision happened.
* If all conditions are satisfied, then the moment of impact is t0 + t. * If all conditions are satisfied, then the moment of impact is t0 + t.
*/ */
// @formatter:on
private static Collision computeWallCollision( private static Collision computeWallCollision(
Wall obstacleWall, Wall obstacleWall,
AABBoid colliderModel, AABBoid colliderModel,

View File

@ -212,66 +212,72 @@ public class Collider {
handlePhysics(collision); handlePhysics(collision);
} }
// @formatter:off
/* /*
* Here we compute the change in body velocities due to a collision. * Here we compute the change in body velocities due to a collision.
*
* We make the following simplifications: * We make the following simplifications:
* 1) The bodies are perfectly rigid; * 1) The bodies are perfectly rigid;
* 2) The collision is perfectly inelastic * 2) The collision is perfectly inelastic
* (no bouncing); * (no bouncing);
* 3) The bodies are spherical; * 3) The bodies are spherical;
* 4) No tangential friction exists * 4) No tangential friction exists
* (bodies do not experience friction when sliding against each other); * (bodies do not experience friction when sliding against each other);
* 5) Velocities are not relativistic. * 5) Velocities are not relativistic.
*
* Angular momentum is ignored per 3) and 4), * Angular momentum is ignored per 3) and 4),
* e.g. when something pushes an end of a long stick, the stick does not * e.g. when something pushes an end of a long stick, the stick does not rotate.
* rotate. *
* DETAILED EXPLANATION: * DETAILED EXPLANATION:
* Two spherical (sic) bodies, a and b, experience a perfectly inelastic *
* collision * Two spherical (sic) bodies, a and b, experience a perfectly inelastic collision
* along a unit vector * along a unit vector
* _ _ _ _ _ * _ _ _ _ _
* n = (w h) / (|w h|), * n = (w h) / (|w h|),
* _ _ * _ _
* where w and h are two noncollinear nonzero vectors on the dividing plane. * where w and h are two noncollinear nonzero vectors on the dividing plane.
* ___ ___ * ___ ___
* Body masses and velocities are M_a, M_b and v_a, v_b, respectively. * Body masses and velocities are M_a, M_b and v_a, v_b, respectively.
* ___ ___ * ___ ___
* After the collision desired velocities are u_a and u_b, respectively. * After the collision desired velocities are u_a and u_b, respectively.
* _ * _
* (Notation convention: suffix 'n' denotes a vector projection onto vector * (Notation convention: suffix 'n' denotes a vector projection onto vector n,
* n,
* and suffix 't' denotes a vector projection onto the dividing plane.) * and suffix 't' denotes a vector projection onto the dividing plane.)
* Consider the law of conservation of momentum for axis n and the dividing *
* plane: * Consider the law of conservation of momentum for axis n and the dividing plane:
* ____________ ____________ ________________ * ____________ ____________ ________________
* n: p_a_before_n + p_b_before_n = p_common_after_n; * n: p_a_before_n + p_b_before_n = p_common_after_n;
* ___________ ____________ * ___________ ____________
* t: p_i_after_t = p_i_before_t for any i in {a, b}. * t: p_i_after_t = p_i_before_t for any i in {a, b}.
*
* Expressing all p_* in given terms: * Expressing all p_* in given terms:
* ___ _ ___ _ ___ ___ ____ ____ * ___ _ ___ _ ___ ___ ____ ____
* n: M_a * (v_a n) + M_b * (v_b n) = (M_a + M_b) * u_n, where u_n * n: M_a * (v_a n) + M_b * (v_b n) = (M_a + M_b) * u_n, where u_n u_an = u_bn;
* u_an = u_bn; * ____ ___ _ ___ _
* ____ ___ _ ___ _ * t: u_it = v_i - n * (v_i n) for any i in {a, b}.
* t: u_it = v_i - n * (v_i n) for any i in {a, b}. *
* Therefore: * Therefore:
* ___ _ ___ _ ___ _ * ___ _ ___ _ ___ _
* u_n = n * ( M_a/(M_a + M_b) * v_a n + M_b/(M_a + M_b) * v_b n ); * u_n = n * ( M_a/(M_a + M_b) * v_a n + M_b/(M_a + M_b) * v_b n );
*
* or, equivalently, * or, equivalently,
* ___ _ ___ _ ___ _ * ___ _ ___ _ ___ _
* u_n = n * ( m_a * v_a n + m_b * v_b n ), * u_n = n * ( m_a * v_a n + m_b * v_b n ),
*
* where m_a and m_b are relative masses (see below). * where m_a and m_b are relative masses (see below).
*
* Finally, * Finally,
* ___ ____ ___ * ___ ____ ___
* u_i = u_it + u_n for any i in {a, b}. * u_i = u_it + u_n for any i in {a, b}.
* The usage of relative masses m_i permits a convenient generalization of *
* the algorithm * The usage of relative masses m_i permits a convenient generalization of the algorithm
* for infinite masses, signifying masses "significantly greater" than * for infinite masses, signifying masses "significantly greater" than finite masses:
* finite masses: *
* 1) If both M_a and M_b are finite, let m_i = M_i / (M_a + M_b) for any i * 1) If both M_a and M_b are finite, let m_i = M_i / (M_a + M_b) for any i in {a, b}.
* in {a, b}. * 2) If M_i is finite but M_j is infinite, let m_i = 0 and m_j = 1.
* 2) If M_i is finite but M_j is infinite, let m_i = 0 and m_j = 1. * 3) If both M_a and M_b are infinite, let m_i = 1/2 for any i in {a, b}.
* 3) If both M_a and M_b are infinite, let m_i = 1/2 for any i in {a, b}.
*/ */
// @formatter:on
private static void handlePhysics(Collision collision) { private static void handlePhysics(Collision collision) {
// Fuck JGLM // Fuck JGLM
Vec3 n = Vectors.grab3(); Vec3 n = Vectors.grab3();

View File

@ -30,6 +30,7 @@ import java.util.function.Consumer;
import glm.vec._3.i.Vec3i; import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.util.VectorUtil; import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.block.BlockData; import ru.windcorp.progressia.common.world.block.BlockData;
import ru.windcorp.progressia.common.world.generic.GenericChunk; import ru.windcorp.progressia.common.world.generic.GenericChunk;
import ru.windcorp.progressia.common.world.rels.AbsFace; import ru.windcorp.progressia.common.world.rels.AbsFace;
@ -44,14 +45,16 @@ public class ChunkData
implements GenericChunk<ChunkData, BlockData, TileData, TileDataStack> { implements GenericChunk<ChunkData, BlockData, TileData, TileDataStack> {
public static final int BLOCKS_PER_CHUNK = Coordinates.CHUNK_SIZE; public static final int BLOCKS_PER_CHUNK = Coordinates.CHUNK_SIZE;
public static final int CHUNK_RADIUS = BLOCKS_PER_CHUNK / 2;
private final Vec3i position = new Vec3i(); private final Vec3i position = new Vec3i();
private final WorldData world; private final WorldData world;
private final BlockData[] blocks = new BlockData[BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK]; private final BlockData[] blocks = new BlockData[BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK];
private final TileDataStack[] tiles = new TileDataStack[BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * private final TileDataStack[] tiles = new TileDataStack[
BLOCK_FACE_COUNT]; BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * BLOCK_FACE_COUNT
];
private final AbsFace up; private final AbsFace up;
@ -91,6 +94,13 @@ public class ChunkData
}); });
} }
} }
public void setBlockRel(Vec3i relativeBlockInChunk, BlockData block, boolean notify) {
Vec3i absoluteBlockInChunk = Vectors.grab3i();
resolve(relativeBlockInChunk, absoluteBlockInChunk);
setBlock(absoluteBlockInChunk, block, notify);
Vectors.release(absoluteBlockInChunk);
}
@Override @Override
public TileDataStack getTilesOrNull(Vec3i blockInChunk, BlockFace face) { public TileDataStack getTilesOrNull(Vec3i blockInChunk, BlockFace face) {

View File

@ -282,9 +282,9 @@ public class EntityData extends StatefulObject implements Collideable, GenericEn
// Don't format. @formatter:off // Don't format. @formatter:off
matrix.set( matrix.set(
cos + (1 - cos)*x*x, (1 - cos)*x*y - sin*z, (1 - cos)*x*z + sin*y, cos + (1 - cos)*x*x, (1 - cos)*y*x + sin*z, (1 - cos)*z*x - sin*y,
(1 - cos)*y*x + sin*z, cos + (1 - cos)*y*y, (1 - cos)*y*z - sin*x, (1 - cos)*x*y - sin*z, cos + (1 - cos)*y*y, (1 - cos)*z*y + sin*x,
(1 - cos)*z*x - sin*y, (1 - cos)*z*y + sin*x, cos + (1 - cos)*z*z (1 - cos)*x*z + sin*y, (1 - cos)*y*z - sin*x, cos + (1 - cos)*z*z
); );
// @formatter:on // @formatter:on

View File

@ -63,7 +63,7 @@ public class Player extends PlayerData implements ChunkLoader {
for (cursor.x = -iRadius; cursor.x <= +iRadius; ++cursor.x) { for (cursor.x = -iRadius; cursor.x <= +iRadius; ++cursor.x) {
for (cursor.y = -iRadius; cursor.y <= +iRadius; ++cursor.y) { for (cursor.y = -iRadius; cursor.y <= +iRadius; ++cursor.y) {
for (cursor.z = -iRadius; cursor.z <= +iRadius; ++cursor.z) { for (cursor.z = -iRadius; cursor.z <= +iRadius; ++cursor.z) {
if (cursor.x * cursor.x + cursor.y * cursor.y + (cursor.z * 2) * (cursor.z * 2) <= radiusSq) { if (cursor.x * cursor.x + cursor.y * cursor.y + (cursor.z/* * 2*/) * (cursor.z/* * 2*/) <= radiusSq) {
cursor.add(start); cursor.add(start);
chunkConsumer.accept(cursor); chunkConsumer.accept(cursor);

View File

@ -31,7 +31,7 @@ import ru.windcorp.progressia.server.world.WorldLogic;
import ru.windcorp.progressia.server.world.tasks.WorldAccessor; import ru.windcorp.progressia.server.world.tasks.WorldAccessor;
import ru.windcorp.progressia.server.world.ticking.Change; import ru.windcorp.progressia.server.world.ticking.Change;
import ru.windcorp.progressia.server.world.ticking.Evaluation; import ru.windcorp.progressia.server.world.ticking.Evaluation;
import ru.windcorp.progressia.test.gen.TestWorldGenerator; import ru.windcorp.progressia.test.gen.TestPlanetGenerator;
public class Server { public class Server {
@ -60,7 +60,7 @@ public class Server {
private final TickingSettings tickingSettings = new TickingSettings(); private final TickingSettings tickingSettings = new TickingSettings();
public Server(WorldData world) { public Server(WorldData world) {
this.world = new WorldLogic(world, this, TestWorldGenerator::new); this.world = new WorldLogic(world, this, w -> new TestPlanetGenerator("Test:PlanetGenerator", Units.get("48 m"), w));
this.serverThread = new ServerThread(this); this.serverThread = new ServerThread(this);
this.clientManager = new ClientManager(this); this.clientManager = new ClientManager(this);
@ -206,7 +206,7 @@ public class Server {
} }
public float getLoadDistance(Player player) { public float getLoadDistance(Player player) {
return Units.get(150.0f, "m"); return Units.get(100.5f, "m");
} }
/** /**

View File

@ -59,13 +59,15 @@ import ru.windcorp.progressia.server.world.block.*;
import ru.windcorp.progressia.server.world.entity.*; import ru.windcorp.progressia.server.world.entity.*;
import ru.windcorp.progressia.server.world.tile.*; import ru.windcorp.progressia.server.world.tile.*;
import ru.windcorp.progressia.test.gen.TestGravityModel; import ru.windcorp.progressia.test.gen.TestGravityModel;
import ru.windcorp.progressia.test.gen.TestPlanetGravityModel;
public class TestContent { public class TestContent {
public static final String PLAYER_LOGIN = "Sasha"; public static final String PLAYER_LOGIN = "Sasha";
public static final long PLAYER_ENTITY_ID = 0x42; public static final long PLAYER_ENTITY_ID = 0x42;
public static final long STATIE_ENTITY_ID = 0xDEADBEEF; public static final long STATIE_ENTITY_ID = 0xDEADBEEF;
public static final Vec3 SPAWN = new Vec3(8, 8, 880); // public static final Vec3 SPAWN = new Vec3(8, 8, 880);
public static final Vec3 SPAWN = new Vec3(0, 0, 66);
public static final List<BlockData> PLACEABLE_BLOCKS = new ArrayList<>(); public static final List<BlockData> PLACEABLE_BLOCKS = new ArrayList<>();
public static final List<TileData> PLACEABLE_TILES = new ArrayList<>(); public static final List<TileData> PLACEABLE_TILES = new ArrayList<>();
@ -424,6 +426,7 @@ public class TestContent {
ChunkIO.registerCodec(new TestChunkCodec()); ChunkIO.registerCodec(new TestChunkCodec());
ChunkRenderOptimizerRegistry.getInstance().register("Core:SurfaceOptimizer", ChunkRenderOptimizerSurface::new); ChunkRenderOptimizerRegistry.getInstance().register("Core:SurfaceOptimizer", ChunkRenderOptimizerSurface::new);
GravityModelRegistry.getInstance().register(new TestGravityModel()); GravityModelRegistry.getInstance().register(new TestGravityModel());
GravityModelRegistry.getInstance().register(new TestPlanetGravityModel());
} }
} }

View File

@ -0,0 +1,181 @@
/*
* 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.test.gen;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Arrays;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.DecodingException;
import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.common.world.block.BlockData;
import ru.windcorp.progressia.common.world.block.BlockDataRegistry;
import ru.windcorp.progressia.common.world.rels.RelFace;
import ru.windcorp.progressia.common.world.tile.TileData;
import ru.windcorp.progressia.common.world.tile.TileDataRegistry;
import ru.windcorp.progressia.server.world.WorldLogic;
import ru.windcorp.progressia.server.world.generation.AbstractWorldGenerator;
public class TestPlanetGenerator extends AbstractWorldGenerator<Boolean> {
private final int surfaceLevel;
public TestPlanetGenerator(String id, float planetRadius, WorldLogic world) {
super(id, Boolean.class, "Test:PlanetGravityModel");
this.surfaceLevel = (int) (planetRadius / ChunkData.BLOCKS_PER_CHUNK);
if (surfaceLevel < 2) {
throw new IllegalArgumentException("planetRadius too small, must be at least 32 m");
}
}
@Override
protected Boolean doReadGenerationHint(DataInputStream input) throws IOException, DecodingException {
return input.readBoolean();
}
@Override
protected void doWriteGenerationHint(DataOutputStream output, Boolean hint) throws IOException {
output.writeBoolean(hint);
}
@Override
protected boolean checkIsChunkReady(Boolean hint) {
return hint;
}
@Override
public ChunkData generate(Vec3i chunkPos, WorldData world) {
ChunkData chunk = new ChunkData(chunkPos, world);
generate(chunk);
chunk.setGenerationHint(true);
world.addChunk(chunk);
return chunk;
}
private enum ChunkType {
SURFACE, UNDERGROUND, EDGE_SURFACE, EDGE_UNDERGROUND, CORE, AIR;
}
private void generate(ChunkData chunk) {
switch (getChunkType(chunk.getPosition())) {
case SURFACE:
fillSurface(chunk);
break;
case UNDERGROUND:
fillUndeground(chunk);
break;
case EDGE_SURFACE:
fillEdgeSurface(chunk);
break;
case EDGE_UNDERGROUND:
fillEdgeUnderground(chunk);
break;
case CORE:
fillCore(chunk);
break;
case AIR:
fillAir(chunk);
break;
}
}
private void fillSurface(ChunkData chunk) {
final int bpc = ChunkData.BLOCKS_PER_CHUNK;
BlockData dirt = BlockDataRegistry.getInstance().get("Test:Dirt");
BlockData granite = BlockDataRegistry.getInstance().get("Test:GraniteMonolith");
TileData grass = TileDataRegistry.getInstance().get("Test:Grass");
chunk.forEachBiC(bic -> {
BlockData block;
if (bic.z > bpc - 4) {
block = dirt;
} else {
block = granite;
}
chunk.setBlockRel(bic, block, false);
});
VectorUtil.iterateCuboid(0, 0, bpc - 1, bpc, bpc, bpc, bic -> {
chunk.getTilesRel(bic, RelFace.UP).add(grass);
});
}
private void fillUndeground(ChunkData chunk) {
fill(chunk, BlockDataRegistry.getInstance().get("Test:GraniteMonolith"));
}
private void fillEdgeSurface(ChunkData chunk) {
fill(chunk, BlockDataRegistry.getInstance().get("Test:Stone"));
}
private void fillEdgeUnderground(ChunkData chunk) {
fill(chunk, BlockDataRegistry.getInstance().get("Test:Stone"));
}
private void fillCore(ChunkData chunk) {
fill(chunk, BlockDataRegistry.getInstance().get("Test:Stone"));
}
private void fillAir(ChunkData chunk) {
fill(chunk, BlockDataRegistry.getInstance().get("Test:Air"));
}
private void fill(ChunkData chunk, BlockData block) {
chunk.forEachBiC(bic -> chunk.setBlock(bic, block, false));
}
private ChunkType getChunkType(Vec3i pos) {
int[] abs = pos.abs_().toIA_();
Arrays.sort(abs);
int medium = abs[1];
int largest = abs[2];
int level = largest;
if (level == 0) {
return ChunkType.CORE;
}
if (largest > surfaceLevel) {
return ChunkType.AIR;
}
boolean isSurface = largest == surfaceLevel;
if (medium == largest) {
return isSurface ? ChunkType.EDGE_SURFACE : ChunkType.EDGE_UNDERGROUND;
} else {
return isSurface ? ChunkType.SURFACE : ChunkType.UNDERGROUND;
}
}
}

View File

@ -0,0 +1,132 @@
/*
* 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.test.gen;
import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.Units;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.GravityModel;
import ru.windcorp.progressia.common.world.rels.AbsFace;
import static java.lang.Math.*;
public class TestPlanetGravityModel extends GravityModel {
private static final float GRAVITATIONAL_ACCELERATION = Units.get("9.8 m/s^2");
private static final float ROUNDNESS = Units.get("16 m");
private static final float INNER_RADIUS = Units.get("16 m");
public TestPlanetGravityModel() {
this("Test:PlanetGravityModel");
}
protected TestPlanetGravityModel(String id) {
super(id);
}
@Override
protected void doGetGravity(Vec3 pos, Vec3 output) {
// Change to a CS where (0;0;0) is the center of the center chunk
float px = pos.x - ChunkData.CHUNK_RADIUS;
float py = pos.y - ChunkData.CHUNK_RADIUS;
float pz = pos.z - ChunkData.CHUNK_RADIUS;
// Assume weightlessness when too close to center
if ((px*px + py*py + pz*pz) < INNER_RADIUS*INNER_RADIUS) {
output.set(0, 0, 0);
return;
}
// Cache absolute coordinates
float ax = abs(px);
float ay = abs(py);
float az = abs(pz);
// Determine maximum and middle coordinates by absolute value
final float maxAbs;
final float midAbs;
// herptyderp
if (ax > ay) {
if (ax > az) {
maxAbs = ax;
midAbs = ay > az ? ay : az;
} else {
maxAbs = az;
midAbs = ax;
}
} else {
if (ay > az) {
maxAbs = ay;
midAbs = ax > az ? ax : az;
} else {
maxAbs = az;
midAbs = ay;
}
}
output.x = maxAbs - ax < ROUNDNESS ? (px > 0 ? +1 : -1) : 0;
output.y = maxAbs - ay < ROUNDNESS ? (py > 0 ? +1 : -1) : 0;
output.z = maxAbs - az < ROUNDNESS ? (pz > 0 ? +1 : -1) : 0;
if (maxAbs - midAbs < ROUNDNESS) {
output.normalize();
computeEdgeGravity(output.x, output.y, output.z, px, py, pz, output);
} else {
assert output.dot(output) == 1 : "maxAbs - midAbs = " + maxAbs + " - " + midAbs + " > " + ROUNDNESS + " yet l*l != 1";
}
output.mul(-GRAVITATIONAL_ACCELERATION);
}
private void computeEdgeGravity(float lx, float ly, float lz, float rx, float ry, float rz, Vec3 output) {
// da math is gud, no worry
// - Javapony
if (lx == 0) rx = 0;
if (ly == 0) ry = 0;
if (lz == 0) rz = 0;
float scalarProduct = rx*lx + ry*ly + rz*lz;
float rSquared = rx*rx + ry*ry + rz*rz;
float distanceAlongEdge = scalarProduct - (float) sqrt(
scalarProduct*scalarProduct - rSquared + ROUNDNESS*ROUNDNESS
);
output.set(lx, ly, lz).mul(-distanceAlongEdge).add(rx, ry, rz).div(ROUNDNESS);
final float f = (float) sqrt(3.0/2);
if (signum(lx) != signum(output.x)) {
computeEdgeGravity(0, ly*f, lz*f, rx, ry, rz, output);
} else if (signum(ly) != signum(output.y)) {
computeEdgeGravity(lx*f, 0, lz*f, rx, ry, rz, output);
} else if (signum(lz) != signum(output.z)) {
computeEdgeGravity(lx*f, ly*f, 0, rx, ry, rz, output);
}
}
@Override
protected AbsFace doGetDiscreteUp(Vec3i chunkPos) {
AbsFace rounded = AbsFace.roundToFace(chunkPos.x, chunkPos.y, chunkPos.z);
return rounded == null ? AbsFace.POS_Z : rounded;
}
}