diff --git a/src/main/java/ru/windcorp/progressia/common/collision/colliders/AABBoidCollider.java b/src/main/java/ru/windcorp/progressia/common/collision/colliders/AABBoidCollider.java index d6c31c1..ed42594 100644 --- a/src/main/java/ru/windcorp/progressia/common/collision/colliders/AABBoidCollider.java +++ b/src/main/java/ru/windcorp/progressia/common/collision/colliders/AABBoidCollider.java @@ -122,46 +122,51 @@ class AABBoidCollider { return output; } + // @formatter:off /* - * Here we determine whether a collision has actually happened, and if it - * did, at what moment. - * The basic idea is to compute the moment of collision and impact - * coordinates in wall coordinate space. - * Then, we can check impact coordinates to determine if we actually hit the - * 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. + * Here we determine whether a collision has actually happened, and if it did, at what moment. + * + * The basic idea is to compute the moment of collision and impact coordinates in wall coordinate space. + * Then, we can check impact coordinates to determine if we actually hit the 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: - * 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. + * * Then, a collision occurs if there exist x, y and t such that - * ______ _ - * r_line + v * t + * ______ _ + * r_line + v * t + * * and - * ______ _ _ - * r_wall + w * x + h * y - * 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 - * system). + * ______ _ _ + * r_wall + w * x + h * y + * + * 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 system). + * * Therefore, - * ______ _ ______ _ _ - * 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_z h_z -v_z⎦ ⎣t⎦ - * ⎡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⎦ + * ______ _ ______ _ _ + * 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_z h_z -v_z⎦ ⎣t⎦ + * + * ⎡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: - * 0 < x < 1, - * 0 < y < 1, and - * 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. + * 0 < x < 1, + * 0 < y < 1, and + * 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 all conditions are satisfied, then the moment of impact is t0 + t. */ + // @formatter:on private static Collision computeWallCollision( Wall obstacleWall, AABBoid colliderModel, diff --git a/src/main/java/ru/windcorp/progressia/common/collision/colliders/Collider.java b/src/main/java/ru/windcorp/progressia/common/collision/colliders/Collider.java index 56bc8ad..62a7808 100644 --- a/src/main/java/ru/windcorp/progressia/common/collision/colliders/Collider.java +++ b/src/main/java/ru/windcorp/progressia/common/collision/colliders/Collider.java @@ -212,66 +212,72 @@ public class Collider { handlePhysics(collision); } + // @formatter:off /* * Here we compute the change in body velocities due to a collision. + * * We make the following simplifications: - * 1) The bodies are perfectly rigid; - * 2) The collision is perfectly inelastic - * (no bouncing); - * 3) The bodies are spherical; - * 4) No tangential friction exists - * (bodies do not experience friction when sliding against each other); - * 5) Velocities are not relativistic. + * 1) The bodies are perfectly rigid; + * 2) The collision is perfectly inelastic + * (no bouncing); + * 3) The bodies are spherical; + * 4) No tangential friction exists + * (bodies do not experience friction when sliding against each other); + * 5) Velocities are not relativistic. + * * Angular momentum is ignored per 3) and 4), - * e.g. when something pushes an end of a long stick, the stick does not - * rotate. + * e.g. when something pushes an end of a long stick, the stick does not rotate. + * * 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 - * _ _ _ _ _ - * n = (w ⨯ h) / (|w ⨯ h|), - * _ _ + * _ _ _ _ _ + * n = (w ⨯ h) / (|w ⨯ h|), + * _ _ * 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. - * ___ ___ + * ___ ___ * After the collision desired velocities are u_a and u_b, respectively. - * _ - * (Notation convention: suffix 'n' denotes a vector projection onto vector - * n, + * _ + * (Notation convention: suffix 'n' denotes a vector projection onto vector n, * 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: - * ____________ ____________ ________________ - * 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}. + * + * 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; + * ⎨ ___________ ____________ + * t: ⎩ p_i_after_t = p_i_before_t for any i in {a, b}. + * * 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 ≡ - * u_an = u_bn; - * ⎨ ____ ___ _ ___ _ - * t: ⎩ u_it = v_i - n * (v_i ⋅ n) for any i in {a, b}. + * ___ _ ___ _ ___ ___ ____ ____ + * n: ⎧ M_a * (v_a ⋅ n) + M_b * (v_b ⋅ n) = (M_a + M_b) * u_n, where u_n ≡ u_an = u_bn; + * ⎨ ____ ___ _ ___ _ + * t: ⎩ u_it = v_i - n * (v_i ⋅ n) for any i in {a, b}. + * * 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, - * ___ _ ___ _ ___ _ - * 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). + * * Finally, - * ___ ____ ___ - * 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 - * for infinite masses, signifying masses "significantly greater" than - * finite masses: - * 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}. - * 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}. + * ___ ____ ___ + * 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 + * for infinite masses, signifying masses "significantly greater" than finite masses: + * + * 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}. + * 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}. */ + // @formatter:on private static void handlePhysics(Collision collision) { // Fuck JGLM Vec3 n = Vectors.grab3(); diff --git a/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java b/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java index ac0fb93..5788053 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java +++ b/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java @@ -30,6 +30,7 @@ import java.util.function.Consumer; import glm.vec._3.i.Vec3i; 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.generic.GenericChunk; import ru.windcorp.progressia.common.world.rels.AbsFace; @@ -44,14 +45,16 @@ public class ChunkData implements GenericChunk { 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 WorldData world; 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 * - BLOCK_FACE_COUNT]; + private final TileDataStack[] tiles = new TileDataStack[ + BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * BLOCK_FACE_COUNT + ]; 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 public TileDataStack getTilesOrNull(Vec3i blockInChunk, BlockFace face) { diff --git a/src/main/java/ru/windcorp/progressia/common/world/entity/EntityData.java b/src/main/java/ru/windcorp/progressia/common/world/entity/EntityData.java index 344439a..e776e6a 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/entity/EntityData.java +++ b/src/main/java/ru/windcorp/progressia/common/world/entity/EntityData.java @@ -282,9 +282,9 @@ public class EntityData extends StatefulObject implements Collideable, GenericEn // Don't format. @formatter:off matrix.set( - cos + (1 - cos)*x*x, (1 - cos)*x*y - sin*z, (1 - cos)*x*z + sin*y, - (1 - cos)*y*x + sin*z, cos + (1 - cos)*y*y, (1 - cos)*y*z - sin*x, - (1 - cos)*z*x - sin*y, (1 - cos)*z*y + sin*x, cos + (1 - cos)*z*z + cos + (1 - cos)*x*x, (1 - cos)*y*x + sin*z, (1 - cos)*z*x - sin*y, + (1 - cos)*x*y - sin*z, cos + (1 - cos)*y*y, (1 - cos)*z*y + sin*x, + (1 - cos)*x*z + sin*y, (1 - cos)*y*z - sin*x, cos + (1 - cos)*z*z ); // @formatter:on diff --git a/src/main/java/ru/windcorp/progressia/server/Player.java b/src/main/java/ru/windcorp/progressia/server/Player.java index ea373a8..734783f 100644 --- a/src/main/java/ru/windcorp/progressia/server/Player.java +++ b/src/main/java/ru/windcorp/progressia/server/Player.java @@ -63,7 +63,7 @@ public class Player extends PlayerData implements ChunkLoader { for (cursor.x = -iRadius; cursor.x <= +iRadius; ++cursor.x) { for (cursor.y = -iRadius; cursor.y <= +iRadius; ++cursor.y) { 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); chunkConsumer.accept(cursor); diff --git a/src/main/java/ru/windcorp/progressia/server/Server.java b/src/main/java/ru/windcorp/progressia/server/Server.java index a46100a..40fdca6 100644 --- a/src/main/java/ru/windcorp/progressia/server/Server.java +++ b/src/main/java/ru/windcorp/progressia/server/Server.java @@ -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.ticking.Change; 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 { @@ -60,7 +60,7 @@ public class Server { private final TickingSettings tickingSettings = new TickingSettings(); 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.clientManager = new ClientManager(this); @@ -206,7 +206,7 @@ public class Server { } public float getLoadDistance(Player player) { - return Units.get(150.0f, "m"); + return Units.get(100.5f, "m"); } /** diff --git a/src/main/java/ru/windcorp/progressia/test/TestContent.java b/src/main/java/ru/windcorp/progressia/test/TestContent.java index 2b0b90c..d34c3ba 100644 --- a/src/main/java/ru/windcorp/progressia/test/TestContent.java +++ b/src/main/java/ru/windcorp/progressia/test/TestContent.java @@ -59,13 +59,15 @@ import ru.windcorp.progressia.server.world.block.*; import ru.windcorp.progressia.server.world.entity.*; import ru.windcorp.progressia.server.world.tile.*; import ru.windcorp.progressia.test.gen.TestGravityModel; +import ru.windcorp.progressia.test.gen.TestPlanetGravityModel; public class TestContent { public static final String PLAYER_LOGIN = "Sasha"; public static final long PLAYER_ENTITY_ID = 0x42; 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 PLACEABLE_BLOCKS = new ArrayList<>(); public static final List PLACEABLE_TILES = new ArrayList<>(); @@ -424,6 +426,7 @@ public class TestContent { ChunkIO.registerCodec(new TestChunkCodec()); ChunkRenderOptimizerRegistry.getInstance().register("Core:SurfaceOptimizer", ChunkRenderOptimizerSurface::new); GravityModelRegistry.getInstance().register(new TestGravityModel()); + GravityModelRegistry.getInstance().register(new TestPlanetGravityModel()); } } diff --git a/src/main/java/ru/windcorp/progressia/test/gen/TestPlanetGenerator.java b/src/main/java/ru/windcorp/progressia/test/gen/TestPlanetGenerator.java new file mode 100644 index 0000000..bbcdbfd --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/gen/TestPlanetGenerator.java @@ -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 . + */ +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 { + + 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; + } + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/gen/TestPlanetGravityModel.java b/src/main/java/ru/windcorp/progressia/test/gen/TestPlanetGravityModel.java new file mode 100644 index 0000000..05d659a --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/gen/TestPlanetGravityModel.java @@ -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 . + */ +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; + } + +}