diff --git a/src/main/java/ru/windcorp/progressia/client/Client.java b/src/main/java/ru/windcorp/progressia/client/Client.java
index 5602048..3121602 100644
--- a/src/main/java/ru/windcorp/progressia/client/Client.java
+++ b/src/main/java/ru/windcorp/progressia/client/Client.java
@@ -24,7 +24,7 @@ import ru.windcorp.progressia.client.graphics.world.Camera;
import ru.windcorp.progressia.client.graphics.world.EntityAnchor;
import ru.windcorp.progressia.client.graphics.world.LocalPlayer;
import ru.windcorp.progressia.client.world.WorldRender;
-import ru.windcorp.progressia.common.world.WorldData;
+import ru.windcorp.progressia.common.world.DefaultWorldData;
import ru.windcorp.progressia.common.world.entity.EntityData;
public class Client {
@@ -36,7 +36,7 @@ public class Client {
private final ServerCommsChannel comms;
- public Client(WorldData world, ServerCommsChannel comms) {
+ public Client(DefaultWorldData world, ServerCommsChannel comms) {
this.world = new WorldRender(world, this);
this.comms = comms;
diff --git a/src/main/java/ru/windcorp/progressia/client/ClientState.java b/src/main/java/ru/windcorp/progressia/client/ClientState.java
index 6060623..91d21a7 100644
--- a/src/main/java/ru/windcorp/progressia/client/ClientState.java
+++ b/src/main/java/ru/windcorp/progressia/client/ClientState.java
@@ -21,7 +21,7 @@ package ru.windcorp.progressia.client;
import ru.windcorp.progressia.client.comms.localhost.LocalServerCommsChannel;
import ru.windcorp.progressia.client.graphics.GUI;
import ru.windcorp.progressia.client.graphics.world.LayerWorld;
-import ru.windcorp.progressia.common.world.WorldData;
+import ru.windcorp.progressia.common.world.DefaultWorldData;
import ru.windcorp.progressia.server.ServerState;
import ru.windcorp.progressia.test.LayerAbout;
import ru.windcorp.progressia.test.LayerTestUI;
@@ -41,7 +41,7 @@ public class ClientState {
public static void connectToLocalServer() {
- WorldData world = new WorldData();
+ DefaultWorldData world = new DefaultWorldData();
LocalServerCommsChannel channel = new LocalServerCommsChannel(ServerState.getInstance());
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/Colors.java b/src/main/java/ru/windcorp/progressia/client/graphics/Colors.java
index 0aeee7d..b37a6a8 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/Colors.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/Colors.java
@@ -15,20 +15,32 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package ru.windcorp.progressia.client.graphics;
import glm.vec._4.Vec4;
public class Colors {
- public static final Vec4 WHITE = toVector(0xFFFFFFFF), BLACK = toVector(0xFF000000),
+ public static final Vec4 WHITE = toVector(0xFFFFFFFF),
+ BLACK = toVector(0xFF000000),
- GRAY_4 = toVector(0xFF444444), GRAY = toVector(0xFF888888), GRAY_A = toVector(0xFFAAAAAA),
+ GRAY_4 = toVector(0xFF444444),
+ GRAY = toVector(0xFF888888),
+ GRAY_A = toVector(0xFFAAAAAA),
- DEBUG_RED = toVector(0xFFFF0000), DEBUG_GREEN = toVector(0xFF00FF00), DEBUG_BLUE = toVector(0xFF0000FF),
- DEBUG_CYAN = toVector(0xFF00FFFF), DEBUG_MAGENTA = toVector(0xFFFF00FF),
- DEBUG_YELLOW = toVector(0xFFFFFF00);
+ DEBUG_RED = toVector(0xFFFF0000),
+ DEBUG_GREEN = toVector(0xFF00FF00),
+ DEBUG_BLUE = toVector(0xFF0000FF),
+ DEBUG_CYAN = toVector(0xFF00FFFF),
+ DEBUG_MAGENTA = toVector(0xFFFF00FF),
+ DEBUG_YELLOW = toVector(0xFFFFFF00),
+
+ LIGHT_GRAY = toVector(0xFFCBCBD0),
+ BLUE = toVector(0xFF37A2E6),
+ HOVER_BLUE = toVector(0xFFC3E4F7),
+ DISABLED_GRAY = toVector(0xFFE5E5E5),
+ DISABLED_BLUE = toVector(0xFFB2D8ED);
public static Vec4 toVector(int argb) {
return toVector(argb, new Vec4());
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/GUI.java b/src/main/java/ru/windcorp/progressia/client/graphics/GUI.java
index 97e499c..38e405f 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/GUI.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/GUI.java
@@ -24,6 +24,7 @@ import java.util.List;
import com.google.common.eventbus.Subscribe;
+import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.input.CursorEvent;
import ru.windcorp.progressia.client.graphics.input.FrameResizeEvent;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
@@ -57,15 +58,24 @@ public class GUI {
}
public static void addBottomLayer(Layer layer) {
- modify(layers -> layers.add(layer));
+ modify(layers -> {
+ layers.add(layer);
+ layer.onAdded();
+ });
}
public static void addTopLayer(Layer layer) {
- modify(layers -> layers.add(0, layer));
+ modify(layers -> {
+ layers.add(0, layer);
+ layer.onAdded();
+ });
}
public static void removeLayer(Layer layer) {
- modify(layers -> layers.remove(layer));
+ modify(layers -> {
+ layers.remove(layer);
+ layer.onRemoved();
+ });
}
private static void modify(LayerStackModification mod) {
@@ -78,12 +88,33 @@ public class GUI {
public static void render() {
synchronized (LAYERS) {
- MODIFICATION_QUEUE.forEach(action -> action.affect(LAYERS));
- MODIFICATION_QUEUE.clear();
-
+
+ if (!MODIFICATION_QUEUE.isEmpty()) {
+ MODIFICATION_QUEUE.forEach(action -> action.affect(LAYERS));
+ MODIFICATION_QUEUE.clear();
+
+ boolean isMouseCurrentlyCaptured = GraphicsInterface.isMouseCaptured();
+ Layer.CursorPolicy policy = Layer.CursorPolicy.REQUIRE;
+
+ for (Layer layer : LAYERS) {
+ Layer.CursorPolicy currentPolicy = layer.getCursorPolicy();
+
+ if (currentPolicy != Layer.CursorPolicy.INDIFFERENT) {
+ policy = currentPolicy;
+ break;
+ }
+ }
+
+ boolean shouldCaptureMouse = (policy == Layer.CursorPolicy.FORBID);
+ if (shouldCaptureMouse != isMouseCurrentlyCaptured) {
+ GraphicsInterface.setMouseCaptured(shouldCaptureMouse);
+ }
+ }
+
for (int i = LAYERS.size() - 1; i >= 0; --i) {
LAYERS.get(i).render();
}
+
}
}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/Layer.java b/src/main/java/ru/windcorp/progressia/client/graphics/Layer.java
index ef6424c..b7ef5b4 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/Layer.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/Layer.java
@@ -30,15 +30,52 @@ public abstract class Layer {
private boolean hasInitialized = false;
private final AtomicBoolean isValid = new AtomicBoolean(false);
+
+ /**
+ * Represents various requests that a {@link Layer} can make regarding the
+ * presence of a visible cursor. The value of the highest layer that is not
+ * {@link #INDIFFERENT} is used.
+ */
+ public static enum CursorPolicy {
+ /**
+ * Require that a cursor is visible.
+ */
+ REQUIRE,
+
+ /**
+ * The {@link Layer} should not affect the presence or absence of a
+ * visible cursor; lower layers should be consulted.
+ */
+ INDIFFERENT,
+
+ /**
+ * Forbid a visible cursor.
+ */
+ FORBID
+ }
+
+ private CursorPolicy cursorPolicy = CursorPolicy.INDIFFERENT;
public Layer(String name) {
this.name = name;
}
+
+ public String getName() {
+ return name;
+ }
@Override
public String toString() {
return "Layer " + name;
}
+
+ public CursorPolicy getCursorPolicy() {
+ return cursorPolicy;
+ }
+
+ public void setCursorPolicy(CursorPolicy cursorPolicy) {
+ this.cursorPolicy = cursorPolicy;
+ }
void render() {
GraphicsInterface.startNextLayer();
@@ -78,5 +115,13 @@ public abstract class Layer {
protected int getHeight() {
return GraphicsInterface.getFrameHeight();
}
+
+ protected void onAdded() {
+ // Do nothing
+ }
+
+ protected void onRemoved() {
+ // Do nothing
+ }
}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/backend/GraphicsBackend.java b/src/main/java/ru/windcorp/progressia/client/graphics/backend/GraphicsBackend.java
index 233f3c9..879f54b 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/backend/GraphicsBackend.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/backend/GraphicsBackend.java
@@ -179,4 +179,18 @@ public class GraphicsBackend {
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
return vidmode.refreshRate();
}
+
+ public static boolean isMouseCaptured() {
+ return glfwGetInputMode(windowHandle, GLFW_CURSOR) == GLFW_CURSOR_DISABLED;
+ }
+
+ public static void setMouseCaptured(boolean capture) {
+ int mode = capture ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL;
+ glfwSetInputMode(windowHandle, GLFW_CURSOR, mode);
+
+ if (!capture) {
+ glfwSetCursorPos(windowHandle, FRAME_SIZE.x / 2.0, FRAME_SIZE.y / 2.0);
+ }
+ }
+
}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/backend/GraphicsInterface.java b/src/main/java/ru/windcorp/progressia/client/graphics/backend/GraphicsInterface.java
index 1fb18e3..ffe0add 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/backend/GraphicsInterface.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/backend/GraphicsInterface.java
@@ -81,5 +81,13 @@ public class GraphicsInterface {
}
GraphicsBackend.setVSyncEnabled(GraphicsBackend.isVSyncEnabled());
}
+
+ public static boolean isMouseCaptured() {
+ return GraphicsBackend.isMouseCaptured();
+ }
+
+ public static void setMouseCaptured(boolean capture) {
+ GraphicsBackend.setMouseCaptured(capture);
+ }
}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/backend/LWJGLInitializer.java b/src/main/java/ru/windcorp/progressia/client/graphics/backend/LWJGLInitializer.java
index e311316..6664266 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/backend/LWJGLInitializer.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/backend/LWJGLInitializer.java
@@ -65,8 +65,6 @@ class LWJGLInitializer {
GraphicsBackend.setWindowHandle(handle);
- glfwSetInputMode(handle, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
-
glfwMakeContextCurrent(handle);
glfwSwapInterval(0); // TODO: remove after config system is added
}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/flat/RenderTarget.java b/src/main/java/ru/windcorp/progressia/client/graphics/flat/RenderTarget.java
index 080b876..70f5471 100755
--- a/src/main/java/ru/windcorp/progressia/client/graphics/flat/RenderTarget.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/flat/RenderTarget.java
@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package ru.windcorp.progressia.client.graphics.flat;
import java.util.ArrayList;
@@ -29,8 +29,8 @@ import glm.vec._3.Vec3;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.backend.Usage;
-import ru.windcorp.progressia.client.graphics.model.Face;
-import ru.windcorp.progressia.client.graphics.model.Faces;
+import ru.windcorp.progressia.client.graphics.model.ShapePart;
+import ru.windcorp.progressia.client.graphics.model.ShapeParts;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.texture.Texture;
@@ -45,7 +45,11 @@ public class RenderTarget {
private final Mat4 transform;
private final Renderable renderable;
- public Clip(Iterable masks, Mat4 transform, Renderable renderable) {
+ public Clip(
+ Iterable masks,
+ Mat4 transform,
+ Renderable renderable
+ ) {
for (TransformedMask mask : masks) {
this.masks.pushMask(mask);
}
@@ -80,7 +84,7 @@ public class RenderTarget {
private final Deque maskStack = new LinkedList<>();
private final Deque transformStack = new LinkedList<>();
- private final List currentClipFaces = new ArrayList<>();
+ private final List currentClipFaces = new ArrayList<>();
private int depth = 0;
@@ -90,19 +94,33 @@ public class RenderTarget {
protected void assembleCurrentClipFromFaces() {
if (!currentClipFaces.isEmpty()) {
- Face[] faces = currentClipFaces.toArray(new Face[currentClipFaces.size()]);
+ ShapePart[] faces = currentClipFaces.toArray(
+ new ShapePart[currentClipFaces.size()]
+ );
currentClipFaces.clear();
- Shape shape = new Shape(Usage.STATIC, FlatRenderProgram.getDefault(), faces);
+ Shape shape = new Shape(
+ Usage.STATIC,
+ FlatRenderProgram.getDefault(),
+ faces
+ );
- assembled.add(new Clip(maskStack, getTransform(), shape));
+ assembled.add(
+ new Clip(
+ maskStack,
+ getTransform(),
+ shape
+ )
+ );
}
}
public Clip[] assemble() {
assembleCurrentClipFromFaces();
- Clip[] result = assembled.toArray(new Clip[assembled.size()]);
+ Clip[] result = assembled.toArray(
+ new Clip[assembled.size()]
+ );
reset();
@@ -124,11 +142,21 @@ public class RenderTarget {
pushTransform(new Mat4().identity().translate(startX, startY, 0));
- maskStack.push(new TransformedMask(new Mask(startX, startY, endX, endY), getTransform()));
+ maskStack.push(
+ new TransformedMask(
+ new Mask(startX, startY, endX, endY),
+ getTransform()
+ )
+ );
}
public void pushMask(Mask mask) {
- pushMaskStartEnd(mask.getStartX(), mask.getStartY(), mask.getEndX(), mask.getEndY());
+ pushMaskStartEnd(
+ mask.getStartX(),
+ mask.getStartY(),
+ mask.getEndX(),
+ mask.getEndY()
+ );
}
public void pushMaskStartSize(int x, int y, int width, int height) {
@@ -161,58 +189,139 @@ public class RenderTarget {
public void addCustomRenderer(Renderable renderable) {
assembleCurrentClipFromFaces();
- assembled.add(new Clip(maskStack, getTransform(), renderable));
+
+ float depth = this.depth--;
+ Mat4 transform = new Mat4().translate(0, 0, depth).mul(getTransform());
+ assembled.add(new Clip(maskStack, transform, renderable));
}
- protected void addFaceToCurrentClip(Face face) {
+ protected void addFaceToCurrentClip(ShapePart face) {
currentClipFaces.add(face);
}
- public void drawTexture(int x, int y, int width, int height, Vec4 color, Texture texture) {
- addFaceToCurrentClip(createRectagleFace(x, y, width, height, color, texture));
+ public void drawTexture(
+ int x,
+ int y,
+ int width,
+ int height,
+ Vec4 color,
+ Texture texture
+ ) {
+ addFaceToCurrentClip(
+ createRectagleFace(x, y, width, height, color, texture)
+ );
}
- public void drawTexture(int x, int y, int width, int height, int color, Texture texture) {
+ public void drawTexture(
+ int x,
+ int y,
+ int width,
+ int height,
+ int color,
+ Texture texture
+ ) {
drawTexture(x, y, width, height, Colors.toVector(color), texture);
}
- public void drawTexture(int x, int y, int width, int height, Texture texture) {
+ public void drawTexture(
+ int x,
+ int y,
+ int width,
+ int height,
+ Texture texture
+ ) {
drawTexture(x, y, width, height, Colors.WHITE, texture);
}
- public void fill(int x, int y, int width, int height, Vec4 color) {
+ public void fill(
+ int x,
+ int y,
+ int width,
+ int height,
+ Vec4 color
+ ) {
drawTexture(x, y, width, height, color, null);
}
- public void fill(int x, int y, int width, int height, int color) {
+ public void fill(
+ int x,
+ int y,
+ int width,
+ int height,
+ int color
+ ) {
fill(x, y, width, height, Colors.toVector(color));
}
public void fill(Vec4 color) {
- fill(Integer.MIN_VALUE / 2, Integer.MIN_VALUE / 2, Integer.MAX_VALUE, Integer.MAX_VALUE, color);
+ fill(
+ Integer.MIN_VALUE / 2,
+ Integer.MIN_VALUE / 2,
+ Integer.MAX_VALUE,
+ Integer.MAX_VALUE,
+ color
+ );
}
public void fill(int color) {
fill(Colors.toVector(color));
}
- public Face createRectagleFace(int x, int y, int width, int height, Vec4 color, Texture texture) {
+ public ShapePart createRectagleFace(
+ int x,
+ int y,
+ int width,
+ int height,
+ Vec4 color,
+ Texture texture
+ ) {
float depth = this.depth--;
- return Faces.createRectangle(FlatRenderProgram.getDefault(), texture, color, new Vec3(x, y, depth),
- new Vec3(width, 0, 0), new Vec3(0, height, 0), false);
+ return ShapeParts.createRectangle(
+ FlatRenderProgram.getDefault(),
+ texture,
+ color,
+ new Vec3(x, y, depth),
+ new Vec3(width, 0, 0),
+ new Vec3(0, height, 0),
+ false
+ );
}
- public Face createRectagleFace(int x, int y, int width, int height, int color, Texture texture) {
+ public ShapePart createRectagleFace(
+ int x,
+ int y,
+ int width,
+ int height,
+ int color,
+ Texture texture
+ ) {
return createRectagleFace(x, y, width, height, Colors.toVector(color), texture);
}
- public Shape createRectagle(int x, int y, int width, int height, Vec4 color, Texture texture) {
- return new Shape(Usage.STATIC, FlatRenderProgram.getDefault(),
- createRectagleFace(x, y, width, height, color, texture));
+ public Shape createRectagle(
+ int x,
+ int y,
+ int width,
+ int height,
+ Vec4 color,
+ Texture texture
+ ) {
+ return new Shape(
+ Usage.STATIC,
+ FlatRenderProgram.getDefault(),
+ createRectagleFace(x, y, width, height, color, texture)
+ );
}
- public Shape createRectagle(int x, int y, int width, int height, int color, Texture texture) {
+ public Shape createRectagle(
+ int x,
+ int y,
+ int width,
+ int height,
+ int color,
+ Texture texture
+ ) {
return createRectagle(x, y, width, height, Colors.toVector(color), texture);
}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/font/SpriteTypeface.java b/src/main/java/ru/windcorp/progressia/client/graphics/font/SpriteTypeface.java
index 4b13a79..d1db43a 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/font/SpriteTypeface.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/font/SpriteTypeface.java
@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package ru.windcorp.progressia.client.graphics.font;
import java.util.ArrayList;
@@ -33,8 +33,8 @@ import gnu.trove.stack.TIntStack;
import gnu.trove.stack.array.TIntArrayStack;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.backend.Usage;
-import ru.windcorp.progressia.client.graphics.model.Face;
-import ru.windcorp.progressia.client.graphics.model.Faces;
+import ru.windcorp.progressia.client.graphics.model.ShapePart;
+import ru.windcorp.progressia.client.graphics.model.ShapeParts;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
@@ -105,7 +105,13 @@ public abstract class SpriteTypeface extends Typeface {
public abstract ShapeRenderProgram getProgram();
@Override
- public Vec2i getSize(CharSequence chars, int style, float align, float maxWidth, Vec2i output) {
+ public Vec2i getSize(
+ CharSequence chars,
+ int style,
+ float align,
+ float maxWidth,
+ Vec2i output
+ ) {
if (output == null)
output = new Vec2i();
@@ -135,8 +141,19 @@ public abstract class SpriteTypeface extends Typeface {
}
private Shape createCharShape(char c) {
- return new Shape(Usage.STATIC, getProgram(), Faces.createRectangle(getProgram(), getTexture(c), Colors.WHITE,
- Vectors.ZERO_3, new Vec3(getWidth(c), 0, 0), new Vec3(0, getHeight(), 0), false));
+ return new Shape(
+ Usage.STATIC,
+ getProgram(),
+ ShapeParts.createRectangle(
+ getProgram(),
+ getTexture(c),
+ Colors.WHITE,
+ Vectors.ZERO_3,
+ new Vec3(getWidth(c), 0, 0),
+ new Vec3(0, getHeight(), 0),
+ false
+ )
+ );
}
private class DynamicText implements Renderable, Drawer {
@@ -147,8 +164,19 @@ public abstract class SpriteTypeface extends Typeface {
private final float maxWidth;
private final Vec4 color;
- private final Renderable unitLine = new Shape(Usage.STATIC, getProgram(), Faces.createRectangle(getProgram(),
- null, Vectors.UNIT_4, Vectors.ZERO_3, new Vec3(1, 0, 0), new Vec3(0, 1, 0), false));
+ private final Renderable unitLine = new Shape(
+ Usage.STATIC,
+ getProgram(),
+ ShapeParts.createRectangle(
+ getProgram(),
+ null,
+ Vectors.UNIT_4,
+ Vectors.ZERO_3,
+ new Vec3(1, 0, 0),
+ new Vec3(0, 1, 0),
+ false
+ )
+ );
private class DynamicWorkspace extends Workspace {
private ShapeRenderHelper renderer;
@@ -162,7 +190,13 @@ public abstract class SpriteTypeface extends Typeface {
private final DynamicWorkspace workspace = new DynamicWorkspace();
- public DynamicText(Supplier supplier, int style, float align, float maxWidth, Vec4 color) {
+ public DynamicText(
+ Supplier supplier,
+ int style,
+ float align,
+ float maxWidth,
+ Vec4 color
+ ) {
this.supplier = supplier;
this.style = style;
this.align = align;
@@ -223,7 +257,7 @@ public abstract class SpriteTypeface extends Typeface {
private class SDWorkspace extends SpriteTypeface.Workspace {
- private final Collection faces = new ArrayList<>();
+ private final Collection faces = new ArrayList<>();
private final Vec3 origin = new Vec3();
private final Vec3 width = new Vec3();
@@ -263,12 +297,25 @@ public abstract class SpriteTypeface extends Typeface {
workspace.width.sub(workspace.origin);
workspace.height.sub(workspace.origin);
- workspace.faces.add(Faces.createRectangle(getProgram(), texture, color, workspace.origin, workspace.width,
- workspace.height, false));
+ workspace.faces.add(
+ ShapeParts.createRectangle(
+ getProgram(),
+ texture,
+ color,
+ workspace.origin,
+ workspace.width,
+ workspace.height,
+ false
+ )
+ );
}
public Renderable assemble() {
- return new Shape(Usage.STATIC, getProgram(), workspace.faces.toArray(new Face[workspace.faces.size()]));
+ return new Shape(
+ Usage.STATIC,
+ getProgram(),
+ workspace.faces.toArray(new ShapePart[workspace.faces.size()])
+ );
}
}
@@ -281,8 +328,13 @@ public abstract class SpriteTypeface extends Typeface {
}
@Override
- public Renderable assembleDynamic(Supplier supplier, int style, float align, float maxWidth,
- Vec4 color) {
+ public Renderable assembleDynamic(
+ Supplier supplier,
+ int style,
+ float align,
+ float maxWidth,
+ Vec4 color
+ ) {
return new DynamicText(supplier, style, align, maxWidth, color);
}
@@ -372,8 +424,15 @@ public abstract class SpriteTypeface extends Typeface {
void drawRectangle(Vec2 size, Vec4 color, Mat4 transform);
}
- protected void draw(CharSequence text, Drawer drawer, Workspace workspace, int style, float align, float maxWidth,
- Vec4 color) {
+ protected void draw(
+ CharSequence text,
+ Drawer drawer,
+ Workspace workspace,
+ int style,
+ float align,
+ float maxWidth,
+ Vec4 color
+ ) {
workspace.text = text;
workspace.toIndex = text.length();
workspace.align = align;
@@ -430,7 +489,12 @@ public abstract class SpriteTypeface extends Typeface {
return w.align * (w.totalSize.x - w.currentWidth);
}
- private static final float[][] OUTLINE_DIRECTIONS = new float[][] { { 0, 1 }, { 1, 0 }, { -1, 0 }, { 0, -1 } };
+ private static final float[][] OUTLINE_DIRECTIONS = new float[][] {
+ { 0, 1 },
+ { 1, 0 },
+ { -1, 0 },
+ { 0, -1 }
+ };
private void drawLine(Drawer drawer, Workspace workspace) {
int style = workspace.styles.peek();
@@ -527,8 +591,11 @@ public abstract class SpriteTypeface extends Typeface {
workspace.styles.pop();
} else {
- throw new IllegalArgumentException("Style contains unknown flags " + Integer.toBinaryString(
- (style & ~(Style.BOLD | Style.ITALIC | Style.SHADOW | Style.STRIKETHRU | Style.UNDERLINED))));
+ throw new IllegalArgumentException(
+ "Style contains unknown flags " + Integer.toBinaryString(
+ (style & ~(Style.BOLD | Style.ITALIC | Style.SHADOW | Style.STRIKETHRU | Style.UNDERLINED))
+ )
+ );
}
}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/gui/BasicButton.java b/src/main/java/ru/windcorp/progressia/client/graphics/gui/BasicButton.java
new file mode 100644
index 0000000..cd30152
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/gui/BasicButton.java
@@ -0,0 +1,151 @@
+/*
+ * 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.client.graphics.gui;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+import org.lwjgl.glfw.GLFW;
+
+import com.google.common.eventbus.Subscribe;
+
+import ru.windcorp.progressia.client.graphics.font.Font;
+import ru.windcorp.progressia.client.graphics.gui.event.ButtonEvent;
+import ru.windcorp.progressia.client.graphics.gui.event.EnableEvent;
+import ru.windcorp.progressia.client.graphics.gui.event.FocusEvent;
+import ru.windcorp.progressia.client.graphics.gui.event.HoverEvent;
+import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
+import ru.windcorp.progressia.client.graphics.input.KeyEvent;
+
+public abstract class BasicButton extends Component {
+
+ private final Label label;
+
+ private boolean isPressed = false;
+ private final Collection> actions = Collections.synchronizedCollection(new ArrayList<>());
+
+ public BasicButton(String name, String label, Font labelFont) {
+ super(name);
+ this.label = new Label(name + ".Label", labelFont, label);
+
+ setLayout(new LayoutAlign(10));
+ addChild(this.label);
+
+ setFocusable(true);
+ reassembleAt(ARTrigger.HOVER, ARTrigger.FOCUS, ARTrigger.ENABLE);
+
+ // Click triggers
+ addListener(KeyEvent.class, e -> {
+ if (e.isRepeat()) {
+ return false;
+ } else if (
+ e.isLeftMouseButton() ||
+ e.getKey() == GLFW.GLFW_KEY_SPACE ||
+ e.getKey() == GLFW.GLFW_KEY_ENTER
+ ) {
+ setPressed(e.isPress());
+ return true;
+ } else {
+ return false;
+ }
+ });
+
+ addListener(new Object() {
+
+ // Release when losing focus
+ @Subscribe
+ public void onFocusChange(FocusEvent e) {
+ if (!e.getNewState()) {
+ setPressed(false);
+ }
+ }
+
+ // Release when hover ends
+ @Subscribe
+ public void onHoverEnded(HoverEvent e) {
+ if (!e.isNowHovered()) {
+ setPressed(false);
+ }
+ }
+
+ // Release when disabled
+ @Subscribe
+ public void onDisabled(EnableEvent e) {
+ if (!e.getComponent().isEnabled()) {
+ setPressed(false);
+ }
+ }
+
+ // Trigger virtualClick when button is released
+ @Subscribe
+ public void onRelease(ButtonEvent.Release e) {
+ virtualClick();
+ }
+
+ });
+ }
+
+ public BasicButton(String name, String label) {
+ this(name, label, new Font());
+ }
+
+ public boolean isPressed() {
+ return isPressed;
+ }
+
+ public void click() {
+ setPressed(true);
+ setPressed(false);
+ }
+
+ public void setPressed(boolean isPressed) {
+ if (this.isPressed != isPressed) {
+ this.isPressed = isPressed;
+
+ if (isPressed) {
+ takeFocus();
+ }
+
+ dispatchEvent(ButtonEvent.create(this, this.isPressed));
+ }
+ }
+
+ public BasicButton addAction(Consumer action) {
+ this.actions.add(Objects.requireNonNull(action, "action"));
+ return this;
+ }
+
+ public boolean removeAction(Consumer action) {
+ return this.actions.remove(action);
+ }
+
+ public void virtualClick() {
+ this.actions.forEach(action -> {
+ action.accept(this);
+ });
+ }
+
+ public Label getLabel() {
+ return label;
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/gui/Button.java b/src/main/java/ru/windcorp/progressia/client/graphics/gui/Button.java
new file mode 100644
index 0000000..bbeb361
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/gui/Button.java
@@ -0,0 +1,79 @@
+/*
+ * 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.client.graphics.gui;
+
+import glm.vec._4.Vec4;
+import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
+import ru.windcorp.progressia.client.graphics.font.Font;
+import ru.windcorp.progressia.client.graphics.Colors;
+
+public class Button extends BasicButton {
+
+ public Button(String name, String label, Font labelFont) {
+ super(name, label, labelFont);
+ }
+
+ public Button(String name, String label) {
+ this(name, label, new Font());
+ }
+
+ @Override
+ protected void assembleSelf(RenderTarget target) {
+ // Border
+
+ Vec4 borderColor;
+ if (isPressed() || isHovered() || isFocused()) {
+ borderColor = Colors.BLUE;
+ } else {
+ borderColor = Colors.LIGHT_GRAY;
+ }
+ target.fill(getX(), getY(), getWidth(), getHeight(), borderColor);
+
+ // Inside area
+
+ if (isPressed()) {
+ // Do nothing
+ } else {
+ Vec4 backgroundColor;
+ if (isHovered() && isEnabled()) {
+ backgroundColor = Colors.HOVER_BLUE;
+ } else {
+ backgroundColor = Colors.WHITE;
+ }
+ target.fill(getX() + 2, getY() + 2, getWidth() - 4, getHeight() - 4, backgroundColor);
+ }
+
+ // Change label font color
+
+ if (isPressed()) {
+ getLabel().setFont(getLabel().getFont().withColor(Colors.WHITE));
+ } else {
+ getLabel().setFont(getLabel().getFont().withColor(Colors.BLACK));
+ }
+ }
+
+ @Override
+ protected void postAssembleSelf(RenderTarget target) {
+ // Apply disable tint
+
+ if (!isEnabled()) {
+ target.fill(getX(), getY(), getWidth(), getHeight(), Colors.toVector(0x88FFFFFF));
+ }
+ }
+}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/gui/Checkbox.java b/src/main/java/ru/windcorp/progressia/client/graphics/gui/Checkbox.java
new file mode 100644
index 0000000..5f9d0df
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/gui/Checkbox.java
@@ -0,0 +1,149 @@
+/*
+ * 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.client.graphics.gui;
+
+import glm.vec._2.i.Vec2i;
+import glm.vec._4.Vec4;
+import ru.windcorp.progressia.client.graphics.Colors;
+import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
+import ru.windcorp.progressia.client.graphics.font.Font;
+import ru.windcorp.progressia.client.graphics.font.Typefaces;
+import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
+import ru.windcorp.progressia.client.graphics.gui.layout.LayoutHorizontal;
+
+public class Checkbox extends BasicButton {
+
+ private class Tick extends Component {
+
+ public Tick() {
+ super(Checkbox.this.getName() + ".Tick");
+
+ setPreferredSize(new Vec2i(Typefaces.getDefault().getLineHeight() * 3 / 2));
+ }
+
+ @Override
+ protected void assembleSelf(RenderTarget target) {
+
+ int size = getPreferredSize().x;
+ int x = getX();
+ int y = getY() + (getHeight() - size) / 2;
+
+ // Border
+
+ Vec4 borderColor;
+ if (Checkbox.this.isPressed() || Checkbox.this.isHovered() || Checkbox.this.isFocused()) {
+ borderColor = Colors.BLUE;
+ } else {
+ borderColor = Colors.LIGHT_GRAY;
+ }
+ target.fill(x, y, size, size, borderColor);
+
+ // Inside area
+
+ if (Checkbox.this.isPressed()) {
+ // Do nothing
+ } else {
+ Vec4 backgroundColor;
+ if (Checkbox.this.isHovered() && Checkbox.this.isEnabled()) {
+ backgroundColor = Colors.HOVER_BLUE;
+ } else {
+ backgroundColor = Colors.WHITE;
+ }
+ target.fill(x + 2, y + 2, size - 4, size - 4, backgroundColor);
+ }
+
+ // "Tick"
+
+ if (Checkbox.this.isChecked()) {
+ target.fill(x + 4, y + 4, size - 8, size - 8, Colors.BLUE);
+ }
+ }
+
+ }
+
+ private boolean checked;
+
+ public Checkbox(String name, String label, Font labelFont, boolean check) {
+ super(name, label, labelFont);
+ this.checked = check;
+
+ assert getChildren().size() == 1 : "Checkbox expects that BasicButton contains exactly one child";
+ Component basicChild = getChild(0);
+
+ Group group = new Group(getName() + ".LabelAndTick", new LayoutHorizontal(0, 10));
+ removeChild(basicChild);
+ setLayout(new LayoutAlign(0, 0.5f, 10));
+ group.setLayoutHint(basicChild.getLayoutHint());
+ group.addChild(new Tick());
+ group.addChild(basicChild);
+ addChild(group);
+
+ addAction(b -> switchState());
+ }
+
+ public Checkbox(String name, String label, Font labelFont) {
+ this(name, label, labelFont, false);
+ }
+
+ public Checkbox(String name, String label, boolean check) {
+ this(name, label, new Font(), check);
+ }
+
+ public Checkbox(String name, String label) {
+ this(name, label, false);
+ }
+
+ public void switchState() {
+ setChecked(!isChecked());
+ }
+
+ /**
+ * @return the checked
+ */
+ public boolean isChecked() {
+ return checked;
+ }
+
+ /**
+ * @param checked the checked to set
+ */
+ public void setChecked(boolean checked) {
+ this.checked = checked;
+ }
+
+ @Override
+ protected void assembleSelf(RenderTarget target) {
+ // Change label font color
+
+ if (isPressed()) {
+ getLabel().setFont(getLabel().getFont().withColor(Colors.BLUE));
+ } else {
+ getLabel().setFont(getLabel().getFont().withColor(Colors.BLACK));
+ }
+ }
+
+ @Override
+ protected void postAssembleSelf(RenderTarget target) {
+ // Apply disable tint
+
+ if (!isEnabled()) {
+ target.fill(getX(), getY(), getWidth(), getHeight(), Colors.toVector(0x88FFFFFF));
+ }
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/gui/Component.java b/src/main/java/ru/windcorp/progressia/client/graphics/gui/Component.java
index 05416e8..f9db88f 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/gui/Component.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/gui/Component.java
@@ -19,18 +19,23 @@
package ru.windcorp.progressia.client.graphics.gui;
import java.util.Collections;
+import java.util.EnumMap;
import java.util.List;
+import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import org.lwjgl.glfw.GLFW;
import com.google.common.eventbus.EventBus;
+import com.google.common.eventbus.Subscribe;
import glm.vec._2.i.Vec2i;
import ru.windcorp.progressia.client.graphics.backend.InputTracker;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
import ru.windcorp.progressia.client.graphics.gui.event.ChildAddedEvent;
import ru.windcorp.progressia.client.graphics.gui.event.ChildRemovedEvent;
+import ru.windcorp.progressia.client.graphics.gui.event.EnableEvent;
import ru.windcorp.progressia.client.graphics.gui.event.FocusEvent;
import ru.windcorp.progressia.client.graphics.gui.event.HoverEvent;
import ru.windcorp.progressia.client.graphics.gui.event.ParentChangedEvent;
@@ -61,6 +66,8 @@ public class Component extends Named {
private Object layoutHint = null;
private Layout layout = null;
+
+ private boolean isEnabled = true;
private boolean isFocusable = false;
private boolean isFocused = false;
@@ -285,9 +292,30 @@ public class Component extends Named {
return this;
}
+ /**
+ * Checks whether this component is focusable. A component needs to be
+ * focusable to become focused. A component that is focusable may not
+ * necessarily be ready to gain focus (see {@link #canGainFocusNow()}).
+ *
+ * @return {@code true} iff the component is focusable
+ * @see #canGainFocusNow()
+ */
public boolean isFocusable() {
return isFocusable;
}
+
+ /**
+ * Checks whether this component can become focused at this moment.
+ *
+ * The implementation of this method in {@link Component} considers the
+ * component a focus candidate if it is both focusable and enabled.
+ *
+ * @return {@code true} iff the component can receive focus
+ * @see #isFocusable()
+ */
+ public boolean canGainFocusNow() {
+ return isFocusable() && isEnabled();
+ }
public Component setFocusable(boolean focusable) {
this.isFocusable = focusable;
@@ -337,7 +365,7 @@ public class Component extends Named {
return;
}
- if (component.isFocusable()) {
+ if (component.canGainFocusNow()) {
setFocused(false);
component.setFocused(true);
return;
@@ -379,7 +407,7 @@ public class Component extends Named {
return;
}
- if (component.isFocusable()) {
+ if (component.canGainFocusNow()) {
setFocused(false);
component.setFocused(true);
return;
@@ -432,13 +460,52 @@ public class Component extends Named {
return null;
}
+
+ public boolean isEnabled() {
+ return isEnabled;
+ }
+
+ /**
+ * Enables or disables this component. An {@link EnableEvent} is dispatched
+ * if the state changes.
+ *
+ * @param enabled {@code true} to enable the component, {@code false} to
+ * disable the component
+ * @see #setEnabledRecursively(boolean)
+ */
+ public void setEnabled(boolean enabled) {
+ if (this.isEnabled != enabled) {
+ if (isFocused() && isEnabled()) {
+ focusNext();
+ }
+
+ if (isEnabled()) {
+ setHovered(false);
+ }
+
+ this.isEnabled = enabled;
+ dispatchEvent(new EnableEvent(this));
+ }
+ }
+
+ /**
+ * Enables or disables this component and all of its children recursively.
+ *
+ * @param enabled {@code true} to enable the components, {@code false} to
+ * disable the components
+ * @see #setEnabled(boolean)
+ */
+ public void setEnabledRecursively(boolean enabled) {
+ setEnabled(enabled);
+ getChildren().forEach(c -> c.setEnabledRecursively(enabled));
+ }
public boolean isHovered() {
return isHovered;
}
protected void setHovered(boolean isHovered) {
- if (this.isHovered != isHovered) {
+ if (this.isHovered != isHovered && isEnabled()) {
this.isHovered = isHovered;
if (!isHovered && !getChildren().isEmpty()) {
@@ -499,7 +566,7 @@ public class Component extends Named {
}
protected void handleInput(Input input) {
- if (inputBus != null) {
+ if (inputBus != null && isEnabled()) {
inputBus.dispatch(input);
}
}
@@ -595,6 +662,17 @@ public class Component extends Named {
}
}
+ /**
+ * Schedules the reassembly to occur.
+ *
+ * This method is invoked in root components whenever a
+ * {@linkplain #requestReassembly() reassembly request} is made by one of
+ * its children. When creating the dedicated root component, override this
+ * method to perform any implementation-specific actions that will cause a
+ * reassembly as soon as possible.
+ *
+ * The default implementation of this method does nothing.
+ */
protected void handleReassemblyRequest() {
// To be overridden
}
@@ -634,6 +712,135 @@ public class Component extends Named {
protected void assembleChildren(RenderTarget target) {
getChildren().forEach(child -> child.assemble(target));
}
+
+ /*
+ * Automatic Reassembly
+ */
+
+ /**
+ * The various kinds of changes that may be used with
+ * {@link Component#reassembleAt(ARTrigger...)}.
+ */
+ protected static enum ARTrigger {
+ /**
+ * Reassemble the component whenever its hover status changes, e.g.
+ * whenever the pointer enters or leaves its bounds.
+ */
+ HOVER,
+
+ /**
+ * Reassemble the component whenever it gains or loses focus.
+ *
+ * Component must be focusable to be able to gain focus. The
+ * component will not be reassembled unless
+ * {@link Component#setFocusable(boolean) setFocusable(true)} has been
+ * invoked.
+ */
+ FOCUS,
+
+ /**
+ * Reassemble the component whenever it is enabled or disabled.
+ */
+ ENABLE
+ }
+
+ /**
+ * All trigger objects (event listeners) that are currently registered with
+ * {@link #eventBus}. The field is {@code null} until the first trigger is
+ * installed.
+ */
+ private Map autoReassemblyTriggerObjects = null;
+
+ private Object createTriggerObject(ARTrigger type) {
+ switch (type) {
+ case HOVER:
+ return new Object() {
+ @Subscribe
+ public void onHoverChanged(HoverEvent e) {
+ requestReassembly();
+ }
+ };
+ case FOCUS:
+ return new Object() {
+ @Subscribe
+ public void onFocusChanged(FocusEvent e) {
+ requestReassembly();
+ }
+ };
+ case ENABLE:
+ return new Object() {
+ @Subscribe
+ public void onEnabled(EnableEvent e) {
+ requestReassembly();
+ }
+ };
+ default:
+ throw new NullPointerException("type");
+ }
+ }
+
+ /**
+ * Requests that {@link #requestReassembly()} is invoked on this component
+ * whenever any of the specified changes occur. Duplicate attempts to
+ * register the same trigger are silently ignored.
+ *
+ * {@code triggers} may be empty, which results in a no-op. It must not be
+ * {@code null}.
+ *
+ * @param triggers the {@linkplain ARTrigger triggers} to
+ * request reassembly with.
+ * @see #disableAutoReassemblyAt(ARTrigger...)
+ */
+ protected synchronized void reassembleAt(ARTrigger... triggers) {
+
+ Objects.requireNonNull(triggers, "triggers");
+ if (triggers.length == 0)
+ return;
+
+ if (autoReassemblyTriggerObjects == null) {
+ autoReassemblyTriggerObjects = new EnumMap<>(ARTrigger.class);
+ }
+
+ for (ARTrigger trigger : triggers) {
+ if (!autoReassemblyTriggerObjects.containsKey(trigger)) {
+ Object triggerObject = createTriggerObject(trigger);
+ addListener(trigger);
+ autoReassemblyTriggerObjects.put(trigger, triggerObject);
+ }
+ }
+
+ }
+
+ /**
+ * Requests that {@link #requestReassembly()} is no longer invoked on this
+ * component whenever any of the specified changes occur. After a trigger is
+ * removed, it may be reinstalled with
+ * {@link #reassembleAt(ARTrigger...)}. Attempts to remove a
+ * nonexistant trigger are silently ignored.
+ *
+ * {@code triggers} may be empty, which results in a no-op. It must not be
+ * {@code null}.
+ *
+ * @param triggers the {@linkplain ARTrigger triggers} to remove
+ * @see #reassemblyAt(ARTrigger...)
+ */
+ protected synchronized void disableAutoReassemblyAt(ARTrigger... triggers) {
+
+ Objects.requireNonNull(triggers, "triggers");
+ if (triggers.length == 0)
+ return;
+
+ if (autoReassemblyTriggerObjects == null)
+ return;
+
+ for (ARTrigger trigger : triggers) {
+ Object triggerObject = autoReassemblyTriggerObjects.remove(trigger);
+ if (triggerObject != null) {
+ removeListener(trigger);
+ }
+ }
+
+ }
// /**
// * Returns a component that displays this component in its center.
diff --git a/src/main/java/ru/windcorp/progressia/common/world/tile/TileReference.java b/src/main/java/ru/windcorp/progressia/client/graphics/gui/Group.java
similarity index 78%
rename from src/main/java/ru/windcorp/progressia/common/world/tile/TileReference.java
rename to src/main/java/ru/windcorp/progressia/client/graphics/gui/Group.java
index b8d593f..d8a8b23 100644
--- a/src/main/java/ru/windcorp/progressia/common/world/tile/TileReference.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/gui/Group.java
@@ -15,19 +15,14 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
+
+package ru.windcorp.progressia.client.graphics.gui;
-package ru.windcorp.progressia.common.world.tile;
+public class Group extends Component {
-public interface TileReference {
-
- TileData get();
-
- int getIndex();
-
- TileDataStack getStack();
-
- default boolean isValid() {
- return get() != null;
+ public Group(String name, Layout layout) {
+ super(name);
+ setLayout(layout);
}
}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/gui/Label.java b/src/main/java/ru/windcorp/progressia/client/graphics/gui/Label.java
index cb074da..4450f33 100755
--- a/src/main/java/ru/windcorp/progressia/client/graphics/gui/Label.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/gui/Label.java
@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package ru.windcorp.progressia.client.graphics.gui;
import glm.mat._4.Mat4;
@@ -82,6 +82,11 @@ public class Label extends Component {
public Font getFont() {
return font;
}
+
+ public void setFont(Font font) {
+ this.font = font;
+ requestReassembly();
+ }
public String getCurrentText() {
return currentText;
@@ -95,13 +100,9 @@ public class Label extends Component {
protected void assembleSelf(RenderTarget target) {
float startX = getX() + font.getAlign() * (getWidth() - currentSize.x);
- target.pushTransform(new Mat4().identity().translate(startX, getY(), -1000) // TODO
- // wtf
- // is
- // this
- // magic
- // <---
- .scale(2));
+ target.pushTransform(
+ new Mat4().identity().translate(startX, getY(), 0).scale(2)
+ );
target.addCustomRenderer(font.assemble(currentText, maxWidth));
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/gui/Panel.java b/src/main/java/ru/windcorp/progressia/client/graphics/gui/Panel.java
index 7f0cba9..90357ef 100755
--- a/src/main/java/ru/windcorp/progressia/client/graphics/gui/Panel.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/gui/Panel.java
@@ -15,14 +15,66 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
package ru.windcorp.progressia.client.graphics.gui;
-public class Panel extends Component {
+import java.util.Objects;
+import glm.vec._4.Vec4;
+import ru.windcorp.progressia.client.graphics.Colors;
+import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
+
+public class Panel extends Group {
+
+ private Vec4 fill;
+ private Vec4 border;
+
+ public Panel(String name, Layout layout, Vec4 fill, Vec4 border) {
+ super(name, layout);
+
+ this.fill = Objects.requireNonNull(fill, "fill");
+ this.border = border;
+ }
+
public Panel(String name, Layout layout) {
- super(name);
- setLayout(layout);
+ this(name, layout, Colors.WHITE, Colors.LIGHT_GRAY);
+ }
+
+ /**
+ * @return the fill
+ */
+ public Vec4 getFill() {
+ return fill;
+ }
+
+ /**
+ * @param fill the fill to set
+ */
+ public void setFill(Vec4 fill) {
+ this.fill = Objects.requireNonNull(fill, "fill");
+ }
+
+ /**
+ * @return the border
+ */
+ public Vec4 getBorder() {
+ return border;
+ }
+
+ /**
+ * @param border the border to set
+ */
+ public void setBorder(Vec4 border) {
+ this.border = border;
+ }
+
+ @Override
+ protected void assembleSelf(RenderTarget target) {
+ if (border == null) {
+ target.fill(getX(), getY(), getWidth(), getHeight(), fill);
+ } else {
+ target.fill(getX(), getY(), getWidth(), getHeight(), border);
+ target.fill(getX() + 2, getY() + 2, getWidth() - 4, getHeight() - 4, fill);
+ }
}
}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/gui/RadioButton.java b/src/main/java/ru/windcorp/progressia/client/graphics/gui/RadioButton.java
new file mode 100644
index 0000000..471efb6
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/gui/RadioButton.java
@@ -0,0 +1,205 @@
+/*
+ * 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.client.graphics.gui;
+
+import org.lwjgl.glfw.GLFW;
+
+import glm.vec._2.i.Vec2i;
+import glm.vec._4.Vec4;
+import ru.windcorp.progressia.client.graphics.Colors;
+import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
+import ru.windcorp.progressia.client.graphics.font.Font;
+import ru.windcorp.progressia.client.graphics.font.Typefaces;
+import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
+import ru.windcorp.progressia.client.graphics.gui.layout.LayoutHorizontal;
+import ru.windcorp.progressia.client.graphics.input.KeyEvent;
+
+public class RadioButton extends BasicButton {
+
+ private class Tick extends Component {
+
+ public Tick() {
+ super(RadioButton.this.getName() + ".Tick");
+
+ setPreferredSize(new Vec2i(Typefaces.getDefault().getLineHeight() * 3 / 2));
+ }
+
+ private void cross(RenderTarget target, int x, int y, int size, Vec4 color) {
+ target.fill(x + 4, y, size - 8, size, color);
+ target.fill(x + 2, y + 2, size - 4, size - 4, color);
+ target.fill(x, y + 4, size, size - 8, color);
+ }
+
+ @Override
+ protected void assembleSelf(RenderTarget target) {
+
+ int size = getPreferredSize().x;
+ int x = getX();
+ int y = getY() + (getHeight() - size) / 2;
+
+ // Border
+
+ Vec4 borderColor;
+ if (RadioButton.this.isPressed() || RadioButton.this.isHovered() || RadioButton.this.isFocused()) {
+ borderColor = Colors.BLUE;
+ } else {
+ borderColor = Colors.LIGHT_GRAY;
+ }
+ cross(target, x, y, size, borderColor);
+
+ // Inside area
+
+ if (RadioButton.this.isPressed()) {
+ // Do nothing
+ } else {
+ Vec4 backgroundColor;
+ if (RadioButton.this.isHovered() && RadioButton.this.isEnabled()) {
+ backgroundColor = Colors.HOVER_BLUE;
+ } else {
+ backgroundColor = Colors.WHITE;
+ }
+ cross(target, x + 2, y + 2, size - 4, backgroundColor);
+ }
+
+ // "Tick"
+
+ if (RadioButton.this.isChecked()) {
+ cross(target, x + 4, y + 4, size - 8, Colors.BLUE);
+ }
+ }
+
+ }
+
+ private boolean checked;
+
+ private RadioButtonGroup group = null;
+
+ public RadioButton(String name, String label, Font labelFont, boolean check) {
+ super(name, label, labelFont);
+ this.checked = check;
+
+ assert getChildren().size() == 1 : "RadioButton expects that BasicButton contains exactly one child";
+ Component basicChild = getChild(0);
+
+ Group group = new Group(getName() + ".LabelAndTick", new LayoutHorizontal(0, 10));
+ removeChild(basicChild);
+ setLayout(new LayoutAlign(0, 0.5f, 10));
+ group.setLayoutHint(basicChild.getLayoutHint());
+ group.addChild(new Tick());
+ group.addChild(basicChild);
+ addChild(group);
+
+ addListener(KeyEvent.class, e -> {
+ if (e.isRelease()) return false;
+
+ if (e.getKey() == GLFW.GLFW_KEY_LEFT || e.getKey() == GLFW.GLFW_KEY_UP) {
+ if (this.group != null) {
+ this.group.selectPrevious();
+ this.group.getSelected().takeFocus();
+ }
+
+ return true;
+ } else if (e.getKey() == GLFW.GLFW_KEY_RIGHT || e.getKey() == GLFW.GLFW_KEY_DOWN) {
+ if (this.group != null) {
+ this.group.selectNext();
+ this.group.getSelected().takeFocus();
+ }
+ return true;
+ }
+
+ return false;
+ });
+
+ addAction(b -> setChecked(true));
+ }
+
+ public RadioButton(String name, String label, Font labelFont) {
+ this(name, label, labelFont, false);
+ }
+
+ public RadioButton(String name, String label, boolean check) {
+ this(name, label, new Font(), check);
+ }
+
+ public RadioButton(String name, String label) {
+ this(name, label, false);
+ }
+
+ /**
+ * @param group the group to set
+ */
+ public RadioButton setGroup(RadioButtonGroup group) {
+
+ if (this.group != null) {
+ group.selectNext();
+ removeAction(group.listener);
+ group.buttons.remove(this);
+ group.getSelected(); // Clear reference if this was the only button in the group
+ }
+
+ this.group = group;
+
+ if (this.group != null) {
+ group.buttons.add(this);
+ addAction(group.listener);
+ }
+
+ setChecked(false);
+
+ return this;
+ }
+
+ /**
+ * @return the checked
+ */
+ public boolean isChecked() {
+ return checked;
+ }
+
+ /**
+ * @param checked the checked to set
+ */
+ public void setChecked(boolean checked) {
+ this.checked = checked;
+
+ if (group != null) {
+ group.listener.accept(this); // Failsafe for manual invocations of setChecked()
+ }
+ }
+
+ @Override
+ protected void assembleSelf(RenderTarget target) {
+ // Change label font color
+
+ if (isPressed()) {
+ getLabel().setFont(getLabel().getFont().withColor(Colors.BLUE));
+ } else {
+ getLabel().setFont(getLabel().getFont().withColor(Colors.BLACK));
+ }
+ }
+
+ @Override
+ protected void postAssembleSelf(RenderTarget target) {
+ // Apply disable tint
+
+ if (!isEnabled()) {
+ target.fill(getX(), getY(), getWidth(), getHeight(), Colors.toVector(0x88FFFFFF));
+ }
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/gui/RadioButtonGroup.java b/src/main/java/ru/windcorp/progressia/client/graphics/gui/RadioButtonGroup.java
new file mode 100644
index 0000000..3887018
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/gui/RadioButtonGroup.java
@@ -0,0 +1,119 @@
+/*
+ * 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.client.graphics.gui;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+public class RadioButtonGroup {
+
+ private final Collection> actions = Collections.synchronizedCollection(new ArrayList<>());
+ final List buttons = Collections.synchronizedList(new ArrayList<>());
+
+ private RadioButton selected = null;
+
+ Consumer listener = b -> {
+ if (b instanceof RadioButton && ((RadioButton) b).isChecked() && buttons.contains(b)) {
+ select((RadioButton) b);
+ }
+ };
+
+ public RadioButtonGroup addAction(Consumer action) {
+ this.actions.add(Objects.requireNonNull(action, "action"));
+ return this;
+ }
+
+ public boolean removeAction(Consumer action) {
+ return this.actions.remove(action);
+ }
+
+ public List getButtons() {
+ return Collections.unmodifiableList(buttons);
+ }
+
+ public synchronized RadioButton getSelected() {
+ if (!buttons.contains(selected)) {
+ selected = null;
+ }
+ return selected;
+ }
+
+ public synchronized void select(RadioButton button) {
+ if (button != null && !buttons.contains(button)) {
+ throw new IllegalArgumentException("Button " + button + " is not in the group");
+ }
+
+ getSelected(); // Clear if invalid
+
+ if (selected == button) {
+ return; // Terminate listener-setter recursion
+ }
+
+ if (selected != null) {
+ selected.setChecked(false);
+ }
+
+ selected = button;
+
+ if (selected != null) {
+ selected.setChecked(true);
+ }
+
+ actions.forEach(action -> action.accept(this));
+ }
+
+ public void selectNext() {
+ selectNeighbour(+1);
+ }
+
+ public void selectPrevious() {
+ selectNeighbour(-1);
+ }
+
+ private synchronized void selectNeighbour(int direction) {
+ if (getSelected() == null) {
+ if (buttons.isEmpty()) {
+ throw new IllegalStateException("Cannot select neighbour button: group empty");
+ }
+
+ select(buttons.get(0));
+ } else {
+ RadioButton button;
+ int index = buttons.indexOf(selected);
+
+ do {
+ index += direction;
+
+ if (index >= buttons.size()) {
+ index = 0;
+ } else if (index < 0) {
+ index = buttons.size() - 1;
+ }
+
+ button = buttons.get(index);
+ } while (button != getSelected() && !button.isEnabled());
+
+ select(button);
+ }
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/gui/event/ButtonEvent.java b/src/main/java/ru/windcorp/progressia/client/graphics/gui/event/ButtonEvent.java
new file mode 100644
index 0000000..071f06e
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/gui/event/ButtonEvent.java
@@ -0,0 +1,60 @@
+/*
+ * 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.client.graphics.gui.event;
+
+import ru.windcorp.progressia.client.graphics.gui.BasicButton;
+
+public class ButtonEvent extends ComponentEvent {
+
+ public static class Press extends ButtonEvent {
+ public Press(BasicButton button) {
+ super(button, true);
+ }
+ }
+
+ public static class Release extends ButtonEvent {
+ public Release(BasicButton button) {
+ super(button, false);
+ }
+ }
+
+ private final boolean isPress;
+
+ protected ButtonEvent(BasicButton button, boolean isPress) {
+ super(button);
+ this.isPress = isPress;
+ }
+
+ public static ButtonEvent create(BasicButton button, boolean isPress) {
+ if (isPress) {
+ return new Press(button);
+ } else {
+ return new Release(button);
+ }
+ }
+
+ public boolean isPress() {
+ return isPress;
+ }
+
+ public boolean isRelease() {
+ return !isPress;
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/gui/event/EnableEvent.java b/src/main/java/ru/windcorp/progressia/client/graphics/gui/event/EnableEvent.java
new file mode 100644
index 0000000..f56df2c
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/gui/event/EnableEvent.java
@@ -0,0 +1,11 @@
+package ru.windcorp.progressia.client.graphics.gui.event;
+
+import ru.windcorp.progressia.client.graphics.gui.Component;
+
+public class EnableEvent extends ComponentEvent {
+
+ public EnableEvent(Component component) {
+ super(component);
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/gui/layout/LayoutFill.java b/src/main/java/ru/windcorp/progressia/client/graphics/gui/layout/LayoutFill.java
new file mode 100644
index 0000000..c65fc4a
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/gui/layout/LayoutFill.java
@@ -0,0 +1,78 @@
+/*
+ * 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.client.graphics.gui.layout;
+
+import static java.lang.Math.max;
+
+import glm.vec._2.i.Vec2i;
+import ru.windcorp.progressia.client.graphics.gui.Component;
+import ru.windcorp.progressia.client.graphics.gui.Layout;
+
+public class LayoutFill implements Layout {
+
+ private final int margin;
+
+ public LayoutFill(int margin) {
+ this.margin = margin;
+ }
+
+ public LayoutFill() {
+ this(0);
+ }
+
+ @Override
+ public void layout(Component c) {
+ c.getChildren().forEach(child -> {
+
+ int cWidth = c.getWidth() - 2 * margin;
+ int cHeight = c.getHeight() - 2 * margin;
+
+ child.setBounds(
+ c.getX() + margin,
+ c.getY() + margin,
+ cWidth,
+ cHeight
+ );
+
+ });
+ }
+
+ @Override
+ public Vec2i calculatePreferredSize(Component c) {
+ Vec2i result = new Vec2i(0, 0);
+
+ c.getChildren().stream()
+ .map(child -> child.getPreferredSize())
+ .forEach(size -> {
+ result.x = max(size.x, result.x);
+ result.y = max(size.y, result.y);
+ });
+
+ result.x += 2 * margin;
+ result.y += 2 * margin;
+
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "(" + margin + ")";
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/gui/layout/LayoutGrid.java b/src/main/java/ru/windcorp/progressia/client/graphics/gui/layout/LayoutGrid.java
index 20aebc1..a164378 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/gui/layout/LayoutGrid.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/gui/layout/LayoutGrid.java
@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package ru.windcorp.progressia.client.graphics.gui.layout;
import java.util.Arrays;
@@ -97,14 +97,28 @@ public class LayoutGrid implements Layout {
void setBounds(int column, int row, Component child, Component parent) {
if (!isSummed)
throw new IllegalStateException("Not summed yet");
+
+ int width, height;
+
+ if (column == columns.length - 1) {
+ width = parent.getWidth() - margin - columns[column];
+ } else {
+ width = columns[column + 1] - columns[column] - gap;
+ }
+
+ if (row == rows.length - 1) {
+ height = parent.getHeight() - margin - rows[row];
+ } else {
+ height = rows[row + 1] - rows[row] - gap;
+ }
- child.setBounds(parent.getX() + columns[column], parent.getY() + rows[row],
+ child.setBounds(
+ parent.getX() + columns[column],
+ parent.getY() + parent.getHeight() - (rows[row] + height),
- (column != (columns.length - 1) ? (columns[column + 1] - columns[column] - gap)
- : (parent.getWidth() - margin - columns[column])),
-
- (row != (rows.length - 1) ? (rows[row + 1] - rows[row] - gap)
- : (parent.getHeight() - margin - rows[row])));
+ width,
+ height
+ );
}
}
@@ -129,10 +143,9 @@ public class LayoutGrid implements Layout {
GridDimensions grid = calculateGrid(c);
grid.sum();
- int[] coords;
for (Component child : c.getChildren()) {
- coords = (int[]) child.getLayoutHint();
- grid.setBounds(coords[0], coords[1], child, c);
+ Vec2i coords = (Vec2i) child.getLayoutHint();
+ grid.setBounds(coords.x, coords.y, child, c);
}
}
}
@@ -146,11 +159,10 @@ public class LayoutGrid implements Layout {
private GridDimensions calculateGrid(Component parent) {
GridDimensions result = new GridDimensions();
- int[] coords;
for (Component child : parent.getChildren()) {
- coords = (int[]) child.getLayoutHint();
- result.add(coords[0], coords[1], child.getPreferredSize());
+ Vec2i coords = (Vec2i) child.getLayoutHint();
+ result.add(coords.x, coords.y, child.getPreferredSize());
}
return result;
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/gui/menu/MenuLayer.java b/src/main/java/ru/windcorp/progressia/client/graphics/gui/menu/MenuLayer.java
new file mode 100644
index 0000000..4fa155c
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/gui/menu/MenuLayer.java
@@ -0,0 +1,117 @@
+/*
+ * 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.client.graphics.gui.menu;
+
+import org.lwjgl.glfw.GLFW;
+
+import glm.vec._2.i.Vec2i;
+import ru.windcorp.progressia.client.graphics.Colors;
+import ru.windcorp.progressia.client.graphics.GUI;
+import ru.windcorp.progressia.client.graphics.font.Font;
+import ru.windcorp.progressia.client.graphics.gui.Component;
+import ru.windcorp.progressia.client.graphics.gui.GUILayer;
+import ru.windcorp.progressia.client.graphics.gui.Label;
+import ru.windcorp.progressia.client.graphics.gui.Layout;
+import ru.windcorp.progressia.client.graphics.gui.Panel;
+import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
+import ru.windcorp.progressia.client.graphics.gui.layout.LayoutFill;
+import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
+import ru.windcorp.progressia.client.graphics.input.InputEvent;
+import ru.windcorp.progressia.client.graphics.input.KeyEvent;
+import ru.windcorp.progressia.client.graphics.input.bus.Input;
+import ru.windcorp.progressia.client.localization.MutableString;
+import ru.windcorp.progressia.client.localization.MutableStringLocalized;
+
+public class MenuLayer extends GUILayer {
+
+ private final Component content;
+ private final Component background;
+
+ private final Runnable closeAction = () -> {
+ GUI.removeLayer(this);
+ };
+
+ public MenuLayer(String name, Component content) {
+ super(name, new LayoutFill(0));
+
+ setCursorPolicy(CursorPolicy.REQUIRE);
+
+ this.background = new Panel(name + ".Background", new LayoutAlign(10), Colors.toVector(0x66000000), null);
+ this.content = content;
+
+ background.addChild(content);
+ getRoot().addChild(background);
+ }
+
+ public MenuLayer(String name, Layout contentLayout) {
+ this(name, new Panel(name + ".Content", contentLayout));
+ }
+
+ public MenuLayer(String name) {
+ this(name, new LayoutVertical(20, 10));
+ }
+
+ public Component getContent() {
+ return content;
+ }
+
+ public Component getBackground() {
+ return background;
+ }
+
+ protected void addTitle() {
+ String translationKey = "Layer" + getName() + ".Title";
+ MutableString titleText = new MutableStringLocalized(translationKey);
+ Font titleFont = new Font().deriveBold().withColor(Colors.BLACK).withAlign(0.5f);
+
+ Label label = new Label(getName() + ".Title", titleFont, titleText);
+ getContent().addChild(label);
+
+ Panel panel = new Panel(getName() + ".Title.Underscore", null, Colors.BLUE, null);
+ panel.setLayout(new LayoutFill() {
+ @Override
+ public Vec2i calculatePreferredSize(Component c) {
+ return new Vec2i(label.getPreferredSize().x + 40, 4);
+ }
+ });
+ getContent().addChild(panel);
+ }
+
+ protected Runnable getCloseAction() {
+ return closeAction;
+ }
+
+ @Override
+ protected void handleInput(Input input) {
+
+ if (!input.isConsumed()) {
+ InputEvent event = input.getEvent();
+
+ if (event instanceof KeyEvent) {
+ KeyEvent keyEvent = (KeyEvent) event;
+ if (keyEvent.isPress() && keyEvent.getKey() == GLFW.GLFW_KEY_ESCAPE) {
+ getCloseAction().run();
+ }
+ }
+ }
+
+ super.handleInput(input);
+ input.consume();
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/model/BlockFaceVectors.java b/src/main/java/ru/windcorp/progressia/client/graphics/model/BlockFaceVectors.java
index e850d1a..7d94fb2 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/model/BlockFaceVectors.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/model/BlockFaceVectors.java
@@ -15,63 +15,83 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package ru.windcorp.progressia.client.graphics.model;
-import static ru.windcorp.progressia.common.world.block.BlockFace.*;
+import static ru.windcorp.progressia.common.world.rels.AbsFace.*;
import com.google.common.collect.ImmutableMap;
import glm.vec._3.Vec3;
-import ru.windcorp.progressia.common.world.block.BlockFace;
+import ru.windcorp.progressia.common.world.rels.AbsFace;
class BlockFaceVectors {
private static BlockFaceVectors createInner(BlockFaceVectors outer) {
- ImmutableMap.Builder originBuilder = ImmutableMap.builder();
+ ImmutableMap.Builder originBuilder = ImmutableMap.builder();
- ImmutableMap.Builder widthBuilder = ImmutableMap.builder();
+ ImmutableMap.Builder widthBuilder = ImmutableMap.builder();
- ImmutableMap.Builder heightBuilder = ImmutableMap.builder();
+ ImmutableMap.Builder heightBuilder = ImmutableMap.builder();
- for (BlockFace face : getFaces()) {
+ for (AbsFace face : getFaces()) {
Vec3 width = outer.getWidth(face);
Vec3 height = outer.getHeight(face);
- originBuilder.put(face, new Vec3(outer.getOrigin(face)));
+ originBuilder.put(
+ face,
+ new Vec3(outer.getOrigin(face))
+ );
widthBuilder.put(face, new Vec3(width));
heightBuilder.put(face, new Vec3(height));
}
- return new BlockFaceVectors(originBuilder.build(), widthBuilder.build(), heightBuilder.build());
+ return new BlockFaceVectors(
+ originBuilder.build(),
+ widthBuilder.build(),
+ heightBuilder.build()
+ );
}
private static final BlockFaceVectors OUTER;
private static final BlockFaceVectors INNER;
static {
- OUTER = new BlockFaceVectors(ImmutableMap.builder()
+ OUTER = new BlockFaceVectors(
+ ImmutableMap.builder()
- .put(TOP, new Vec3(-0.5f, +0.5f, +0.5f)).put(BOTTOM, new Vec3(-0.5f, -0.5f, -0.5f))
- .put(NORTH, new Vec3(+0.5f, -0.5f, -0.5f)).put(SOUTH, new Vec3(-0.5f, +0.5f, -0.5f))
- .put(WEST, new Vec3(+0.5f, +0.5f, -0.5f)).put(EAST, new Vec3(-0.5f, -0.5f, -0.5f))
+ .put(POS_Z, new Vec3(-0.5f, +0.5f, +0.5f))
+ .put(NEG_Z, new Vec3(-0.5f, -0.5f, -0.5f))
+ .put(POS_X, new Vec3(+0.5f, -0.5f, -0.5f))
+ .put(NEG_X, new Vec3(-0.5f, +0.5f, -0.5f))
+ .put(POS_Y, new Vec3(+0.5f, +0.5f, -0.5f))
+ .put(NEG_Y, new Vec3(-0.5f, -0.5f, -0.5f))
.build(),
- ImmutableMap.builder()
+ ImmutableMap.builder()
- .put(TOP, new Vec3(0, -1, 0)).put(BOTTOM, new Vec3(0, +1, 0)).put(NORTH, new Vec3(0, +1, 0))
- .put(SOUTH, new Vec3(0, -1, 0)).put(WEST, new Vec3(-1, 0, 0)).put(EAST, new Vec3(+1, 0, 0))
+ .put(POS_Z, new Vec3(0, -1, 0))
+ .put(NEG_Z, new Vec3(0, +1, 0))
+ .put(POS_X, new Vec3(0, +1, 0))
+ .put(NEG_X, new Vec3(0, -1, 0))
+ .put(POS_Y, new Vec3(-1, 0, 0))
+ .put(NEG_Y, new Vec3(+1, 0, 0))
- .build(),
+ .build(),
- ImmutableMap.builder()
+ ImmutableMap.builder()
- .put(TOP, new Vec3(+1, 0, 0)).put(BOTTOM, new Vec3(+1, 0, 0)).put(NORTH, new Vec3(0, 0, +1))
- .put(SOUTH, new Vec3(0, 0, +1)).put(WEST, new Vec3(0, 0, +1)).put(EAST, new Vec3(0, 0, +1))
+ .put(POS_Z, new Vec3(+1, 0, 0))
+ .put(NEG_Z, new Vec3(+1, 0, 0))
+ .put(POS_X, new Vec3(0, 0, +1))
+ .put(NEG_X, new Vec3(0, 0, +1))
+ .put(POS_Y, new Vec3(0, 0, +1))
+ .put(NEG_Y, new Vec3(0, 0, +1))
- .build());
+ .build()
+ );
INNER = createInner(OUTER);
}
@@ -80,26 +100,29 @@ class BlockFaceVectors {
return inner ? INNER : OUTER;
}
- private final ImmutableMap origins;
- private final ImmutableMap widths;
- private final ImmutableMap heights;
+ private final ImmutableMap origins;
+ private final ImmutableMap widths;
+ private final ImmutableMap heights;
- public BlockFaceVectors(ImmutableMap origins, ImmutableMap widths,
- ImmutableMap heights) {
+ public BlockFaceVectors(
+ ImmutableMap origins,
+ ImmutableMap widths,
+ ImmutableMap heights
+ ) {
this.origins = origins;
this.widths = widths;
this.heights = heights;
}
- public Vec3 getOrigin(BlockFace face) {
+ public Vec3 getOrigin(AbsFace face) {
return origins.get(face);
}
- public Vec3 getWidth(BlockFace face) {
+ public Vec3 getWidth(AbsFace face) {
return widths.get(face);
}
- public Vec3 getHeight(BlockFace face) {
+ public Vec3 getHeight(AbsFace face) {
return heights.get(face);
}
}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/model/Shape.java b/src/main/java/ru/windcorp/progressia/client/graphics/model/Shape.java
index 3a29c74..a70eede 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/model/Shape.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/model/Shape.java
@@ -30,10 +30,10 @@ import ru.windcorp.progressia.client.graphics.backend.VertexBufferObject;
public class Shape implements Renderable {
private final ShapeRenderProgram program;
- private final Face[] faces;
+ private final ShapePart[] parts;
private final Usage usage;
- private FaceGroup[] groups;
+ private ShapePartGroup[] groups;
private ByteBuffer vertices;
private ShortBuffer indices;
@@ -45,33 +45,33 @@ public class Shape implements Renderable {
private VertexBufferObject verticesVbo;
private VertexBufferObject indicesVbo;
- public Shape(Usage usage, ShapeRenderProgram program, Face... faces) {
+ public Shape(Usage usage, ShapeRenderProgram program, ShapePart... parts) {
this.program = program;
- this.faces = faces;
+ this.parts = parts;
this.usage = usage;
- configureFaces();
+ configureParts();
program.preprocess(this);
assembleBuffers();
}
- private void configureFaces() {
- for (Face face : faces) {
- face.setShape(this);
+ private void configureParts() {
+ for (ShapePart part : parts) {
+ part.setShape(this);
}
}
private void assembleBuffers() {
// TODO optimize: only update faces that requested it
- sortFaces();
+ sortParts();
resizeBuffers();
- for (Face face : faces) {
- assembleVertices(face);
- assembleIndices(face);
- face.resetUpdateFlags();
+ for (ShapePart part : parts) {
+ assembleVertices(part);
+ assembleIndices(part);
+ part.resetUpdateFlags();
}
this.vertices.flip();
@@ -85,110 +85,110 @@ public class Shape implements Renderable {
private void resizeBuffers() {
int verticesRequired = 0, indicesRequired = 0;
- for (Face face : faces) {
- verticesRequired += face.getVertices().remaining();
- indicesRequired += face.getIndices().remaining();
+ for (ShapePart part : parts) {
+ verticesRequired += part.getVertices().remaining();
+ indicesRequired += part.getIndices().remaining();
}
- if (this.vertices == null || vertices.capacity() < verticesRequired) {
+ if (vertices == null || vertices.capacity() < verticesRequired) {
this.vertices = BufferUtils.createByteBuffer(verticesRequired);
} else {
- this.vertices.position(0).limit(verticesRequired);
+ vertices.position(0).limit(verticesRequired);
}
- if (this.indices == null || this.indices.capacity() < indicesRequired) {
+ if (indices == null || indices.capacity() < indicesRequired) {
this.indices = BufferUtils.createShortBuffer(indicesRequired);
} else {
- this.indices.position(0).limit(indicesRequired);
+ indices.position(0).limit(indicesRequired);
}
}
- private void assembleVertices(Face face) {
- face.locationOfVertices = this.vertices.position();
+ private void assembleVertices(ShapePart part) {
+ part.locationOfVertices = this.vertices.position();
- insertVertices(face);
- linkVerticesWith(face);
+ insertVertices(part);
+ linkVerticesWith(part);
}
- private void insertVertices(Face face) {
- ByteBuffer faceVertices = face.getVertices();
+ private void insertVertices(ShapePart part) {
+ ByteBuffer partVertices = part.getVertices();
- faceVertices.mark();
- this.vertices.put(faceVertices);
- faceVertices.reset();
+ partVertices.mark();
+ this.vertices.put(partVertices);
+ partVertices.reset();
}
- private void linkVerticesWith(Face face) {
+ private void linkVerticesWith(ShapePart part) {
int limit = vertices.limit();
int position = vertices.position();
- vertices.limit(position).position(face.getLocationOfVertices());
- face.vertices = vertices.slice();
+ vertices.limit(position).position(part.getLocationOfVertices());
+ part.vertices = vertices.slice();
vertices.position(position).limit(limit);
}
- private void assembleIndices(Face face) {
- short vertexOffset = (short) (face.getLocationOfVertices() / program.getBytesPerVertex());
+ private void assembleIndices(ShapePart part) {
+ short vertexOffset = (short) (part.getLocationOfVertices() / program.getBytesPerVertex());
- face.locationOfIndices = indices.position();
+ part.locationOfIndices = indices.position();
- ShortBuffer faceIndices = face.getIndices();
+ ShortBuffer partIndices = part.getIndices();
- if (faceIndices == null) {
- for (int i = 0; i < face.getVertexCount(); ++i) {
+ if (partIndices == null) {
+ for (int i = 0; i < part.getVertexCount(); ++i) {
this.indices.put((short) (vertexOffset + i));
}
} else {
- for (int i = faceIndices.position(); i < faceIndices.limit(); ++i) {
- short faceIndex = faceIndices.get(i);
- faceIndex += vertexOffset;
- this.indices.put(faceIndex);
+ for (int i = partIndices.position(); i < partIndices.limit(); ++i) {
+ short partIndex = partIndices.get(i);
+ partIndex += vertexOffset;
+ this.indices.put(partIndex);
}
}
}
- private void sortFaces() {
- Arrays.sort(faces);
+ private void sortParts() {
+ Arrays.sort(parts);
}
private void assembleGroups() {
- int unique = countUniqueFaces();
- this.groups = new FaceGroup[unique];
+ int unique = countUniqueParts();
+ this.groups = new ShapePartGroup[unique];
- if (faces.length == 0)
+ if (parts.length == 0)
return;
- int previousHandle = faces[0].getSortingIndex();
+ int previousHandle = parts[0].getSortingIndex();
int start = 0;
int groupIndex = 0;
- for (int i = 1; i < faces.length; ++i) {
- if (previousHandle != faces[i].getSortingIndex()) {
+ for (int i = 1; i < parts.length; ++i) {
+ if (previousHandle != parts[i].getSortingIndex()) {
- groups[groupIndex] = new FaceGroup(faces, start, i);
+ groups[groupIndex] = new ShapePartGroup(parts, start, i);
start = i;
groupIndex++;
- previousHandle = faces[i].getSortingIndex();
+ previousHandle = parts[i].getSortingIndex();
}
}
assert groupIndex == groups.length - 1;
- groups[groupIndex] = new FaceGroup(faces, start, faces.length);
+ groups[groupIndex] = new ShapePartGroup(parts, start, parts.length);
}
- private int countUniqueFaces() {
- if (faces.length == 0)
+ private int countUniqueParts() {
+ if (parts.length == 0)
return 0;
int result = 1;
- int previousHandle = faces[0].getSortingIndex();
+ int previousHandle = parts[0].getSortingIndex();
- for (int i = 1; i < faces.length; ++i) {
- if (previousHandle != faces[i].getSortingIndex()) {
+ for (int i = 1; i < parts.length; ++i) {
+ if (previousHandle != parts[i].getSortingIndex()) {
result++;
- previousHandle = faces[i].getSortingIndex();
+ previousHandle = parts[i].getSortingIndex();
}
}
@@ -238,11 +238,11 @@ public class Shape implements Renderable {
return program;
}
- public Face[] getFaces() {
- return faces;
+ public ShapePart[] getParts() {
+ return parts;
}
- public FaceGroup[] getGroups() {
+ public ShapePartGroup[] getGroups() {
return groups;
}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/model/Face.java b/src/main/java/ru/windcorp/progressia/client/graphics/model/ShapePart.java
similarity index 82%
rename from src/main/java/ru/windcorp/progressia/client/graphics/model/Face.java
rename to src/main/java/ru/windcorp/progressia/client/graphics/model/ShapePart.java
index 752c23a..aef0ad9 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/model/Face.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/model/ShapePart.java
@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package ru.windcorp.progressia.client.graphics.model;
import java.nio.ByteBuffer;
@@ -24,7 +24,7 @@ import java.util.Objects;
import ru.windcorp.progressia.client.graphics.texture.Texture;
-public class Face implements Comparable {
+public class ShapePart implements Comparable {
private static final ShortBuffer GENERATE_SUCCESSIVE_LATER = null;
@@ -40,13 +40,20 @@ public class Face implements Comparable {
private ShortBuffer userIndices;
private boolean userIndicesUpdated = true;
- public Face(Texture texture, ByteBuffer vertices, ShortBuffer indices) {
+ public ShapePart(
+ Texture texture,
+ ByteBuffer vertices,
+ ShortBuffer indices
+ ) {
setTexture(texture);
setVertices(vertices);
setIndices(indices);
}
- public Face(Texture texture, ByteBuffer vertices) {
+ public ShapePart(
+ Texture texture,
+ ByteBuffer vertices
+ ) {
this(texture, vertices, null);
}
@@ -59,16 +66,22 @@ public class Face implements Comparable {
private void checkVertices() {
if (vertices.remaining() % getBytesPerVertex() != 0) {
- throw new IllegalArgumentException("Invalid vertex buffer: " + (vertices.remaining() % getBytesPerVertex())
- + " extra bytes after last vertex");
+ throw new IllegalArgumentException(
+ "Invalid vertex buffer: " +
+ (vertices.remaining() % getBytesPerVertex()) +
+ " extra bytes after last vertex"
+ );
}
}
private void checkIndices() {
if (userIndices != GENERATE_SUCCESSIVE_LATER) {
if (userIndices.remaining() % 3 != 0) {
- throw new IllegalArgumentException("Invalid vertex indices: " + (userIndices.remaining() % 3)
- + " extra indices after last triangle");
+ throw new IllegalArgumentException(
+ "Invalid vertex indices: " +
+ (userIndices.remaining() % 3) +
+ " extra indices after last triangle"
+ );
}
userIndices.mark();
@@ -78,15 +91,21 @@ public class Face implements Comparable {
short index = userIndices.get();
if (index < 0 || index >= vertexCount) {
throw new IllegalArgumentException(
- "Invalid vertex index " + index + " (" + vertexCount + " vertices available)");
+ "Invalid vertex index " + index +
+ " (" + vertexCount + " vertices available)"
+ );
}
}
userIndices.reset();
} else {
if (getVertexCount() % 3 != 0) {
- throw new IllegalArgumentException("Invalid vertices: " + (getVertexCount() % 3)
- + " extra indices after last triangle " + "(indices are automatic)");
+ throw new IllegalArgumentException(
+ "Invalid vertices: " +
+ (getVertexCount() % 3) +
+ " extra indices after last triangle " +
+ "(indices are automatic)"
+ );
}
}
}
@@ -136,7 +155,7 @@ public class Face implements Comparable {
return vertices;
}
- public Face setVertices(ByteBuffer vertices) {
+ public ShapePart setVertices(ByteBuffer vertices) {
this.vertices = Objects.requireNonNull(vertices, "vertices");
markForVertexUpdate();
return this;
@@ -183,7 +202,7 @@ public class Face implements Comparable {
return userIndices.remaining();
}
- public Face setIndices(ShortBuffer indices) {
+ public ShapePart setIndices(ShortBuffer indices) {
if (indices == null) {
indices = GENERATE_SUCCESSIVE_LATER;
}
@@ -226,7 +245,7 @@ public class Face implements Comparable {
}
@Override
- public int compareTo(Face o) {
+ public int compareTo(ShapePart o) {
return Integer.compare(getSortingIndex(), o.getSortingIndex());
}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/model/FaceGroup.java b/src/main/java/ru/windcorp/progressia/client/graphics/model/ShapePartGroup.java
similarity index 93%
rename from src/main/java/ru/windcorp/progressia/client/graphics/model/FaceGroup.java
rename to src/main/java/ru/windcorp/progressia/client/graphics/model/ShapePartGroup.java
index 7dc0356..e5fcee4 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/model/FaceGroup.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/model/ShapePartGroup.java
@@ -21,13 +21,13 @@ package ru.windcorp.progressia.client.graphics.model;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.graphics.texture.TexturePrimitive;
-public class FaceGroup {
+public class ShapePartGroup {
private final TexturePrimitive texture;
private final int indexCount;
private final int byteOffsetOfIndices;
- FaceGroup(Face[] faces, int start, int end) {
+ ShapePartGroup(ShapePart[] faces, int start, int end) {
Texture t = faces[start].getTexture();
this.texture = t == null ? null : t.getSprite().getPrimitive();
@@ -36,7 +36,7 @@ public class FaceGroup {
int indexCount = 0;
for (int i = start; i < end; ++i) {
- Face face = faces[i];
+ ShapePart face = faces[i];
assert this.texture == null ? (face.getTexture() == null)
: (face.getTexture().getSprite().getPrimitive() == this.texture);
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/model/Faces.java b/src/main/java/ru/windcorp/progressia/client/graphics/model/ShapeParts.java
similarity index 52%
rename from src/main/java/ru/windcorp/progressia/client/graphics/model/Faces.java
rename to src/main/java/ru/windcorp/progressia/client/graphics/model/ShapeParts.java
index 42eb2c0..50f744f 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/model/Faces.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/model/ShapeParts.java
@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package ru.windcorp.progressia.client.graphics.model;
import java.nio.ShortBuffer;
@@ -25,37 +25,93 @@ import glm.vec._3.Vec3;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram.VertexBuilder;
import ru.windcorp.progressia.client.graphics.texture.Texture;
-import ru.windcorp.progressia.common.world.block.BlockFace;
+import ru.windcorp.progressia.common.world.rels.AbsFace;
-public class Faces {
+public class ShapeParts {
- private Faces() {
+ private ShapeParts() {
}
- public static Face createRectangle(ShapeRenderProgram program, Texture texture, Vec4 colorMultiplier, Vec3 origin,
- Vec3 width, Vec3 height, boolean flip) {
+ public static ShapePart createRectangle(
+ ShapeRenderProgram program,
+ Texture texture,
+ Vec4 colorMultiplier,
+ Vec3 origin,
+ Vec3 width,
+ Vec3 height,
+ boolean flip
+ ) {
VertexBuilder builder = program.getVertexBuilder();
- builder.addVertex(origin, colorMultiplier, new Vec2(0, 0))
- .addVertex(origin.add_(height), colorMultiplier, new Vec2(0, 1))
- .addVertex(origin.add_(width), colorMultiplier, new Vec2(1, 0))
- .addVertex(origin.add_(width).add(height), colorMultiplier, new Vec2(1, 1));
+ builder.addVertex(
+ origin,
+ colorMultiplier,
+ new Vec2(0, 0)
+ ).addVertex(
+ origin.add_(height),
+ colorMultiplier,
+ new Vec2(0, 1)
+ ).addVertex(
+ origin.add_(width),
+ colorMultiplier,
+ new Vec2(1, 0)
+ ).addVertex(
+ origin.add_(width).add(height),
+ colorMultiplier,
+ new Vec2(1, 1)
+ );
- ShortBuffer buffer = flip ? ShortBuffer.wrap(new short[] { 0, 1, 3, 0, 3, 2 })
- : ShortBuffer.wrap(new short[] { 3, 1, 0, 2, 3, 0 });
+ ShortBuffer buffer = flip ? ShortBuffer.wrap(
+ new short[] {
+ 0,
+ 1,
+ 3,
+ 0,
+ 3,
+ 2
+ }
+ )
+ : ShortBuffer.wrap(
+ new short[] {
+ 3,
+ 1,
+ 0,
+ 2,
+ 3,
+ 0
+ }
+ );
- return new Face(texture, builder.assemble(), buffer);
+ return new ShapePart(
+ texture,
+ builder.assemble(),
+ buffer
+ );
}
- public static Face createBlockFace(ShapeRenderProgram program, Texture texture, Vec4 colorMultiplier,
- Vec3 blockCenter, BlockFace face, boolean inner) {
+ public static ShapePart createBlockFace(
+ ShapeRenderProgram program,
+ Texture texture,
+ Vec4 colorMultiplier,
+ Vec3 blockCenter,
+ AbsFace face,
+ boolean inner
+ ) {
BlockFaceVectors vectors = BlockFaceVectors.get(inner);
Vec3 origin = blockCenter.add_(vectors.getOrigin(face));
Vec3 width = vectors.getWidth(face);
Vec3 height = vectors.getHeight(face);
- return createRectangle(program, texture, colorMultiplier, origin, width, height, inner);
+ return createRectangle(
+ program,
+ texture,
+ colorMultiplier,
+ origin,
+ width,
+ height,
+ inner
+ );
}
}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/model/ShapeRenderProgram.java b/src/main/java/ru/windcorp/progressia/client/graphics/model/ShapeRenderProgram.java
index 13a33e8..ab4b12a 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/model/ShapeRenderProgram.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/model/ShapeRenderProgram.java
@@ -100,7 +100,7 @@ public class ShapeRenderProgram extends Program {
try {
enableAttributes();
- for (FaceGroup group : shape.getGroups()) {
+ for (ShapePartGroup group : shape.getGroups()) {
renderFaceGroup(group);
}
} finally {
@@ -145,7 +145,7 @@ public class ShapeRenderProgram extends Program {
indices.bind(BindTarget.ELEMENT_ARRAY);
}
- protected void renderFaceGroup(FaceGroup group) {
+ protected void renderFaceGroup(ShapePartGroup group) {
TexturePrimitive texture = group.getTexture();
if (texture != null) {
@@ -165,12 +165,12 @@ public class ShapeRenderProgram extends Program {
}
public void preprocess(Shape shape) {
- for (Face face : shape.getFaces()) {
+ for (ShapePart face : shape.getParts()) {
applySprites(face);
}
}
- private void applySprites(Face face) {
+ private void applySprites(ShapePart face) {
if (face.getTexture() == null)
return;
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/model/Shapes.java b/src/main/java/ru/windcorp/progressia/client/graphics/model/Shapes.java
index e361392..92d9872 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/model/Shapes.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/model/Shapes.java
@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package ru.windcorp.progressia.client.graphics.model;
import java.util.Map;
@@ -24,42 +24,102 @@ import glm.vec._3.Vec3;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.backend.Usage;
import ru.windcorp.progressia.client.graphics.texture.Texture;
-import ru.windcorp.progressia.common.world.block.BlockFace;
+import ru.windcorp.progressia.common.world.rels.AbsFace;
public class Shapes {
public static Shape createParallelepiped(
- // Try saying that 10 times fast
- ShapeRenderProgram program,
+ // Try saying that 10 times fast
+ ShapeRenderProgram program,
- Vec3 origin,
+ Vec3 origin,
- Vec3 width, Vec3 height, Vec3 depth,
+ Vec3 width,
+ Vec3 height,
+ Vec3 depth,
- Vec4 colorMultiplier,
+ Vec4 colorMultiplier,
- Texture topTexture, Texture bottomTexture, Texture northTexture, Texture southTexture, Texture eastTexture,
- Texture westTexture,
+ Texture topTexture,
+ Texture bottomTexture,
+ Texture northTexture,
+ Texture southTexture,
+ Texture eastTexture,
+ Texture westTexture,
- boolean flip) {
+ boolean flip
+ ) {
- Face top = Faces.createRectangle(program, topTexture, colorMultiplier, origin.add_(height).add(width),
- width.negate_(), depth, flip);
+ ShapePart top = ShapeParts.createRectangle(
+ program,
+ topTexture,
+ colorMultiplier,
+ origin.add_(height).add(width),
+ width.negate_(),
+ depth,
+ flip
+ );
- Face bottom = Faces.createRectangle(program, bottomTexture, colorMultiplier, origin, width, depth, flip);
+ ShapePart bottom = ShapeParts.createRectangle(
+ program,
+ bottomTexture,
+ colorMultiplier,
+ origin,
+ width,
+ depth,
+ flip
+ );
- Face north = Faces.createRectangle(program, northTexture, colorMultiplier, origin.add_(depth), width, height,
- flip);
+ ShapePart north = ShapeParts.createRectangle(
+ program,
+ northTexture,
+ colorMultiplier,
+ origin.add_(depth),
+ width,
+ height,
+ flip
+ );
- Face south = Faces.createRectangle(program, southTexture, colorMultiplier, origin.add_(width), width.negate_(),
- height, flip);
+ ShapePart south = ShapeParts.createRectangle(
+ program,
+ southTexture,
+ colorMultiplier,
+ origin.add_(width),
+ width.negate_(),
+ height,
+ flip
+ );
- Face east = Faces.createRectangle(program, eastTexture, colorMultiplier, origin, depth, height, flip);
+ ShapePart east = ShapeParts.createRectangle(
+ program,
+ eastTexture,
+ colorMultiplier,
+ origin,
+ depth,
+ height,
+ flip
+ );
- Face west = Faces.createRectangle(program, westTexture, colorMultiplier, origin.add_(width).add(depth),
- depth.negate_(), height, flip);
+ ShapePart west = ShapeParts.createRectangle(
+ program,
+ westTexture,
+ colorMultiplier,
+ origin.add_(width).add(depth),
+ depth.negate_(),
+ height,
+ flip
+ );
- Shape result = new Shape(Usage.STATIC, program, top, bottom, north, south, east, west);
+ Shape result = new Shape(
+ Usage.STATIC,
+ program,
+ top,
+ bottom,
+ north,
+ south,
+ east,
+ west
+ );
return result;
}
@@ -85,8 +145,15 @@ public class Shapes {
private boolean flip = false;
- public PppBuilder(ShapeRenderProgram program, Texture top, Texture bottom, Texture north, Texture south,
- Texture east, Texture west) {
+ public PppBuilder(
+ ShapeRenderProgram program,
+ Texture top,
+ Texture bottom,
+ Texture north,
+ Texture south,
+ Texture east,
+ Texture west
+ ) {
this.program = program;
this.topTexture = top;
this.bottomTexture = bottom;
@@ -96,10 +163,19 @@ public class Shapes {
this.westTexture = west;
}
- public PppBuilder(ShapeRenderProgram program, Map textureMap) {
- this(program, textureMap.get(BlockFace.TOP), textureMap.get(BlockFace.BOTTOM),
- textureMap.get(BlockFace.NORTH), textureMap.get(BlockFace.SOUTH), textureMap.get(BlockFace.EAST),
- textureMap.get(BlockFace.WEST));
+ public PppBuilder(
+ ShapeRenderProgram program,
+ Map textureMap
+ ) {
+ this(
+ program,
+ textureMap.get(AbsFace.POS_Z),
+ textureMap.get(AbsFace.NEG_Z),
+ textureMap.get(AbsFace.POS_X),
+ textureMap.get(AbsFace.NEG_X),
+ textureMap.get(AbsFace.NEG_Y),
+ textureMap.get(AbsFace.POS_Y)
+ );
}
public PppBuilder(ShapeRenderProgram program, Texture texture) {
@@ -190,8 +266,21 @@ public class Shapes {
}
public Shape create() {
- return createParallelepiped(program, origin, width, height, depth, colorMultiplier, topTexture,
- bottomTexture, northTexture, southTexture, eastTexture, westTexture, flip);
+ return createParallelepiped(
+ program,
+ origin,
+ width,
+ height,
+ depth,
+ colorMultiplier,
+ topTexture,
+ bottomTexture,
+ northTexture,
+ southTexture,
+ eastTexture,
+ westTexture,
+ flip
+ );
}
}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/texture/ComplexTexture.java b/src/main/java/ru/windcorp/progressia/client/graphics/texture/ComplexTexture.java
index 31ecd52..a54ca51 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/texture/ComplexTexture.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/texture/ComplexTexture.java
@@ -15,13 +15,13 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package ru.windcorp.progressia.client.graphics.texture;
import java.util.Map;
import glm.vec._2.Vec2;
-import ru.windcorp.progressia.common.world.block.BlockFace;
+import ru.windcorp.progressia.common.world.rels.AbsFace;
public class ComplexTexture {
@@ -30,27 +30,67 @@ public class ComplexTexture {
private final float assumedWidth;
private final float assumedHeight;
- public ComplexTexture(TexturePrimitive primitive, int abstractWidth, int abstractHeight) {
+ public ComplexTexture(
+ TexturePrimitive primitive,
+ int abstractWidth,
+ int abstractHeight
+ ) {
this.primitive = primitive;
- this.assumedWidth = abstractWidth / (float) primitive.getWidth() * primitive.getBufferWidth();
+ this.assumedWidth = abstractWidth
+ / (float) primitive.getWidth() * primitive.getBufferWidth();
- this.assumedHeight = abstractHeight / (float) primitive.getHeight() * primitive.getBufferHeight();
+ this.assumedHeight = abstractHeight
+ / (float) primitive.getHeight() * primitive.getBufferHeight();
}
public Texture get(int x, int y, int width, int height) {
- return new SimpleTexture(new Sprite(primitive, new Vec2(x / assumedWidth, y / assumedHeight),
- new Vec2(width / assumedWidth, height / assumedHeight)));
+ return new SimpleTexture(
+ new Sprite(
+ primitive,
+ new Vec2(x / assumedWidth, y / assumedHeight),
+ new Vec2(width / assumedWidth, height / assumedHeight)
+ )
+ );
}
- public Map getCuboidTextures(int x, int y, int width, int height, int depth) {
- return BlockFace.mapToFaces(get(x + depth + width, y + height + depth, -width, -depth),
- get(x + depth + width + width, y + height + depth, -width, -depth), get(x + depth, y, width, height),
- get(x + depth + width + depth, y, width, height), get(x, y, depth, height),
- get(x + depth + width, y, depth, height));
+ public Map getCuboidTextures(
+ int x,
+ int y,
+ int width,
+ int height,
+ int depth
+ ) {
+ return AbsFace.mapToFaces(
+ get(
+ x + depth + width,
+ y + height + depth,
+ -width,
+ -depth
+ ),
+ get(
+ x + depth + width + width,
+ y + height + depth,
+ -width,
+ -depth
+ ),
+ get(x + depth, y, width, height),
+ get(
+ x + depth + width + depth,
+ y,
+ width,
+ height
+ ),
+ get(x, y, depth, height),
+ get(x + depth + width, y, depth, height)
+ );
}
- public Map getCuboidTextures(int x, int y, int size) {
+ public Map getCuboidTextures(
+ int x,
+ int y,
+ int size
+ ) {
return getCuboidTextures(x, y, size, size, size);
}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/world/Camera.java b/src/main/java/ru/windcorp/progressia/client/graphics/world/Camera.java
index c8faf61..9c30a49 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/world/Camera.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/world/Camera.java
@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package ru.windcorp.progressia.client.graphics.world;
import static java.lang.Math.*;
@@ -29,6 +29,9 @@ import glm.mat._4.Mat4;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.world.Camera.Anchor.Mode;
+import ru.windcorp.progressia.client.world.entity.NPedModel;
+import ru.windcorp.progressia.common.util.Matrices;
+import ru.windcorp.progressia.common.util.Vectors;
public class Camera {
@@ -42,7 +45,10 @@ public class Camera {
void applyCameraRotation(Mat4 output);
- public static Mode of(Consumer offsetGetter, Consumer rotator) {
+ public static Mode of(
+ Consumer offsetGetter,
+ Consumer rotator
+ ) {
return new Mode() {
@Override
public void getCameraOffset(Vec3 output) {
@@ -57,13 +63,13 @@ public class Camera {
}
}
- void getCameraPosition(Vec3 output);
+ Vec3 getCameraPosition(Vec3 output);
- void getCameraVelocity(Vec3 output);
+ Vec3 getCameraVelocity(Vec3 output);
- float getCameraYaw();
-
- float getCameraPitch();
+ Vec3 getLookingAt(Vec3 output);
+
+ Vec3 getUpVector(Vec3 output);
Collection getCameraModes();
@@ -81,14 +87,11 @@ public class Camera {
*/
private final Vec3 lastAnchorPosition = new Vec3();
- private float lastAnchorYaw;
- private float lastAnchorPitch;
+ private final Vec3 lastAnchorLookingAt = new Vec3();
+ private final Vec3 lastAnchorUpVector = new Vec3();
private final Mat4 lastCameraMatrix = new Mat4();
- private final Vec3 lastAnchorLookingAt = new Vec3();
- private final Vec3 lastAnchorUp = new Vec3();
-
{
invalidateCache();
}
@@ -105,6 +108,9 @@ public class Camera {
*/
public void apply(WorldRenderHelper helper) {
+ if (NPedModel.flag) {
+// System.out.println("Camera.apply()");
+ }
applyPerspective(helper);
rotateCoordinateSystem(helper);
@@ -118,8 +124,13 @@ public class Camera {
private void applyPerspective(WorldRenderHelper helper) {
Mat4 previous = helper.getViewTransform();
- Glm.perspective(computeFovY(), GraphicsInterface.getAspectRatio(), 0.01f, 150.0f, helper.pushViewTransform())
- .mul(previous);
+ Glm.perspective(
+ computeFovY(),
+ GraphicsInterface.getAspectRatio(),
+ 0.01f,
+ 150.0f,
+ helper.pushViewTransform()
+ ).mul(previous);
}
private void rotateCoordinateSystem(WorldRenderHelper helper) {
@@ -141,17 +152,34 @@ public class Camera {
}
private void applyDirection(WorldRenderHelper helper) {
- float pitch = anchor.getCameraPitch();
- float yaw = anchor.getCameraYaw();
+ anchor.getLookingAt(lastAnchorLookingAt);
+ anchor.getUpVector(lastAnchorUpVector);
- helper.pushViewTransform().rotateY(-pitch).rotateZ(-yaw);
+ lookAt(helper.pushViewTransform());
+ }
- this.lastAnchorYaw = yaw;
- this.lastAnchorPitch = pitch;
-
- this.lastAnchorLookingAt.set(cos(pitch) * cos(yaw), cos(pitch) * sin(yaw), sin(pitch));
- this.lastAnchorUp.set(cos(pitch + PI_F / 2) * cos(yaw), cos(pitch + PI_F / 2) * sin(yaw),
- sin(pitch + PI_F / 2));
+ private void lookAt(Mat4 result) {
+ Vec3 f = this.lastAnchorLookingAt;
+ Vec3 s = Vectors.grab3();
+ Vec3 u = Vectors.grab3();
+
+ f.cross(this.lastAnchorUpVector, s);
+ s.normalize();
+
+ s.cross(f, u);
+
+ Mat4 workspace = Matrices.grab4();
+ workspace.set(
+ +f.x, -s.x, +u.x, 0,
+ +f.y, -s.y, +u.y, 0,
+ +f.z, -s.z, +u.z, 0,
+ 0, 0, 0, 1
+ );
+ result.mul(workspace);
+ Matrices.release(workspace);
+
+ Vectors.release(s);
+ Vectors.release(u);
}
private void applyPosition(WorldRenderHelper helper) {
@@ -177,7 +205,11 @@ public class Camera {
if (widthOverHeight >= 1) {
return fieldOfView;
} else {
- return (float) (2 * atan(1 / widthOverHeight * tan(fieldOfView / 2)));
+ return (float) (2 * atan(
+ 1 / widthOverHeight
+ *
+ tan(fieldOfView / 2)
+ ));
}
}
@@ -213,7 +245,9 @@ public class Camera {
if (modesCollection.isEmpty()) {
throw new IllegalArgumentException(
- "Anchor " + anchor + " returned no camera modes," + " at least one required");
+ "Anchor " + anchor + " returned no camera modes,"
+ + " at least one required"
+ );
}
this.anchor = anchor;
@@ -224,14 +258,28 @@ public class Camera {
private void invalidateCache() {
this.lastAnchorPosition.set(Float.NaN);
- this.lastAnchorYaw = Float.NaN;
- this.lastAnchorPitch = Float.NaN;
- this.lastCameraMatrix.set(Float.NaN, Float.NaN, Float.NaN, Float.NaN, Float.NaN, Float.NaN, Float.NaN,
- Float.NaN, Float.NaN, Float.NaN, Float.NaN, Float.NaN, Float.NaN, Float.NaN, Float.NaN, Float.NaN);
+ this.lastCameraMatrix.set(
+ Float.NaN,
+ Float.NaN,
+ Float.NaN,
+ Float.NaN,
+ Float.NaN,
+ Float.NaN,
+ Float.NaN,
+ Float.NaN,
+ Float.NaN,
+ Float.NaN,
+ Float.NaN,
+ Float.NaN,
+ Float.NaN,
+ Float.NaN,
+ Float.NaN,
+ Float.NaN
+ );
this.lastAnchorLookingAt.set(Float.NaN);
- this.lastAnchorUp.set(Float.NaN);
+ this.lastAnchorUpVector.set(Float.NaN);
}
public Anchor.Mode getMode() {
@@ -250,14 +298,6 @@ public class Camera {
return currentModeIndex;
}
- public float getLastAnchorYaw() {
- return lastAnchorYaw;
- }
-
- public float getLastAnchorPitch() {
- return lastAnchorPitch;
- }
-
public Vec3 getLastAnchorPosition() {
return lastAnchorPosition;
}
@@ -271,7 +311,7 @@ public class Camera {
}
public Vec3 getLastAnchorUp() {
- return lastAnchorUp;
+ return lastAnchorUpVector;
}
}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/world/EntityAnchor.java b/src/main/java/ru/windcorp/progressia/client/graphics/world/EntityAnchor.java
index 6b34029..53b7af5 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/world/EntityAnchor.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/world/EntityAnchor.java
@@ -52,24 +52,32 @@ public class EntityAnchor implements Anchor {
}
@Override
- public void getCameraPosition(Vec3 output) {
+ public Vec3 getCameraPosition(Vec3 output) {
+ if (output == null) output = new Vec3();
model.getViewPoint(output);
- output.add(entity.getPosition());
+ output.add(model.getPosition());
+ return output;
}
@Override
- public void getCameraVelocity(Vec3 output) {
+ public Vec3 getCameraVelocity(Vec3 output) {
+ if (output == null) output = new Vec3();
output.set(entity.getVelocity());
+ return output;
}
@Override
- public float getCameraYaw() {
- return entity.getYaw();
+ public Vec3 getLookingAt(Vec3 output) {
+ if (output == null) output = new Vec3();
+ model.getLookingAt(output);
+ return output;
}
-
+
@Override
- public float getCameraPitch() {
- return entity.getPitch();
+ public Vec3 getUpVector(Vec3 output) {
+ if (output == null) output = new Vec3();
+ model.getUpVector(output);
+ return output;
}
@Override
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/world/LayerWorld.java b/src/main/java/ru/windcorp/progressia/client/graphics/world/LayerWorld.java
index 4af53e9..bfe8658 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/world/LayerWorld.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/world/LayerWorld.java
@@ -41,6 +41,8 @@ import ru.windcorp.progressia.common.Units;
import ru.windcorp.progressia.common.collision.Collideable;
import ru.windcorp.progressia.common.collision.colliders.Collider;
import ru.windcorp.progressia.common.util.FloatMathUtil;
+import ru.windcorp.progressia.common.util.Vectors;
+import ru.windcorp.progressia.common.world.GravityModel;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.test.CollisionModelRenderer;
import ru.windcorp.progressia.test.TestPlayerControls;
@@ -57,6 +59,8 @@ public class LayerWorld extends Layer {
super("World");
this.client = client;
this.inputBasedControls = new InputBasedControls(client);
+
+ setCursorPolicy(CursorPolicy.FORBID);
}
@Override
@@ -181,16 +185,25 @@ public class LayerWorld extends Layer {
entity.getVelocity().mul((float) Math.exp(-FRICTION_COEFF / entity.getCollisionMass() * tickLength));
}
- private static final float MC_g = Units.get("32 m/s^2");
- private static final float IRL_g = Units.get("9.8 m/s^2");
-
private void tmp_applyGravity(EntityData entity, float tickLength) {
+ GravityModel gm = ClientState.getInstance().getWorld().getData().getGravityModel();
+
+ Vec3 upVector = Vectors.grab3();
+ gm.getUp(entity.getPosition(), upVector);
+ entity.changeUpVector(upVector);
+ Vectors.release(upVector);
+
if (ClientState.getInstance().getLocalPlayer().getEntity() == entity && tmp_testControls.isFlying()) {
return;
}
- final float gravitationalAcceleration = tmp_testControls.useMinecraftGravity() ? MC_g : IRL_g;
- entity.getVelocity().add(0, 0, -gravitationalAcceleration * tickLength);
+ Vec3 gravitationalAcceleration = Vectors.grab3();
+ gm.getGravity(entity.getPosition(), gravitationalAcceleration);
+
+ gravitationalAcceleration.mul(tickLength);
+ entity.getVelocity().add(gravitationalAcceleration);
+
+ Vectors.release(gravitationalAcceleration);
}
@Override
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/world/Selection.java b/src/main/java/ru/windcorp/progressia/client/graphics/world/Selection.java
index 3d997ee..559e432 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/world/Selection.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/world/Selection.java
@@ -23,13 +23,13 @@ import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.world.WorldRender;
import ru.windcorp.progressia.common.world.BlockRay;
-import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.entity.EntityData;
+import ru.windcorp.progressia.common.world.rels.AbsFace;
public class Selection {
private final Vec3i block = new Vec3i();
- private BlockFace surface = null;
+ private AbsFace surface = null;
private final Vec2 pointOnSurface = new Vec2(0.5f, 0.5f);
private final Vec3 point = new Vec3();
@@ -38,10 +38,9 @@ public class Selection {
private BlockRay ray = new BlockRay();
public void update(WorldRender world, EntityData player) {
- Vec3 direction = new Vec3();
Vec3 start = new Vec3();
-
- player.getLookingAtVector(direction);
+ Vec3 direction = player.getLookingAt();
+
world.getEntityRenderable(player).getViewPoint(start);
start.add(player.getPosition());
@@ -71,7 +70,7 @@ public class Selection {
return exists ? point : null;
}
- public BlockFace getSurface() {
+ public AbsFace getSurface() {
return exists ? surface : null;
}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/world/WorldRenderProgram.java b/src/main/java/ru/windcorp/progressia/client/graphics/world/WorldRenderProgram.java
index 3e7223c..8fb000f 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/world/WorldRenderProgram.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/world/WorldRenderProgram.java
@@ -33,7 +33,7 @@ import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.backend.VertexBufferObject;
import ru.windcorp.progressia.client.graphics.backend.shaders.attributes.*;
import ru.windcorp.progressia.client.graphics.backend.shaders.uniforms.*;
-import ru.windcorp.progressia.client.graphics.model.Face;
+import ru.windcorp.progressia.client.graphics.model.ShapePart;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
@@ -120,12 +120,12 @@ public class WorldRenderProgram extends ShapeRenderProgram {
public void preprocess(Shape shape) {
super.preprocess(shape);
- for (Face face : shape.getFaces()) {
+ for (ShapePart face : shape.getParts()) {
computeNormals(face);
}
}
- private void computeNormals(Face face) {
+ private void computeNormals(ShapePart face) {
Vec3 a = Vectors.grab3();
Vec3 b = Vectors.grab3();
Vec3 c = Vectors.grab3();
@@ -160,7 +160,7 @@ public class WorldRenderProgram extends ShapeRenderProgram {
normal.normalize();
}
- private void loadVertexPosition(Face face, int index, Vec3 result) {
+ private void loadVertexPosition(ShapePart face, int index, Vec3 result) {
ByteBuffer vertices = face.getVertices();
int offset = vertices.position() + index * getBytesPerVertex();
@@ -168,7 +168,7 @@ public class WorldRenderProgram extends ShapeRenderProgram {
vertices.getFloat(offset + 2 * Float.BYTES));
}
- private void saveVertexNormal(Face face, int index, Vec3 normal) {
+ private void saveVertexNormal(ShapePart face, int index, Vec3 normal) {
ByteBuffer vertices = face.getVertices();
int offset = vertices.position() + index * getBytesPerVertex()
+ (3 * Float.BYTES + 4 * Float.BYTES + 2 * Float.BYTES);
diff --git a/src/main/java/ru/windcorp/progressia/client/world/ChunkRender.java b/src/main/java/ru/windcorp/progressia/client/world/ChunkRender.java
index 43f883c..41aa933 100644
--- a/src/main/java/ru/windcorp/progressia/client/world/ChunkRender.java
+++ b/src/main/java/ru/windcorp/progressia/client/world/ChunkRender.java
@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package ru.windcorp.progressia.client.world;
import java.util.Collections;
@@ -27,24 +27,29 @@ import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.client.world.block.BlockRender;
import ru.windcorp.progressia.client.world.block.BlockRenderRegistry;
import ru.windcorp.progressia.client.world.tile.TileRender;
+import ru.windcorp.progressia.client.world.tile.TileRenderReference;
import ru.windcorp.progressia.client.world.tile.TileRenderRegistry;
import ru.windcorp.progressia.client.world.tile.TileRenderStack;
-import ru.windcorp.progressia.common.world.ChunkData;
-import ru.windcorp.progressia.common.world.block.BlockFace;
-import ru.windcorp.progressia.common.world.generic.GenericChunk;
-import ru.windcorp.progressia.common.world.tile.TileDataStack;
+import ru.windcorp.progressia.common.world.DefaultChunkData;
+import ru.windcorp.progressia.common.world.TileDataReference;
+import ru.windcorp.progressia.common.world.TileDataStack;
+import ru.windcorp.progressia.common.world.generic.ChunkGenericRO;
+import ru.windcorp.progressia.common.world.rels.AbsFace;
+import ru.windcorp.progressia.common.world.rels.BlockFace;
+import ru.windcorp.progressia.common.world.rels.RelFace;
-public class ChunkRender implements GenericChunk {
+public class ChunkRender
+ implements ChunkGenericRO {
private final WorldRender world;
- private final ChunkData data;
+ private final DefaultChunkData data;
private final ChunkRenderModel model;
private final Map tileRenderLists = Collections
- .synchronizedMap(new WeakHashMap<>());
+ .synchronizedMap(new WeakHashMap<>());
- public ChunkRender(WorldRender world, ChunkData data) {
+ public ChunkRender(WorldRender world, DefaultChunkData data) {
this.world = world;
this.data = data;
this.model = new ChunkRenderModel(this);
@@ -54,10 +59,17 @@ public class ChunkRender implements GenericChunk optimizers = new ArrayList<>();
private Model model = null;
@@ -51,42 +53,48 @@ public class ChunkRenderModel implements Renderable {
@Override
public void render(ShapeRenderHelper renderer) {
- if (model == null)
- return;
-
- renderer.pushTransform().translate(chunk.getX() * ChunkData.BLOCKS_PER_CHUNK,
- chunk.getY() * ChunkData.BLOCKS_PER_CHUNK, chunk.getZ() * ChunkData.BLOCKS_PER_CHUNK);
+ if (model == null) return;
+
+ float offset = DefaultChunkData.BLOCKS_PER_CHUNK / 2 - 0.5f;
+
+ renderer.pushTransform().translate(
+ chunk.getX() * DefaultChunkData.BLOCKS_PER_CHUNK,
+ chunk.getY() * DefaultChunkData.BLOCKS_PER_CHUNK,
+ chunk.getZ() * DefaultChunkData.BLOCKS_PER_CHUNK
+ ).translate(offset, offset, offset)
+ .mul(AxisRotations.getResolutionMatrix4(chunk.getUp()))
+ .translate(-offset, -offset, -offset);
model.render(renderer);
renderer.popTransform();
}
-
+
public void update() {
setupCROs();
-
+
StaticModel.Builder sink = StaticModel.builder();
-
+
optimizers.forEach(ChunkRenderOptimizer::startRender);
-
- chunk.forEachBiC(blockInChunk -> {
- processBlockAndTiles(blockInChunk, sink);
+
+ GenericChunks.forEachBiC(relBlockInChunk -> {
+ processBlockAndTiles(relBlockInChunk, sink);
});
-
+
for (ChunkRenderOptimizer optimizer : optimizers) {
Renderable renderable = optimizer.endRender();
if (renderable != null) {
sink.addPart(renderable);
}
}
-
+
this.model = sink.build();
this.optimizers.clear();
}
private void setupCROs() {
Set ids = ChunkRenderOptimizerRegistry.getInstance().keySet();
-
+
for (String id : ids) {
ChunkRenderOptimizer optimizer = ChunkRenderOptimizerRegistry.getInstance().create(id);
optimizer.setup(chunk);
@@ -94,61 +102,65 @@ public class ChunkRenderModel implements Renderable {
}
}
- private void processBlockAndTiles(Vec3i blockInChunk, Builder sink) {
- processBlock(blockInChunk, sink);
-
- for (BlockFace face : BlockFace.getFaces()) {
- processTileStack(blockInChunk, face, sink);
+ private void processBlockAndTiles(Vec3i relBlockInChunk, Builder sink) {
+ processBlock(relBlockInChunk, sink);
+
+ for (RelFace face : RelFace.getFaces()) {
+ processTileStack(relBlockInChunk, face, sink);
}
}
- private void processBlock(Vec3i blockInChunk, Builder sink) {
- BlockRender block = chunk.getBlock(blockInChunk);
-
+ private void processBlock(Vec3i relBlockInChunk, Builder sink) {
+ BlockRender block = chunk.getBlockRel(relBlockInChunk);
+
if (block instanceof BlockRenderNone) {
return;
}
-
+
if (block.needsOwnRenderable()) {
- sink.addPart(block.createRenderable(chunk.getData(), blockInChunk),
- new Mat4().identity().translate(blockInChunk.x, blockInChunk.y, blockInChunk.z));
+ sink.addPart(
+ block.createRenderable(chunk.getData(), relBlockInChunk),
+ new Mat4().identity().translate(relBlockInChunk.x, relBlockInChunk.y, relBlockInChunk.z)
+ );
}
-
- processBlockWithCROs(block, blockInChunk);
+
+ processBlockWithCROs(block, relBlockInChunk);
}
- private void processBlockWithCROs(BlockRender block, Vec3i blockInChunk) {
+ private void processBlockWithCROs(BlockRender block, Vec3i relBlockInChunk) {
for (ChunkRenderOptimizer optimizer : optimizers) {
- optimizer.addBlock(block, blockInChunk);
+ optimizer.addBlock(block, relBlockInChunk);
}
}
- private void processTileStack(Vec3i blockInChunk, BlockFace face, Builder sink) {
- TileRenderStack trs = chunk.getTilesOrNull(blockInChunk, face);
-
+ private void processTileStack(Vec3i relBlockInChunk, RelFace face, Builder sink) {
+ TileRenderStack trs = chunk.getTilesOrNullRel(relBlockInChunk, face);
+
if (trs == null || trs.isEmpty()) {
return;
}
-
- trs.forEach(tile -> processTile(tile, blockInChunk, face, sink));
+
+ trs.forEach(tile -> processTile(tile, relBlockInChunk, face, sink));
}
- private void processTile(TileRender tile, Vec3i blockInChunk, BlockFace face, Builder sink) {
+ private void processTile(TileRender tile, Vec3i relBlockInChunk, RelFace face, Builder sink) {
if (tile instanceof TileRenderNone) {
return;
}
-
+
if (tile.needsOwnRenderable()) {
- sink.addPart(tile.createRenderable(chunk.getData(), blockInChunk, face),
- new Mat4().identity().translate(blockInChunk.x, blockInChunk.y, blockInChunk.z));
+ sink.addPart(
+ tile.createRenderable(chunk.getData(), relBlockInChunk, face),
+ new Mat4().identity().translate(relBlockInChunk.x, relBlockInChunk.y, relBlockInChunk.z)
+ );
}
-
- processTileWithCROs(tile, blockInChunk, face);
+
+ processTileWithCROs(tile, relBlockInChunk, face);
}
- private void processTileWithCROs(TileRender tile, Vec3i blockInChunk, BlockFace face) {
+ private void processTileWithCROs(TileRender tile, Vec3i relBlockInChunk, RelFace face) {
for (ChunkRenderOptimizer optimizer : optimizers) {
- optimizer.addTile(tile, blockInChunk, face);
+ optimizer.addTile(tile, relBlockInChunk, face);
}
}
diff --git a/src/main/java/ru/windcorp/progressia/client/world/ChunkUpdateListener.java b/src/main/java/ru/windcorp/progressia/client/world/ChunkUpdateListener.java
index 8e0eb1d..1e854f2 100644
--- a/src/main/java/ru/windcorp/progressia/client/world/ChunkUpdateListener.java
+++ b/src/main/java/ru/windcorp/progressia/client/world/ChunkUpdateListener.java
@@ -15,16 +15,17 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package ru.windcorp.progressia.client.world;
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.ChunkData;
+import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.ChunkDataListener;
import ru.windcorp.progressia.common.world.block.BlockData;
-import ru.windcorp.progressia.common.world.block.BlockFace;
+import ru.windcorp.progressia.common.world.rels.AbsFace;
+import ru.windcorp.progressia.common.world.rels.RelFace;
import ru.windcorp.progressia.common.world.tile.TileData;
class ChunkUpdateListener implements ChunkDataListener {
@@ -36,58 +37,63 @@ class ChunkUpdateListener implements ChunkDataListener {
}
@Override
- public void onChunkChanged(ChunkData chunk) {
+ public void onChunkChanged(DefaultChunkData chunk) {
world.getChunk(chunk).markForUpdate();
}
-
+
@Override
- public void onChunkLoaded(ChunkData chunk) {
+ public void onChunkLoaded(DefaultChunkData chunk) {
Vec3i cursor = new Vec3i();
- for (BlockFace face : BlockFace.getFaces()) {
+ for (AbsFace face : AbsFace.getFaces()) {
cursor.set(chunk.getX(), chunk.getY(), chunk.getZ());
cursor.add(face.getVector());
world.markChunkForUpdate(cursor);
}
}
-
+
@Override
- public void onChunkBlockChanged(ChunkData chunk, Vec3i blockInChunk, BlockData previous, BlockData current) {
+ public void onChunkBlockChanged(DefaultChunkData chunk, Vec3i blockInChunk, BlockData previous, BlockData current) {
+ onLocationChanged(chunk, blockInChunk);
+ }
+
+ @Override
+ public void onChunkTilesChanged(
+ DefaultChunkData chunk,
+ Vec3i blockInChunk,
+ RelFace face,
+ TileData tile,
+ boolean wasAdded
+ ) {
onLocationChanged(chunk, blockInChunk);
}
- @Override
- public void onChunkTilesChanged(ChunkData chunk, Vec3i blockInChunk, BlockFace face, TileData tile,
- boolean wasAdded) {
- onLocationChanged(chunk, blockInChunk);
- }
-
- private void onLocationChanged(ChunkData chunk, Vec3i blockInChunk) {
+ private void onLocationChanged(DefaultChunkData chunk, Vec3i blockInChunk) {
Vec3i chunkPos = Vectors.grab3i().set(chunk.getX(), chunk.getY(), chunk.getZ());
-
+
checkCoordinate(blockInChunk, chunkPos, VectorUtil.Axis.X);
checkCoordinate(blockInChunk, chunkPos, VectorUtil.Axis.Y);
checkCoordinate(blockInChunk, chunkPos, VectorUtil.Axis.Z);
-
+
Vectors.release(chunkPos);
}
private void checkCoordinate(Vec3i blockInChunk, Vec3i chunkPos, VectorUtil.Axis axis) {
int block = VectorUtil.get(blockInChunk, axis);
int diff = 0;
-
+
if (block == 0) {
diff = -1;
- } else if (block == ChunkData.BLOCKS_PER_CHUNK - 1) {
+ } else if (block == DefaultChunkData.BLOCKS_PER_CHUNK - 1) {
diff = +1;
} else {
return;
}
-
+
int previousChunkPos = VectorUtil.get(chunkPos, axis);
VectorUtil.set(chunkPos, axis, previousChunkPos + diff);
world.markChunkForUpdate(chunkPos);
-
+
VectorUtil.set(chunkPos, axis, previousChunkPos);
}
diff --git a/src/main/java/ru/windcorp/progressia/client/world/WorldRender.java b/src/main/java/ru/windcorp/progressia/client/world/WorldRender.java
index c8e63a9..2ab9e7d 100644
--- a/src/main/java/ru/windcorp/progressia/client/world/WorldRender.java
+++ b/src/main/java/ru/windcorp/progressia/client/world/WorldRender.java
@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package ru.windcorp.progressia.client.world;
import java.util.Collection;
@@ -34,57 +34,58 @@ import ru.windcorp.progressia.client.world.block.BlockRender;
import ru.windcorp.progressia.client.world.entity.EntityRenderRegistry;
import ru.windcorp.progressia.client.world.entity.EntityRenderable;
import ru.windcorp.progressia.client.world.tile.TileRender;
+import ru.windcorp.progressia.client.world.tile.TileRenderReference;
import ru.windcorp.progressia.client.world.tile.TileRenderStack;
import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.util.Vectors;
-import ru.windcorp.progressia.common.world.ChunkData;
+import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.ChunkDataListeners;
-import ru.windcorp.progressia.common.world.WorldData;
+import ru.windcorp.progressia.common.world.DefaultWorldData;
import ru.windcorp.progressia.common.world.WorldDataListener;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.generic.ChunkSet;
import ru.windcorp.progressia.common.world.generic.ChunkSets;
-import ru.windcorp.progressia.common.world.generic.GenericWorld;
+import ru.windcorp.progressia.common.world.generic.WorldGenericRO;
public class WorldRender
- implements GenericWorld {
+ implements WorldGenericRO {
- private final WorldData data;
+ private final DefaultWorldData data;
private final Client client;
- private final Map chunks = Collections.synchronizedMap(new HashMap<>());
+ private final Map chunks = Collections.synchronizedMap(new HashMap<>());
private final Map entityModels = Collections.synchronizedMap(new WeakHashMap<>());
private final ChunkSet chunksToUpdate = ChunkSets.newSyncHashSet();
- public WorldRender(WorldData data, Client client) {
+ public WorldRender(DefaultWorldData data, Client client) {
this.data = data;
this.client = client;
data.addListener(ChunkDataListeners.createAdder(new ChunkUpdateListener(this)));
data.addListener(new WorldDataListener() {
@Override
- public void onChunkLoaded(WorldData world, ChunkData chunk) {
+ public void onChunkLoaded(DefaultWorldData world, DefaultChunkData chunk) {
addChunk(chunk);
}
@Override
- public void beforeChunkUnloaded(WorldData world, ChunkData chunk) {
+ public void beforeChunkUnloaded(DefaultWorldData world, DefaultChunkData chunk) {
removeChunk(chunk);
}
});
}
- protected void addChunk(ChunkData chunk) {
+ protected void addChunk(DefaultChunkData chunk) {
chunks.put(chunk, new ChunkRender(WorldRender.this, chunk));
markChunkForUpdate(chunk.getPosition());
}
- protected void removeChunk(ChunkData chunk) {
+ protected void removeChunk(DefaultChunkData chunk) {
chunks.remove(chunk);
}
- public WorldData getData() {
+ public DefaultWorldData getData() {
return data;
}
@@ -92,7 +93,7 @@ public class WorldRender
return client;
}
- public ChunkRender getChunk(ChunkData chunkData) {
+ public ChunkRender getChunk(DefaultChunkData chunkData) {
return chunks.get(chunkData);
}
@@ -110,6 +111,13 @@ public class WorldRender
public Collection getEntities() {
return entityModels.values();
}
+
+ @Override
+ public EntityRenderable getEntity(long entityId) {
+ EntityData entityData = getData().getEntity(entityId);
+ if (entityData == null) return null;
+ return getEntityRenderable(entityData);
+ }
public void render(ShapeRenderHelper renderer) {
updateChunks();
@@ -207,11 +215,15 @@ public class WorldRender
}
public EntityRenderable getEntityRenderable(EntityData entity) {
- return entityModels.computeIfAbsent(entity, WorldRender::createEntityRenderable);
+ return entityModels.computeIfAbsent(
+ entity,
+ WorldRender::createEntityRenderable
+ );
}
private static EntityRenderable createEntityRenderable(EntityData entity) {
- return EntityRenderRegistry.getInstance().get(entity.getId()).createRenderable(entity);
+ return EntityRenderRegistry.getInstance().get(entity.getId())
+ .createRenderable(entity);
}
public void markChunkForUpdate(Vec3i chunkPos) {
diff --git a/src/main/java/ru/windcorp/progressia/client/world/block/BlockRender.java b/src/main/java/ru/windcorp/progressia/client/world/block/BlockRender.java
index 9b6c7ca..6adbfde 100644
--- a/src/main/java/ru/windcorp/progressia/client/world/block/BlockRender.java
+++ b/src/main/java/ru/windcorp/progressia/client/world/block/BlockRender.java
@@ -15,27 +15,22 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package ru.windcorp.progressia.client.world.block;
-import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
-import ru.windcorp.progressia.common.world.ChunkData;
-import ru.windcorp.progressia.common.world.generic.GenericBlock;
+import ru.windcorp.progressia.common.world.DefaultChunkData;
+import ru.windcorp.progressia.common.world.generic.BlockGeneric;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.model.Renderable;
-public abstract class BlockRender extends Namespaced implements GenericBlock {
+public abstract class BlockRender extends Namespaced implements BlockGeneric {
public BlockRender(String id) {
super(id);
}
- public void render(ShapeRenderHelper renderer) {
- throw new UnsupportedOperationException("BlockRender.render() not implemented in " + this);
- }
-
- public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk) {
+ public Renderable createRenderable(DefaultChunkData chunk, Vec3i relBlockInChunk) {
return null;
}
diff --git a/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderNone.java b/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderNone.java
index 3f422f2..7e60358 100644
--- a/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderNone.java
+++ b/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderNone.java
@@ -21,7 +21,7 @@ package ru.windcorp.progressia.client.world.block;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.model.EmptyModel;
import ru.windcorp.progressia.client.graphics.model.Renderable;
-import ru.windcorp.progressia.common.world.ChunkData;
+import ru.windcorp.progressia.common.world.DefaultChunkData;
public class BlockRenderNone extends BlockRender {
@@ -30,7 +30,7 @@ public class BlockRenderNone extends BlockRender {
}
@Override
- public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk) {
+ public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk) {
return EmptyModel.getInstance();
}
diff --git a/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderOpaqueCube.java b/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderOpaqueCube.java
index 5b2dc27..01a6011 100644
--- a/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderOpaqueCube.java
+++ b/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderOpaqueCube.java
@@ -15,25 +15,60 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package ru.windcorp.progressia.client.world.block;
import ru.windcorp.progressia.client.graphics.texture.Texture;
-import ru.windcorp.progressia.common.world.block.BlockFace;
+import ru.windcorp.progressia.common.world.rels.RelFace;
public class BlockRenderOpaqueCube extends BlockRenderTexturedCube {
- public BlockRenderOpaqueCube(String id, Texture topTexture, Texture bottomTexture, Texture northTexture,
- Texture southTexture, Texture eastTexture, Texture westTexture) {
- super(id, topTexture, bottomTexture, northTexture, southTexture, eastTexture, westTexture);
+ public BlockRenderOpaqueCube(
+ String id,
+ Texture topTexture,
+ Texture bottomTexture,
+ Texture northTexture,
+ Texture southTexture,
+ Texture westTexture,
+ Texture eastTexture
+ ) {
+ super(
+ id,
+ topTexture,
+ bottomTexture,
+ northTexture,
+ southTexture,
+ westTexture,
+ eastTexture
+ );
}
public BlockRenderOpaqueCube(String id, Texture texture) {
- this(id, texture, texture, texture, texture, texture, texture);
+ this(
+ id,
+ texture,
+ texture,
+ texture,
+ texture,
+ texture,
+ texture
+ );
+ }
+
+ public BlockRenderOpaqueCube(String id, Texture topTexture, Texture bottomTexture, Texture sideTexture) {
+ this(
+ id,
+ topTexture,
+ bottomTexture,
+ sideTexture,
+ sideTexture,
+ sideTexture,
+ sideTexture
+ );
}
@Override
- public boolean isOpaque(BlockFace face) {
+ public boolean isOpaque(RelFace face) {
return true;
}
diff --git a/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderTexturedCube.java b/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderTexturedCube.java
index d254798..91d05e1 100644
--- a/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderTexturedCube.java
+++ b/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderTexturedCube.java
@@ -15,12 +15,11 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package ru.windcorp.progressia.client.world.block;
-import static ru.windcorp.progressia.common.world.block.BlockFace.*;
+import static ru.windcorp.progressia.common.world.rels.AbsFace.*;
-import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
@@ -29,69 +28,86 @@ import glm.vec._3.i.Vec3i;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.backend.Usage;
-import ru.windcorp.progressia.client.graphics.model.Face;
-import ru.windcorp.progressia.client.graphics.model.Faces;
+import ru.windcorp.progressia.client.graphics.model.ShapePart;
+import ru.windcorp.progressia.client.graphics.model.ShapeParts;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSurface.BlockOptimizedSurface;
import ru.windcorp.progressia.common.util.Vectors;
-import ru.windcorp.progressia.common.world.ChunkData;
-import ru.windcorp.progressia.common.world.block.BlockFace;
+import ru.windcorp.progressia.common.world.DefaultChunkData;
+import ru.windcorp.progressia.common.world.rels.AbsFace;
+import ru.windcorp.progressia.common.world.rels.RelFace;
-public abstract class BlockRenderTexturedCube extends BlockRender implements BlockOptimizedSurface {
+public abstract class BlockRenderTexturedCube
+ extends BlockRender
+ implements BlockOptimizedSurface {
- private final Map textures = new HashMap<>();
+ private final Map textures;
- public BlockRenderTexturedCube(String id, Texture topTexture, Texture bottomTexture, Texture northTexture,
- Texture southTexture, Texture eastTexture, Texture westTexture) {
+ public BlockRenderTexturedCube(
+ String id,
+ Texture topTexture,
+ Texture bottomTexture,
+ Texture northTexture,
+ Texture southTexture,
+ Texture westTexture,
+ Texture eastTexture
+ ) {
super(id);
-
- textures.put(TOP, topTexture);
- textures.put(BOTTOM, bottomTexture);
- textures.put(NORTH, northTexture);
- textures.put(SOUTH, southTexture);
- textures.put(EAST, eastTexture);
- textures.put(WEST, westTexture);
+ this.textures = RelFace.mapToFaces(topTexture, bottomTexture, northTexture, southTexture, westTexture, eastTexture);
}
- public Texture getTexture(BlockFace blockFace) {
+ public Texture getTexture(RelFace blockFace) {
return textures.get(blockFace);
}
-
- public Vec4 getColorMultiplier(BlockFace blockFace) {
+
+ public Vec4 getColorMultiplier(RelFace blockFace) {
return Colors.WHITE;
}
@Override
- public final void getFaces(ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace, boolean inner,
- Consumer output, Vec3 offset) {
+ public final void getShapeParts(
+ DefaultChunkData chunk, Vec3i blockInChunk, RelFace blockFace,
+ boolean inner,
+ Consumer output,
+ Vec3 offset
+ ) {
output.accept(createFace(chunk, blockInChunk, blockFace, inner, offset));
}
-
- private Face createFace(ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace, boolean inner, Vec3 offset) {
- return Faces.createBlockFace(WorldRenderProgram.getDefault(), getTexture(blockFace),
- getColorMultiplier(blockFace), offset, blockFace, inner);
+
+ private ShapePart createFace(
+ DefaultChunkData chunk, Vec3i blockInChunk, RelFace blockFace,
+ boolean inner,
+ Vec3 offset
+ ) {
+ return ShapeParts.createBlockFace(
+ WorldRenderProgram.getDefault(),
+ getTexture(blockFace),
+ getColorMultiplier(blockFace),
+ offset,
+ blockFace.resolve(AbsFace.POS_Z),
+ inner
+ );
}
@Override
- public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk) {
+ public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk) {
boolean opaque = isBlockOpaque();
-
- Face[] faces = new Face[BLOCK_FACE_COUNT + (opaque ? BLOCK_FACE_COUNT : 0)];
-
+
+ ShapePart[] faces = new ShapePart[BLOCK_FACE_COUNT + (opaque ? BLOCK_FACE_COUNT : 0)];
+
for (int i = 0; i < BLOCK_FACE_COUNT; ++i) {
- faces[i] = createFace(chunk, blockInChunk, BlockFace.getFaces().get(i), false, Vectors.ZERO_3);
+ faces[i] = createFace(chunk, blockInChunk, RelFace.getFaces().get(i), false, Vectors.ZERO_3);
}
-
+
if (!opaque) {
for (int i = 0; i < BLOCK_FACE_COUNT; ++i) {
- faces[i + BLOCK_FACE_COUNT] = createFace(chunk, blockInChunk, BlockFace.getFaces().get(i), true,
- Vectors.ZERO_3);
+ faces[i + BLOCK_FACE_COUNT] = createFace(chunk, blockInChunk, RelFace.getFaces().get(i), true, Vectors.ZERO_3);
}
}
-
+
return new Shape(Usage.STATIC, WorldRenderProgram.getDefault(), faces);
}
diff --git a/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderTransparentCube.java b/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderTransparentCube.java
index 944d451..f9801ca 100644
--- a/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderTransparentCube.java
+++ b/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderTransparentCube.java
@@ -15,25 +15,60 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package ru.windcorp.progressia.client.world.block;
import ru.windcorp.progressia.client.graphics.texture.Texture;
-import ru.windcorp.progressia.common.world.block.BlockFace;
+import ru.windcorp.progressia.common.world.rels.RelFace;
public class BlockRenderTransparentCube extends BlockRenderTexturedCube {
- public BlockRenderTransparentCube(String id, Texture topTexture, Texture bottomTexture, Texture northTexture,
- Texture southTexture, Texture eastTexture, Texture westTexture) {
- super(id, topTexture, bottomTexture, northTexture, southTexture, eastTexture, westTexture);
+ public BlockRenderTransparentCube(
+ String id,
+ Texture topTexture,
+ Texture bottomTexture,
+ Texture northTexture,
+ Texture southTexture,
+ Texture westTexture,
+ Texture eastTexture
+ ) {
+ super(
+ id,
+ topTexture,
+ bottomTexture,
+ northTexture,
+ southTexture,
+ westTexture,
+ eastTexture
+ );
}
public BlockRenderTransparentCube(String id, Texture texture) {
- this(id, texture, texture, texture, texture, texture, texture);
+ this(
+ id,
+ texture,
+ texture,
+ texture,
+ texture,
+ texture,
+ texture
+ );
+ }
+
+ public BlockRenderTransparentCube(String id, Texture topTexture, Texture bottomTexture, Texture sideTexture) {
+ this(
+ id,
+ topTexture,
+ bottomTexture,
+ sideTexture,
+ sideTexture,
+ sideTexture,
+ sideTexture
+ );
}
@Override
- public boolean isOpaque(BlockFace face) {
+ public boolean isOpaque(RelFace face) {
return false;
}
diff --git a/src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizer.java b/src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizer.java
index ddf923a..12396dc 100644
--- a/src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizer.java
+++ b/src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizer.java
@@ -24,7 +24,7 @@ import ru.windcorp.progressia.client.world.ChunkRender;
import ru.windcorp.progressia.client.world.block.BlockRender;
import ru.windcorp.progressia.client.world.tile.TileRender;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
-import ru.windcorp.progressia.common.world.block.BlockFace;
+import ru.windcorp.progressia.common.world.rels.RelFace;
/**
* Chunk render optimizer (CRO) is an object that produces optimized models for
@@ -35,15 +35,22 @@ import ru.windcorp.progressia.common.world.block.BlockFace;
* tiles. An example of a CRO is {@link ChunkRenderOptimizerSurface}: it removes
* block surfaces and tiles that it knows cannot be seen, thus significantly
* reducing total polygon count.
- *
CRO lifecycle
A CRO instance is created by
- * {@link ChunkRenderOptimizerRegistry}. It may then be used to work on multiple
- * chunks sequentially. Each chunk is processed in the following way:
+ *
+ * As with everything related to rendering chunks, CROs are interacted with
+ * using the relative local chunk coordinate system. In this coordinate system,
+ * the coordinates are the chunk coordinates relativized using the chunks's up
+ * direction. In simpler terms, coordinates are {@code [0; BLOCKS_PER_CHUNK)}
+ * and Z is always up.
+ *
CRO lifecycle
+ * A CRO instance is created by {@link ChunkRenderOptimizerRegistry}. It may
+ * then be used to work on multiple chunks sequentially. Each chunk is processed
+ * in the following way:
*
*
{@link #setup(ChunkRender)} is invoked to provide the {@link ChunkRender}
* instance.
*
{@link #startRender()} is invoked. The CRO must reset its state.
*
{@link #addBlock(BlockRender, Vec3i)} and
- * {@link #addTile(TileRender, Vec3i, BlockFace)} are invoked for each block and
+ * {@link #addTile(TileRender, Vec3i, RelFace)} are invoked for each block and
* tile that this CRO should optimize. {@code addTile} specifies tiles in order
* of ascension within a tile stack.
*
{@link #endRender()} is invoked. The CRO may perform any pending
@@ -62,8 +69,7 @@ public abstract class ChunkRenderOptimizer extends Namespaced {
/**
* Creates a new CRO instance with the specified ID.
*
- * @param id
- * the ID of this CRO
+ * @param id the ID of this CRO
*/
public ChunkRenderOptimizer(String id) {
super(id);
@@ -74,8 +80,7 @@ public abstract class ChunkRenderOptimizer extends Namespaced {
* specify the chunk. When overriding, {@code super.setup(chunk)} must be
* invoked.
*
- * @param chunk
- * the chunk that will be processed next
+ * @param chunk the chunk that will be processed next
*/
public void setup(ChunkRender chunk) {
this.chunk = chunk;
@@ -99,13 +104,13 @@ public abstract class ChunkRenderOptimizer extends Namespaced {
* method is only invoked once per block. This method is not necessarily
* invoked for each block.
*
- * @param block
- * a {@link BlockRender} instance describing the block. It
- * corresponds to {@code getChunk().getBlock(blockInChunk)}.
- * @param blockInChunk
- * the position of the block
+ * @param block a {@link BlockRender} instance describing the
+ * block.
+ * It corresponds to
+ * {@code getChunk().getBlock(blockInChunk)}.
+ * @param relBlockInChunk the relative position of the block
*/
- public abstract void addBlock(BlockRender block, Vec3i blockInChunk);
+ public abstract void addBlock(BlockRender block, Vec3i relBlockInChunk);
/**
* Requests that this CRO processes the provided tile. This method may only
@@ -114,14 +119,12 @@ public abstract class ChunkRenderOptimizer extends Namespaced {
* invoked for each tile. When multiple tiles in a tile stack are requested,
* this method is invoked for lower tiles first.
*
- * @param tile
- * a {@link BlockRender} instance describing the tile
- * @param blockInChunk
- * the position of the block that the tile belongs to
- * @param blockFace
- * the face that the tile belongs to
+ * @param tile a {@link BlockRender} instance describing the tile
+ * @param relBlockInChunk the relative position of the block that the tile
+ * belongs to
+ * @param blockFace the face that the tile belongs to
*/
- public abstract void addTile(TileRender tile, Vec3i blockInChunk, BlockFace blockFace);
+ public abstract void addTile(TileRender tile, Vec3i relBlockInChunk, RelFace blockFace);
/**
* Requests that the CRO assembles and outputs its model. This method may
diff --git a/src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizerSurface.java b/src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizerSurface.java
index 12b4275..296e7b8 100644
--- a/src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizerSurface.java
+++ b/src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizerSurface.java
@@ -18,9 +18,9 @@
package ru.windcorp.progressia.client.world.cro;
-import static ru.windcorp.progressia.common.world.ChunkData.BLOCKS_PER_CHUNK;
-import static ru.windcorp.progressia.common.world.block.BlockFace.BLOCK_FACE_COUNT;
-import static ru.windcorp.progressia.common.world.generic.GenericTileStack.TILES_PER_FACE;
+import static ru.windcorp.progressia.common.world.DefaultChunkData.BLOCKS_PER_CHUNK;
+import static ru.windcorp.progressia.common.world.generic.TileGenericStackRO.TILES_PER_FACE;
+import static ru.windcorp.progressia.common.world.rels.AbsFace.BLOCK_FACE_COUNT;
import java.util.ArrayList;
import java.util.Collection;
@@ -29,7 +29,7 @@ import java.util.function.Consumer;
import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.backend.Usage;
-import ru.windcorp.progressia.client.graphics.model.Face;
+import ru.windcorp.progressia.client.graphics.model.ShapePart;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
@@ -37,8 +37,9 @@ import ru.windcorp.progressia.client.world.ChunkRender;
import ru.windcorp.progressia.client.world.block.BlockRender;
import ru.windcorp.progressia.client.world.tile.TileRender;
import ru.windcorp.progressia.common.util.Vectors;
-import ru.windcorp.progressia.common.world.ChunkData;
-import ru.windcorp.progressia.common.world.block.BlockFace;
+import ru.windcorp.progressia.common.world.DefaultChunkData;
+import ru.windcorp.progressia.common.world.generic.GenericChunks;
+import ru.windcorp.progressia.common.world.rels.RelFace;
public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
@@ -52,38 +53,42 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
private static interface OptimizedSurface {
/**
- * Creates and outputs a set of faces that correspond to this surface.
- * The coordinates of the face vertices must be in chunk coordinate
- * system.
+ * Creates and outputs a set of shape parts that correspond to this
+ * surface. The coordinates of the face vertices must be in chunk
+ * coordinate system.
*
- * @param chunk
- * the chunk that contains the requested face
- * @param blockInChunk
- * the block in chunk
- * @param blockFace
- * the requested face
- * @param inner
- * whether this face should be visible from inside
- * ({@code true}) or outside ({@code false})
- * @param output
- * a consumer that the created faces must be given to
- * @param offset
- * an additional offset that must be applied to all vertices
+ * @param chunk the chunk that contains the requested face
+ * @param relBlockInChunk the relative block in chunk
+ * @param blockFace the requested face
+ * @param inner whether this face should be visible from
+ * inside
+ * ({@code true}) or outside ({@code false})
+ * @param output a consumer that the created shape parts must
+ * be
+ * given to
+ * @param offset an additional offset that must be applied to
+ * all
+ * vertices
*/
- void getFaces(ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace, boolean inner, Consumer output,
- Vec3 offset /* kostyl 156% */
+ void getShapeParts(
+ DefaultChunkData chunk,
+ Vec3i relBlockInChunk,
+ RelFace blockFace,
+ boolean inner,
+ Consumer output,
+ Vec3 offset /* kostyl 156% */
);
/**
* Returns the opacity of the surface identified by the provided
- * {@link BlockFace}. Opaque surfaces prevent surfaces behind them from
- * being included in chunk models.
+ * {@link RelFace}.
+ * Opaque surfaces prevent surfaces behind them from being included in
+ * chunk models.
*
- * @param blockFace
- * the face to query
+ * @param blockFace the face to query
* @return {@code true} iff the surface is opaque.
*/
- boolean isOpaque(BlockFace blockFace);
+ boolean isOpaque(RelFace blockFace);
}
/**
@@ -94,7 +99,8 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
/**
* Returns the opacity of the block. Opaque blocks do not expect that
* the camera can be inside them. Opaque blocks prevent surfaces that
- * face them from being included in chunk models.
+ * face them
+ * from being included in chunk models.
*
* @return {@code true} iff the block is opaque.
*/
@@ -157,29 +163,29 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
}
@Override
- public void addBlock(BlockRender block, Vec3i pos) {
+ public void addBlock(BlockRender block, Vec3i relBlockInChunk) {
if (!(block instanceof BlockOptimizedSurface))
return;
BlockOptimizedSurface bos = (BlockOptimizedSurface) block;
- addBlock(pos, bos);
+ addBlock(relBlockInChunk, bos);
}
@Override
- public void addTile(TileRender tile, Vec3i pos, BlockFace face) {
+ public void addTile(TileRender tile, Vec3i relBlockInChunk, RelFace face) {
if (!(tile instanceof TileOptimizedSurface))
return;
TileOptimizedSurface tos = (TileOptimizedSurface) tile;
- addTile(pos, face, tos);
+ addTile(relBlockInChunk, face, tos);
}
- protected void addBlock(Vec3i pos, BlockOptimizedSurface block) {
- getBlock(pos).block = block;
+ private void addBlock(Vec3i relBlockInChunk, BlockOptimizedSurface block) {
+ getBlock(relBlockInChunk).block = block;
}
- private void addTile(Vec3i pos, BlockFace face, TileOptimizedSurface tile) {
- FaceInfo faceInfo = getFace(pos, face);
+ private void addTile(Vec3i relBlockInChunk, RelFace face, TileOptimizedSurface tile) {
+ FaceInfo faceInfo = getFace(relBlockInChunk, face);
int index = faceInfo.tileCount;
faceInfo.tileCount++;
@@ -195,119 +201,130 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
}
}
- protected BlockInfo getBlock(Vec3i cursor) {
- return data[cursor.x][cursor.y][cursor.z];
+ protected BlockInfo getBlock(Vec3i relBlockInChunk) {
+ return data[relBlockInChunk.x][relBlockInChunk.y][relBlockInChunk.z];
}
- protected FaceInfo getFace(Vec3i cursor, BlockFace face) {
- return getBlock(cursor).faces[face.getId()];
+ protected FaceInfo getFace(Vec3i relBlockInChunk, RelFace face) {
+ return getBlock(relBlockInChunk).faces[face.getId()];
}
@Override
public Renderable endRender() {
- Collection shapeFaces = new ArrayList<>(BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * 3);
+ Collection shapeParts = new ArrayList<>(
+ BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * 3
+ );
- Vec3i cursor = new Vec3i();
- Consumer consumer = shapeFaces::add;
+ Consumer consumer = shapeParts::add;
- for (cursor.x = 0; cursor.x < BLOCKS_PER_CHUNK; ++cursor.x) {
- for (cursor.y = 0; cursor.y < BLOCKS_PER_CHUNK; ++cursor.y) {
- for (cursor.z = 0; cursor.z < BLOCKS_PER_CHUNK; ++cursor.z) {
- processInnerFaces(cursor, consumer);
- processOuterFaces(cursor, consumer);
- }
- }
- }
+ GenericChunks.forEachBiC(relBlockInChunk -> {
+ processInnerFaces(relBlockInChunk, consumer);
+ processOuterFaces(relBlockInChunk, consumer);
+ });
- if (shapeFaces.isEmpty()) {
+ if (shapeParts.isEmpty()) {
return null;
}
- return new Shape(Usage.STATIC, WorldRenderProgram.getDefault(),
- shapeFaces.toArray(new Face[shapeFaces.size()]));
+ return new Shape(
+ Usage.STATIC,
+ WorldRenderProgram.getDefault(),
+ shapeParts.toArray(new ShapePart[shapeParts.size()])
+ );
}
- private void processOuterFaces(Vec3i blockInChunk, Consumer output) {
- for (BlockFace blockFace : BlockFace.getFaces()) {
- processOuterFace(blockInChunk, blockFace, output);
+ private void processOuterFaces(
+ Vec3i relBlockInChunk,
+ Consumer output
+ ) {
+ for (RelFace blockFace : RelFace.getFaces()) {
+ processOuterFace(relBlockInChunk, blockFace, output);
}
}
- private void processOuterFace(Vec3i blockInChunk, BlockFace blockFace, Consumer output) {
- if (!shouldRenderOuterFace(blockInChunk, blockFace))
+ private void processOuterFace(Vec3i relBlockInChunk, RelFace blockFace, Consumer output) {
+ if (!shouldRenderOuterFace(relBlockInChunk, blockFace))
return;
- FaceInfo info = getFace(blockInChunk, blockFace);
+ FaceInfo info = getFace(relBlockInChunk, blockFace);
if (info.tileCount == 0 && info.block.block == null)
return;
- Vec3 faceOrigin = new Vec3(blockInChunk.x, blockInChunk.y, blockInChunk.z);
- Vec3 offset = new Vec3(blockFace.getFloatVector()).mul(OVERLAY_OFFSET);
+ Vec3 faceOrigin = new Vec3(relBlockInChunk.x, relBlockInChunk.y, relBlockInChunk.z);
+ Vec3 offset = new Vec3(blockFace.getRelFloatVector()).mul(OVERLAY_OFFSET);
- for (int layer = info.topOpaqueSurface; layer < info.tileCount; ++layer) {
+ for (
+ int layer = info.topOpaqueSurface;
+ layer < info.tileCount;
+ ++layer
+ ) {
OptimizedSurface surface = info.getSurface(layer);
if (surface == null)
continue; // layer may be BLOCK_LAYER, then block may be null
- surface.getFaces(chunk.getData(), blockInChunk, blockFace, false, output, faceOrigin);
+ surface.getShapeParts(chunk.getData(), relBlockInChunk, blockFace, false, output, faceOrigin);
faceOrigin.add(offset);
}
}
- private void processInnerFaces(Vec3i blockInChunk, Consumer output) {
- for (BlockFace blockFace : BlockFace.getFaces()) {
- processInnerFace(blockInChunk, blockFace, output);
+ private void processInnerFaces(Vec3i relBlockInChunk, Consumer output) {
+ for (RelFace blockFace : RelFace.getFaces()) {
+ processInnerFace(relBlockInChunk, blockFace, output);
}
}
- private void processInnerFace(Vec3i blockInChunk, BlockFace blockFace, Consumer output) {
- if (!shouldRenderInnerFace(blockInChunk, blockFace))
+ private void processInnerFace(Vec3i relBlockInChunk, RelFace blockFace, Consumer output) {
+ if (!shouldRenderInnerFace(relBlockInChunk, blockFace))
return;
- FaceInfo info = getFace(blockInChunk, blockFace);
+ FaceInfo info = getFace(relBlockInChunk, blockFace);
if (info.tileCount == 0 && info.block.block == null)
return;
- Vec3 faceOrigin = new Vec3(blockInChunk.x, blockInChunk.y, blockInChunk.z);
- Vec3 offset = new Vec3(blockFace.getFloatVector()).mul(OVERLAY_OFFSET);
+ Vec3 faceOrigin = new Vec3(relBlockInChunk.x, relBlockInChunk.y, relBlockInChunk.z);
+ Vec3 offset = new Vec3(blockFace.getRelFloatVector()).mul(OVERLAY_OFFSET);
- for (int layer = FaceInfo.BLOCK_LAYER; layer <= info.bottomOpaqueSurface && layer < info.tileCount; ++layer) {
+ for (
+ int layer = FaceInfo.BLOCK_LAYER;
+ layer <= info.bottomOpaqueSurface && layer < info.tileCount;
+ ++layer
+ ) {
OptimizedSurface surface = info.getSurface(layer);
if (surface == null)
continue; // layer may be BLOCK_LAYER, then block may be null
- surface.getFaces(chunk.getData(), blockInChunk, blockFace, true, output, faceOrigin);
+ surface.getShapeParts(chunk.getData(), relBlockInChunk, blockFace, true, output, faceOrigin);
faceOrigin.add(offset);
}
}
- private boolean shouldRenderOuterFace(Vec3i blockInChunk, BlockFace face) {
- blockInChunk.add(face.getVector());
+ private boolean shouldRenderOuterFace(Vec3i relBlockInChunk, RelFace face) {
+ relBlockInChunk.add(face.getRelVector());
try {
- return shouldRenderWhenFacing(blockInChunk, face);
+ return shouldRenderWhenFacing(relBlockInChunk, face);
} finally {
- blockInChunk.sub(face.getVector());
+ relBlockInChunk.sub(face.getRelVector());
}
}
- private boolean shouldRenderInnerFace(Vec3i blockInChunk, BlockFace face) {
- return shouldRenderWhenFacing(blockInChunk, face);
+ private boolean shouldRenderInnerFace(Vec3i relBlockInChunk, RelFace face) {
+ return shouldRenderWhenFacing(relBlockInChunk, face);
}
- private boolean shouldRenderWhenFacing(Vec3i blockInChunk, BlockFace face) {
- if (chunk.containsBiC(blockInChunk)) {
- return shouldRenderWhenFacingLocal(blockInChunk, face);
+ private boolean shouldRenderWhenFacing(Vec3i relBlockInChunk, RelFace face) {
+ if (GenericChunks.containsBiC(relBlockInChunk)) {
+ return shouldRenderWhenFacingLocal(relBlockInChunk, face);
} else {
- return shouldRenderWhenFacingNeighbor(blockInChunk, face);
+ return shouldRenderWhenFacingNeighbor(relBlockInChunk, face);
}
}
- private boolean shouldRenderWhenFacingLocal(Vec3i blockInChunk, BlockFace face) {
- BlockOptimizedSurface block = getBlock(blockInChunk).block;
+ private boolean shouldRenderWhenFacingLocal(Vec3i relBlockInChunk, RelFace face) {
+ BlockOptimizedSurface block = getBlock(relBlockInChunk).block;
if (block == null) {
return true;
@@ -319,33 +336,38 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
return true;
}
- private boolean shouldRenderWhenFacingNeighbor(Vec3i blockInLocalChunk, BlockFace face) {
- Vec3i blockInChunk = Vectors.grab3i().set(blockInLocalChunk.x, blockInLocalChunk.y, blockInLocalChunk.z);
+ private boolean shouldRenderWhenFacingNeighbor(Vec3i relBlockInLocalChunk, RelFace face) {
+ Vec3i blockInChunk = Vectors.grab3i();
+ chunk.resolve(relBlockInLocalChunk, blockInChunk);
Vec3i chunkPos = Vectors.grab3i().set(chunk.getX(), chunk.getY(), chunk.getZ());
try {
// Determine blockInChunk and chunkPos
- if (blockInLocalChunk.x == -1) {
+ if (blockInChunk.x == -1) {
blockInChunk.x = BLOCKS_PER_CHUNK - 1;
chunkPos.x -= 1;
- } else if (blockInLocalChunk.x == BLOCKS_PER_CHUNK) {
+ } else if (blockInChunk.x == BLOCKS_PER_CHUNK) {
blockInChunk.x = 0;
chunkPos.x += 1;
- } else if (blockInLocalChunk.y == -1) {
+ } else if (blockInChunk.y == -1) {
blockInChunk.y = BLOCKS_PER_CHUNK - 1;
chunkPos.y -= 1;
- } else if (blockInLocalChunk.y == BLOCKS_PER_CHUNK) {
+ } else if (blockInChunk.y == BLOCKS_PER_CHUNK) {
blockInChunk.y = 0;
chunkPos.y += 1;
- } else if (blockInLocalChunk.z == -1) {
+ } else if (blockInChunk.z == -1) {
blockInChunk.z = BLOCKS_PER_CHUNK - 1;
chunkPos.z -= 1;
- } else if (blockInLocalChunk.z == BLOCKS_PER_CHUNK) {
+ } else if (blockInChunk.z == BLOCKS_PER_CHUNK) {
blockInChunk.z = 0;
chunkPos.z += 1;
} else {
- throw new AssertionError("Requested incorrent neighbor (" + blockInLocalChunk.x + "; "
- + blockInLocalChunk.y + "; " + blockInLocalChunk.z + ")");
+ throw new AssertionError(
+ "Requested incorrent neighbor ("
+ + relBlockInLocalChunk.x + "; "
+ + relBlockInLocalChunk.y + "; "
+ + relBlockInLocalChunk.z + ")"
+ );
}
ChunkRender chunk = this.chunk.getWorld().getChunk(chunkPos);
@@ -357,8 +379,11 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
return true;
BlockOptimizedSurface bos = (BlockOptimizedSurface) block;
- if (!bos.isOpaque(face))
+ RelFace rotatedFace = face.rotate(this.chunk.getUp(), chunk.getUp());
+
+ if (!bos.isOpaque(rotatedFace)) {
return true;
+ }
return false;
diff --git a/src/main/java/ru/windcorp/progressia/client/world/entity/EntityRenderable.java b/src/main/java/ru/windcorp/progressia/client/world/entity/EntityRenderable.java
index 361f7cd..1749ffb 100644
--- a/src/main/java/ru/windcorp/progressia/client/world/entity/EntityRenderable.java
+++ b/src/main/java/ru/windcorp/progressia/client/world/entity/EntityRenderable.java
@@ -19,18 +19,45 @@
package ru.windcorp.progressia.client.world.entity;
import glm.vec._3.Vec3;
+import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.model.Renderable;
+import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.common.world.entity.EntityData;
-import ru.windcorp.progressia.common.world.generic.GenericEntity;
+import ru.windcorp.progressia.common.world.generic.EntityGeneric;
-public abstract class EntityRenderable implements Renderable, GenericEntity {
+public abstract class EntityRenderable implements Renderable, EntityGeneric {
private final EntityData data;
+
+ private long stateComputedForFrame = -1;
public EntityRenderable(EntityData data) {
this.data = data;
}
+ /**
+ * Updates the state of this model. This method is invoked exactly once per
+ * renderable per frame before this entity is queried for the first time.
+ */
+ protected void update() {
+ // Do nothing
+ }
+
+ private void updateIfNecessary() {
+ if (stateComputedForFrame != GraphicsInterface.getFramesRendered()) {
+ update();
+ stateComputedForFrame = GraphicsInterface.getFramesRendered();
+ }
+ }
+
+ @Override
+ public final void render(ShapeRenderHelper renderer) {
+ updateIfNecessary();
+ doRender(renderer);
+ }
+
+ protected abstract void doRender(ShapeRenderHelper renderer);
+
public EntityData getData() {
return data;
}
@@ -44,8 +71,42 @@ public abstract class EntityRenderable implements Renderable, GenericEntity {
public String getId() {
return getData().getId();
}
+
+ @Override
+ public long getEntityId() {
+ return getData().getEntityId();
+ }
- public void getViewPoint(Vec3 output) {
+ public final Vec3 getLookingAt(Vec3 output) {
+ if (output == null) output = new Vec3();
+ updateIfNecessary();
+ doGetLookingAt(output);
+ return output;
+ }
+
+ protected void doGetLookingAt(Vec3 output) {
+ output.set(getData().getLookingAt());
+ }
+
+ public final Vec3 getUpVector(Vec3 output) {
+ if (output == null) output = new Vec3();
+ updateIfNecessary();
+ doGetUpVector(output);
+ return output;
+ }
+
+ protected void doGetUpVector(Vec3 output) {
+ output.set(getData().getUpVector());
+ }
+
+ public final Vec3 getViewPoint(Vec3 output) {
+ if (output == null) output = new Vec3();
+ updateIfNecessary();
+ doGetViewPoint(output);
+ return output;
+ }
+
+ protected void doGetViewPoint(Vec3 output) {
output.set(0, 0, 0);
}
diff --git a/src/main/java/ru/windcorp/progressia/client/world/entity/HumanoidModel.java b/src/main/java/ru/windcorp/progressia/client/world/entity/HumanoidModel.java
index 2f13407..205981e 100644
--- a/src/main/java/ru/windcorp/progressia/client/world/entity/HumanoidModel.java
+++ b/src/main/java/ru/windcorp/progressia/client/world/entity/HumanoidModel.java
@@ -39,6 +39,7 @@ public class HumanoidModel extends NPedModel {
@Override
protected void applyTransform(Mat4 mat, NPedModel model) {
+ super.applyTransform(mat, model);
float phase = model.getWalkingFrequency() * model.getWalkingParameter() + animationOffset;
float value = sin(phase);
float amplitude = getSwingAmplitude((HumanoidModel) model) * model.getVelocityParameter();
diff --git a/src/main/java/ru/windcorp/progressia/client/world/entity/NPedModel.java b/src/main/java/ru/windcorp/progressia/client/world/entity/NPedModel.java
index 33bef76..12a078e 100644
--- a/src/main/java/ru/windcorp/progressia/client/world/entity/NPedModel.java
+++ b/src/main/java/ru/windcorp/progressia/client/world/entity/NPedModel.java
@@ -15,14 +15,11 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package ru.windcorp.progressia.client.world.entity;
-import static java.lang.Math.atan2;
-import static java.lang.Math.min;
import static java.lang.Math.pow;
import static java.lang.Math.toRadians;
-import static ru.windcorp.progressia.common.util.FloatMathUtil.normalizeAngle;
import glm.Glm;
import glm.mat._4.Mat4;
@@ -32,6 +29,9 @@ import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.common.Units;
+import ru.windcorp.progressia.common.util.FloatMathUtil;
+import ru.windcorp.progressia.common.util.VectorUtil;
+import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.entity.EntityData;
public abstract class NPedModel extends EntityRenderable {
@@ -47,30 +47,34 @@ public abstract class NPedModel extends EntityRenderable {
}
}
- protected void render(ShapeRenderHelper renderer, NPedModel model) {
- renderer.pushTransform().translate(translation);
+ protected void render(
+ ShapeRenderHelper renderer,
+ NPedModel model
+ ) {
applyTransform(renderer.pushTransform(), model);
renderable.render(renderer);
renderer.popTransform();
- renderer.popTransform();
}
- protected abstract void applyTransform(Mat4 mat, NPedModel model);
+ protected void applyTransform(Mat4 mat, NPedModel model) {
+ mat.translate(getTranslation());
+ }
public Vec3 getTranslation() {
return translation;
}
+
+ public Mat4 getTransform(Mat4 output, NPedModel model) {
+ if (output == null) output = new Mat4().identity();
+ applyTransform(output, model);
+ return output;
+ }
}
public static class Body extends BodyPart {
public Body(Renderable renderable) {
super(renderable, null);
}
-
- @Override
- protected void applyTransform(Mat4 mat, NPedModel model) {
- // Do nothing
- }
}
public static class Head extends BodyPart {
@@ -79,7 +83,13 @@ public abstract class NPedModel extends EntityRenderable {
private final Vec3 viewPoint;
- public Head(Renderable renderable, Vec3 joint, double maxYawDegrees, double maxPitchDegrees, Vec3 viewPoint) {
+ public Head(
+ Renderable renderable,
+ Vec3 joint,
+ double maxYawDegrees,
+ double maxPitchDegrees,
+ Vec3 viewPoint
+ ) {
super(renderable, joint);
this.maxYaw = (float) toRadians(maxYawDegrees);
this.maxPitch = (float) toRadians(maxPitchDegrees);
@@ -88,7 +98,8 @@ public abstract class NPedModel extends EntityRenderable {
@Override
protected void applyTransform(Mat4 mat, NPedModel model) {
- mat.rotateZ(model.getHeadYaw()).rotateY(model.getHeadPitch());
+ super.applyTransform(mat, model);
+ mat.rotateZ(-model.getHeadYaw()).rotateY(-model.getHeadPitch());
}
public Vec3 getViewPoint() {
@@ -96,6 +107,8 @@ public abstract class NPedModel extends EntityRenderable {
}
}
+ public static boolean flag;
+
protected final Body body;
protected final Head head;
@@ -119,7 +132,9 @@ public abstract class NPedModel extends EntityRenderable {
private float walkingFrequency;
- private float bodyYaw = Float.NaN;
+ private final Vec3 bodyLookingAt = new Vec3().set(0);
+ private final Mat4 bodyTransform = new Mat4();
+
private float headYaw;
private float headPitch;
@@ -129,63 +144,121 @@ public abstract class NPedModel extends EntityRenderable {
this.head = head;
this.scale = scale;
- evaluateAngles();
+ computeRotations();
}
@Override
- public void render(ShapeRenderHelper renderer) {
- renderer.pushTransform().scale(scale).rotateZ(bodyYaw);
+ protected void doRender(ShapeRenderHelper renderer) {
+ renderer.pushTransform().scale(scale).mul(bodyTransform);
renderBodyParts(renderer);
renderer.popTransform();
-
- accountForVelocity();
- evaluateAngles();
}
protected void renderBodyParts(ShapeRenderHelper renderer) {
body.render(renderer, this);
head.render(renderer, this);
}
-
- private void evaluateAngles() {
- float globalYaw = normalizeAngle(getData().getYaw());
-
- if (Float.isNaN(bodyYaw)) {
- bodyYaw = globalYaw;
- headYaw = 0;
- } else {
- headYaw = normalizeAngle(globalYaw - bodyYaw);
-
- if (headYaw > +head.maxYaw) {
- bodyYaw += headYaw - +head.maxYaw;
- headYaw = +head.maxYaw;
- } else if (headYaw < -head.maxYaw) {
- bodyYaw += headYaw - -head.maxYaw;
- headYaw = -head.maxYaw;
- }
- }
-
- bodyYaw = normalizeAngle(bodyYaw);
-
- headPitch = Glm.clamp(getData().getPitch(), -head.maxPitch, head.maxPitch);
+
+ @Override
+ protected void update() {
+ advanceTime();
+ computeRotations();
}
- private void accountForVelocity() {
- Vec3 horizontal = new Vec3(getData().getVelocity());
- horizontal.z = 0;
+ private void computeRotations() {
+ if (!bodyLookingAt.any()) {
+ getData().getForwardVector(bodyLookingAt);
+ headYaw = 0;
+ } else {
+ ensureBodyLookingAtIsPerpendicularToUpVector();
+ computeDesiredHeadYaw();
+ clampHeadYawAndChangeBodyLookingAt();
+ }
+
+ recomputeBodyTransform();
+
+ setHeadPitch();
+ }
+
+ private void ensureBodyLookingAtIsPerpendicularToUpVector() {
+ Vec3 up = getData().getUpVector();
+ if (up.dot(bodyLookingAt) > 1 - 1e-4) return;
+
+ Vec3 tmp = Vectors.grab3();
+
+ tmp.set(up).mul(-up.dot(bodyLookingAt)).add(bodyLookingAt);
+
+ float tmpLength = tmp.length();
+ if (tmpLength > 1e-4) {
+ bodyLookingAt.set(tmp).div(tmpLength);
+ } else {
+ // bodyLookingAt is suddenly parallel to up vector -- PANIC! ENTERING RESCUE MODE!
+ getData().getForwardVector(bodyLookingAt);
+ }
+
+ Vectors.release(tmp);
+ }
+
+ private void computeDesiredHeadYaw() {
+ Vec3 newDirection = getData().getForwardVector(null);
+ Vec3 oldDirection = bodyLookingAt;
+ Vec3 up = getData().getUpVector();
+
+ headYaw = (float) VectorUtil.getAngle(oldDirection, newDirection, up);
+ }
+
+ private void clampHeadYawAndChangeBodyLookingAt() {
+ float bodyYawChange = 0;
+
+ if (headYaw > +head.maxYaw) {
+ bodyYawChange = headYaw - +head.maxYaw;
+ headYaw = +head.maxYaw;
+ } else if (headYaw < -head.maxYaw) {
+ bodyYawChange = headYaw - -head.maxYaw;
+ headYaw = -head.maxYaw;
+ }
+
+ if (bodyYawChange != 0) {
+ VectorUtil.rotate(bodyLookingAt, getData().getUpVector(), bodyYawChange);
+ }
+ }
+
+ private void recomputeBodyTransform() {
+ Vec3 u = getData().getUpVector();
+ Vec3 f = bodyLookingAt;
+ Vec3 s = Vectors.grab3();
+
+ s.set(u).cross(f);
+
+ bodyTransform.identity().set(
+ +f.x, +f.y, +f.z, 0,
+ -s.x, -s.y, -s.z, 0,
+ +u.x, +u.y, +u.z, 0,
+ 0, 0, 0, 1
+ );
+
+ Vectors.release(s);
+ }
+
+ private void setHeadPitch() {
+ headPitch = Glm.clamp((float) getData().getPitch(), -head.maxPitch, +head.maxPitch);
+ }
+
+ private void advanceTime() {
+ Vec3 horizontal = getData().getUpVector()
+ .mul_(-getData().getUpVector().dot(getData().getVelocity()))
+ .add(getData().getVelocity());
velocity = horizontal.length();
- evaluateVelocityCoeff();
+ computeVelocityParameter();
- // TODO switch to world time
walkingParameter += velocity * GraphicsInterface.getFrameLength() * 1000;
-
- bodyYaw += velocityParameter * normalizeAngle((float) (atan2(horizontal.y, horizontal.x) - bodyYaw))
- * min(1, GraphicsInterface.getFrameLength() * 10);
+
+ rotateBodyWithMovement(horizontal);
}
- private void evaluateVelocityCoeff() {
+ private void computeVelocityParameter() {
if (velocity > maxEffectiveVelocity) {
velocityParameter = 1;
} else {
@@ -193,12 +266,39 @@ public abstract class NPedModel extends EntityRenderable {
}
}
+ private void rotateBodyWithMovement(Vec3 target) {
+ if (velocityParameter == 0 || !target.any() || Glm.equals(target, bodyLookingAt)) {
+ return;
+ }
+
+ Vec3 axis = getData().getUpVector();
+
+ float yawDifference = FloatMathUtil.normalizeAngle(
+ (float) VectorUtil.getAngle(
+ bodyLookingAt,
+ target.normalize_(),
+ axis
+ )
+ );
+
+ float bodyYawChange =
+ velocityParameter *
+ yawDifference *
+ (float) Math.expm1(GraphicsInterface.getFrameLength() * 10);
+
+ VectorUtil.rotate(bodyLookingAt, axis, bodyYawChange);
+ }
+
@Override
- public void getViewPoint(Vec3 output) {
+ protected void doGetViewPoint(Vec3 output) {
Mat4 m = new Mat4();
Vec4 v = new Vec4();
- m.identity().scale(scale).rotateZ(bodyYaw).translate(head.getTranslation()).rotateZ(headYaw).rotateY(headPitch);
+ m.identity()
+ .scale(scale)
+ .mul(bodyTransform);
+
+ head.getTransform(m, this);
v.set(head.getViewPoint(), 1);
m.mul(v);
@@ -213,9 +313,9 @@ public abstract class NPedModel extends EntityRenderable {
public Head getHead() {
return head;
}
-
- public float getBodyYaw() {
- return bodyYaw;
+
+ public Vec3 getBodyLookingAt() {
+ return bodyLookingAt;
}
public float getHeadYaw() {
@@ -228,8 +328,9 @@ public abstract class NPedModel extends EntityRenderable {
/**
* Returns a number in the range [0; 1] that can be used to scale animation
- * effects that depend on speed. This parameter is 0 when the entity is not
- * moving and 1 when it's moving "fast".
+ * effects that depend on speed.
+ * This parameter is 0 when the entity is not moving and 1 when it's moving
+ * "fast".
*
* @return velocity parameter
*/
@@ -239,8 +340,9 @@ public abstract class NPedModel extends EntityRenderable {
/**
* Returns a number that can be used to parameterize animation effects that
- * depend on walking. This parameter increases when the entity moves (e.g.
- * this can be total traveled distance).
+ * depend on walking.
+ * This parameter increases when the entity moves (e.g. this can be total
+ * traveled distance).
*
* @return walking parameter
*/
diff --git a/src/main/java/ru/windcorp/progressia/client/world/entity/QuadripedModel.java b/src/main/java/ru/windcorp/progressia/client/world/entity/QuadripedModel.java
index d31579f..5cb335c 100644
--- a/src/main/java/ru/windcorp/progressia/client/world/entity/QuadripedModel.java
+++ b/src/main/java/ru/windcorp/progressia/client/world/entity/QuadripedModel.java
@@ -40,6 +40,7 @@ public class QuadripedModel extends NPedModel {
@Override
protected void applyTransform(Mat4 mat, NPedModel model) {
+ super.applyTransform(mat, model);
float phase = model.getWalkingFrequency() * model.getWalkingParameter() + animationOffset;
float value = sin(phase);
float amplitude = ((QuadripedModel) model).getWalkingSwing() * model.getVelocityParameter();
diff --git a/src/main/java/ru/windcorp/progressia/client/world/tile/TileRender.java b/src/main/java/ru/windcorp/progressia/client/world/tile/TileRender.java
index 7cc3615..dcb6c54 100644
--- a/src/main/java/ru/windcorp/progressia/client/world/tile/TileRender.java
+++ b/src/main/java/ru/windcorp/progressia/client/world/tile/TileRender.java
@@ -15,29 +15,24 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package ru.windcorp.progressia.client.world.tile;
-import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizer;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
-import ru.windcorp.progressia.common.world.ChunkData;
-import ru.windcorp.progressia.common.world.block.BlockFace;
-import ru.windcorp.progressia.common.world.generic.GenericTile;
+import ru.windcorp.progressia.common.world.DefaultChunkData;
+import ru.windcorp.progressia.common.world.generic.TileGeneric;
+import ru.windcorp.progressia.common.world.rels.RelFace;
-public class TileRender extends Namespaced implements GenericTile {
+public class TileRender extends Namespaced implements TileGeneric {
public TileRender(String id) {
super(id);
}
- public void render(ShapeRenderHelper renderer, BlockFace face) {
- throw new UnsupportedOperationException("TileRender.render() not implemented in " + this);
- }
-
- public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk, BlockFace face) {
+ public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk, RelFace face) {
return null;
}
diff --git a/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderNone.java b/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderNone.java
index 56ea3cb..2e37033 100644
--- a/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderNone.java
+++ b/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderNone.java
@@ -20,8 +20,8 @@ package ru.windcorp.progressia.client.world.tile;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.model.EmptyModel;
import ru.windcorp.progressia.client.graphics.model.Renderable;
-import ru.windcorp.progressia.common.world.ChunkData;
-import ru.windcorp.progressia.common.world.block.BlockFace;
+import ru.windcorp.progressia.common.world.DefaultChunkData;
+import ru.windcorp.progressia.common.world.rels.RelFace;
public class TileRenderNone extends TileRender {
@@ -30,7 +30,7 @@ public class TileRenderNone extends TileRender {
}
@Override
- public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk, BlockFace face) {
+ public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk, RelFace face) {
return EmptyModel.getInstance();
}
diff --git a/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderOpaqueSurface.java b/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderOpaqueSurface.java
index bcc9df5..068bb1e 100644
--- a/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderOpaqueSurface.java
+++ b/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderOpaqueSurface.java
@@ -19,7 +19,7 @@
package ru.windcorp.progressia.client.world.tile;
import ru.windcorp.progressia.client.graphics.texture.Texture;
-import ru.windcorp.progressia.common.world.block.BlockFace;
+import ru.windcorp.progressia.common.world.rels.RelFace;
public class TileRenderOpaqueSurface extends TileRenderSurface {
@@ -28,7 +28,7 @@ public class TileRenderOpaqueSurface extends TileRenderSurface {
}
@Override
- public boolean isOpaque(BlockFace face) {
+ public boolean isOpaque(RelFace face) {
return true;
}
diff --git a/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderReference.java b/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderReference.java
new file mode 100644
index 0000000..9ec5194
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderReference.java
@@ -0,0 +1,27 @@
+/*
+ * 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.client.world.tile;
+
+import ru.windcorp.progressia.client.world.ChunkRender;
+import ru.windcorp.progressia.client.world.block.BlockRender;
+import ru.windcorp.progressia.common.world.generic.TileGenericReferenceRO;
+
+public interface TileRenderReference
+ extends TileGenericReferenceRO {
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderStack.java b/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderStack.java
index 8c3d00b..b0c6b65 100644
--- a/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderStack.java
+++ b/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderStack.java
@@ -15,14 +15,18 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package ru.windcorp.progressia.client.world.tile;
-import ru.windcorp.progressia.client.world.ChunkRender;
-import ru.windcorp.progressia.common.world.generic.GenericTileStack;
-import ru.windcorp.progressia.common.world.tile.TileDataStack;
+import java.util.AbstractList;
-public abstract class TileRenderStack extends GenericTileStack {
+import ru.windcorp.progressia.client.world.ChunkRender;
+import ru.windcorp.progressia.client.world.block.BlockRender;
+import ru.windcorp.progressia.common.world.TileDataStack;
+import ru.windcorp.progressia.common.world.generic.TileGenericStackRO;
+public abstract class TileRenderStack
+ extends AbstractList
+ implements TileGenericStackRO {
public abstract TileDataStack getData();
diff --git a/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderSurface.java b/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderSurface.java
index c41d811..604bf69 100644
--- a/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderSurface.java
+++ b/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderSurface.java
@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package ru.windcorp.progressia.client.world.tile;
import java.util.function.Consumer;
@@ -25,16 +25,17 @@ import glm.vec._3.i.Vec3i;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.backend.Usage;
-import ru.windcorp.progressia.client.graphics.model.Face;
-import ru.windcorp.progressia.client.graphics.model.Faces;
+import ru.windcorp.progressia.client.graphics.model.ShapePart;
+import ru.windcorp.progressia.client.graphics.model.ShapeParts;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSurface.TileOptimizedSurface;
import ru.windcorp.progressia.common.util.Vectors;
-import ru.windcorp.progressia.common.world.ChunkData;
-import ru.windcorp.progressia.common.world.block.BlockFace;
+import ru.windcorp.progressia.common.world.DefaultChunkData;
+import ru.windcorp.progressia.common.world.rels.AbsFace;
+import ru.windcorp.progressia.common.world.rels.RelFace;
public abstract class TileRenderSurface extends TileRender implements TileOptimizedSurface {
@@ -44,36 +45,53 @@ public abstract class TileRenderSurface extends TileRender implements TileOptimi
super(id);
this.texture = texture;
}
-
+
public TileRenderSurface(String id) {
this(id, null);
}
- public Texture getTexture(BlockFace blockFace) {
+ public Texture getTexture(RelFace blockFace) {
return texture;
}
-
- public Vec4 getColorMultiplier(BlockFace blockFace) {
+
+ public Vec4 getColorMultiplier(RelFace blockFace) {
return Colors.WHITE;
}
-
+
@Override
- public final void getFaces(ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace, boolean inner,
- Consumer output, Vec3 offset) {
- output.accept(createFace(chunk, blockInChunk, blockFace, inner, offset));
+ public final void getShapeParts(
+ DefaultChunkData chunk, Vec3i relBlockInChunk, RelFace blockFace,
+ boolean inner,
+ Consumer output,
+ Vec3 offset
+ ) {
+ output.accept(createFace(chunk, relBlockInChunk, blockFace, inner, offset));
}
-
- private Face createFace(ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace, boolean inner, Vec3 offset) {
- return Faces.createBlockFace(WorldRenderProgram.getDefault(), getTexture(blockFace),
- getColorMultiplier(blockFace), offset, blockFace, inner);
+
+ private ShapePart createFace(
+ DefaultChunkData chunk, Vec3i blockInChunk, RelFace blockFace,
+ boolean inner,
+ Vec3 offset
+ ) {
+ return ShapeParts.createBlockFace(
+ WorldRenderProgram.getDefault(),
+ getTexture(blockFace),
+ getColorMultiplier(blockFace),
+ offset,
+ blockFace.resolve(AbsFace.POS_Z),
+ inner
+ );
}
@Override
- public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace) {
- return new Shape(Usage.STATIC, WorldRenderProgram.getDefault(),
-
- createFace(chunk, blockInChunk, blockFace, false, Vectors.ZERO_3),
- createFace(chunk, blockInChunk, blockFace, true, Vectors.ZERO_3));
+ public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk, RelFace blockFace) {
+ return new Shape(
+ Usage.STATIC,
+ WorldRenderProgram.getDefault(),
+
+ createFace(chunk, blockInChunk, blockFace, false, Vectors.ZERO_3),
+ createFace(chunk, blockInChunk, blockFace, true, Vectors.ZERO_3)
+ );
}
@Override
diff --git a/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderTransparentSurface.java b/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderTransparentSurface.java
index af512a9..d311231 100644
--- a/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderTransparentSurface.java
+++ b/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderTransparentSurface.java
@@ -19,7 +19,7 @@
package ru.windcorp.progressia.client.world.tile;
import ru.windcorp.progressia.client.graphics.texture.Texture;
-import ru.windcorp.progressia.common.world.block.BlockFace;
+import ru.windcorp.progressia.common.world.rels.RelFace;
public class TileRenderTransparentSurface extends TileRenderSurface {
@@ -28,7 +28,7 @@ public class TileRenderTransparentSurface extends TileRenderSurface {
}
@Override
- public boolean isOpaque(BlockFace face) {
+ public boolean isOpaque(RelFace face) {
return false;
}
diff --git a/src/main/java/ru/windcorp/progressia/common/collision/AABBRotator.java b/src/main/java/ru/windcorp/progressia/common/collision/AABBRotator.java
new file mode 100644
index 0000000..cbacb29
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/collision/AABBRotator.java
@@ -0,0 +1,134 @@
+/*
+ * 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.collision;
+
+import java.util.function.Supplier;
+
+import com.google.common.collect.ImmutableList;
+
+import glm.vec._3.Vec3;
+import ru.windcorp.progressia.common.util.Vectors;
+import ru.windcorp.progressia.common.world.rels.AbsFace;
+import ru.windcorp.progressia.common.world.rels.AxisRotations;
+
+public class AABBRotator implements AABBoid {
+
+ private class AABBRotatorWall implements Wall {
+
+ private final int id;
+
+ public AABBRotatorWall(int id) {
+ this.id = id;
+ }
+
+ @Override
+ public void getOrigin(Vec3 output) {
+ parent.getWall(id).getOrigin(output);
+ AxisRotations.resolve(output, upSupplier.get(), output);
+ }
+
+ @Override
+ public void getWidth(Vec3 output) {
+ parent.getWall(id).getWidth(output);
+ AxisRotations.resolve(output, upSupplier.get(), output);
+ }
+
+ @Override
+ public void getHeight(Vec3 output) {
+ parent.getWall(id).getHeight(output);
+ AxisRotations.resolve(output, upSupplier.get(), output);
+ }
+
+ }
+
+ private final Supplier upSupplier;
+ private final Supplier hingeSupplier;
+ private final AABBoid parent;
+
+ private final AABBRotatorWall[] walls = new AABBRotatorWall[AbsFace.BLOCK_FACE_COUNT];
+
+ {
+ for (int id = 0; id < walls.length; ++id) {
+ walls[id] = new AABBRotatorWall(id);
+ }
+ }
+
+ public AABBRotator(Supplier upSupplier, Supplier hingeSupplier, AABBoid parent) {
+ this.upSupplier = upSupplier;
+ this.hingeSupplier = hingeSupplier;
+ this.parent = parent;
+ }
+
+ @Override
+ public void setOrigin(Vec3 origin) {
+ Vec3 relativeOrigin = Vectors.grab3();
+ Vec3 hinge = hingeSupplier.get();
+
+ origin.sub(hinge, relativeOrigin);
+ AxisRotations.relativize(relativeOrigin, upSupplier.get(), relativeOrigin);
+ relativeOrigin.add(hinge);
+
+ parent.setOrigin(relativeOrigin);
+
+ Vectors.release(relativeOrigin);
+ }
+
+ @Override
+ public void moveOrigin(Vec3 displacement) {
+ parent.moveOrigin(displacement);
+ }
+
+ @Override
+ public void getOrigin(Vec3 output) {
+ parent.getOrigin(output);
+ Vec3 hinge = hingeSupplier.get();
+
+ output.sub(hinge);
+ AxisRotations.resolve(output, upSupplier.get(), output);
+ output.add(hinge);
+ }
+
+ @Override
+ public void getSize(Vec3 output) {
+ parent.getSize(output);
+ AxisRotations.resolve(output, upSupplier.get(), output);
+ output.abs();
+ }
+
+ @Override
+ public Wall getWall(int faceId) {
+ return walls[faceId];
+ }
+
+ public static CollisionModel rotate(Supplier upSupplier, Supplier hingeSupplier, CollisionModel parent) {
+ if (parent instanceof AABBoid) {
+ return new AABBRotator(upSupplier, hingeSupplier, (AABBoid) parent);
+ } else if (parent instanceof CompoundCollisionModel) {
+ ImmutableList.Builder models = ImmutableList.builder();
+
+ for (CollisionModel original : ((CompoundCollisionModel) parent).getModels()) {
+ models.add(rotate(upSupplier, hingeSupplier, original));
+ }
+
+ return new CompoundCollisionModel(models.build());
+ } else {
+ throw new RuntimeException("not supported");
+ }
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/collision/AABBoid.java b/src/main/java/ru/windcorp/progressia/common/collision/AABBoid.java
index 04312f1..3a36b8b 100644
--- a/src/main/java/ru/windcorp/progressia/common/collision/AABBoid.java
+++ b/src/main/java/ru/windcorp/progressia/common/collision/AABBoid.java
@@ -19,7 +19,7 @@
package ru.windcorp.progressia.common.collision;
import glm.vec._3.Vec3;
-import ru.windcorp.progressia.common.world.block.BlockFace;
+import ru.windcorp.progressia.common.world.rels.AbsFace;
public interface AABBoid extends CollisionModel {
@@ -27,7 +27,7 @@ public interface AABBoid extends CollisionModel {
void getSize(Vec3 output);
- default Wall getWall(BlockFace face) {
+ default Wall getWall(AbsFace face) {
return getWall(face.getId());
}
diff --git a/src/main/java/ru/windcorp/progressia/common/collision/TranslatedAABB.java b/src/main/java/ru/windcorp/progressia/common/collision/TranslatedAABB.java
index fd94cfc..95fe1d3 100644
--- a/src/main/java/ru/windcorp/progressia/common/collision/TranslatedAABB.java
+++ b/src/main/java/ru/windcorp/progressia/common/collision/TranslatedAABB.java
@@ -20,7 +20,7 @@ package ru.windcorp.progressia.common.collision;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.common.util.Vectors;
-import ru.windcorp.progressia.common.world.block.BlockFace;
+import ru.windcorp.progressia.common.world.rels.AbsFace;
public class TranslatedAABB implements AABBoid {
@@ -51,7 +51,7 @@ public class TranslatedAABB implements AABBoid {
private AABBoid parent;
private final Vec3 translation = new Vec3();
- private final TranslatedAABBWall[] walls = new TranslatedAABBWall[BlockFace.BLOCK_FACE_COUNT];
+ private final TranslatedAABBWall[] walls = new TranslatedAABBWall[AbsFace.BLOCK_FACE_COUNT];
{
for (int id = 0; id < walls.length; ++id) {
diff --git a/src/main/java/ru/windcorp/progressia/common/collision/WorldCollisionHelper.java b/src/main/java/ru/windcorp/progressia/common/collision/WorldCollisionHelper.java
index c843edc..9dff95d 100644
--- a/src/main/java/ru/windcorp/progressia/common/collision/WorldCollisionHelper.java
+++ b/src/main/java/ru/windcorp/progressia/common/collision/WorldCollisionHelper.java
@@ -24,7 +24,7 @@ import java.util.Collection;
import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.util.LowOverheadCache;
-import ru.windcorp.progressia.common.world.WorldData;
+import ru.windcorp.progressia.common.world.DefaultWorldData;
public class WorldCollisionHelper {
@@ -79,7 +79,7 @@ public class WorldCollisionHelper {
* @param maxTime
* maximum collision time
*/
- public void tuneToCollideable(WorldData world, Collideable collideable, float maxTime) {
+ public void tuneToCollideable(DefaultWorldData world, Collideable collideable, float maxTime) {
activeBlockModels.forEach(blockModelCache::release);
activeBlockModels.clear();
CollisionPathComputer.forEveryBlockInCollisionPath(collideable, maxTime,
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 7ff3ca1..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
@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package ru.windcorp.progressia.common.collision.colliders;
import glm.mat._3.Mat3;
@@ -25,12 +25,18 @@ import ru.windcorp.progressia.common.collision.colliders.Collider.ColliderWorksp
import ru.windcorp.progressia.common.collision.colliders.Collider.Collision;
import ru.windcorp.progressia.common.util.Matrices;
import ru.windcorp.progressia.common.util.Vectors;
-import ru.windcorp.progressia.common.world.block.BlockFace;
+import ru.windcorp.progressia.common.world.rels.AbsFace;
class AABBoidCollider {
- static Collider.Collision computeModelCollision(Collideable aBody, Collideable bBody, AABBoid aModel,
- AABBoid bModel, float tickLength, ColliderWorkspace workspace) {
+ static Collider.Collision computeModelCollision(
+ Collideable aBody,
+ Collideable bBody,
+ AABBoid aModel,
+ AABBoid bModel,
+ float tickLength,
+ ColliderWorkspace workspace
+ ) {
Collideable obstacleBody = bBody;
Collideable colliderBody = aBody;
AABBoid obstacleModel = bModel;
@@ -44,11 +50,18 @@ class AABBoidCollider {
computeCollisionVelocity(collisionVelocity, obstacleBody, colliderBody);
// For every wall of collision space
- for (int i = 0; i < BlockFace.BLOCK_FACE_COUNT; ++i) {
+ for (int i = 0; i < AbsFace.BLOCK_FACE_COUNT; ++i) {
Wall wall = originCollisionSpace.getWall(i);
- Collision collision = computeWallCollision(wall, colliderModel, collisionVelocity, tickLength, workspace,
- aBody, bBody);
+ Collision collision = computeWallCollision(
+ wall,
+ colliderModel,
+ collisionVelocity,
+ tickLength,
+ workspace,
+ aBody,
+ bBody
+ );
// Update result
if (collision != null) {
@@ -73,7 +86,11 @@ class AABBoidCollider {
return result;
}
- private static void computeCollisionVelocity(Vec3 output, Collideable obstacleBody, Collideable colliderBody) {
+ private static void computeCollisionVelocity(
+ Vec3 output,
+ Collideable obstacleBody,
+ Collideable colliderBody
+ ) {
Vec3 obstacleVelocity = Vectors.grab3();
Vec3 colliderVelocity = Vectors.grab3();
@@ -105,31 +122,60 @@ 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. DETAILED EXPLANATION: 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 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). 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⎦ 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. If all conditions are satisfied, then the moment of
- * impact is t0 + t.
+ * 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 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
+ *
+ * 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).
+ *
+ * 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⎦
+ *
+ * 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.
+ * If all conditions are satisfied, then the moment of impact is t0 + t.
*/
- private static Collision computeWallCollision(Wall obstacleWall, AABBoid colliderModel, Vec3 collisionVelocity,
- float tickLength, ColliderWorkspace workspace, Collideable aBody, Collideable bBody) {
+ // @formatter:on
+ private static Collision computeWallCollision(
+ Wall obstacleWall,
+ AABBoid colliderModel,
+ Vec3 collisionVelocity,
+ float tickLength,
+ ColliderWorkspace workspace,
+ Collideable aBody,
+ Collideable bBody
+ ) {
Vec3 w = Vectors.grab3();
Vec3 h = Vectors.grab3();
Vec3 v = Vectors.grab3();
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 48e1640..9b97e40 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
@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package ru.windcorp.progressia.common.collision.colliders;
import java.util.Collection;
@@ -27,7 +27,7 @@ import glm.vec._3.Vec3;
import ru.windcorp.progressia.common.collision.*;
import ru.windcorp.progressia.common.util.LowOverheadCache;
import ru.windcorp.progressia.common.util.Vectors;
-import ru.windcorp.progressia.common.world.WorldData;
+import ru.windcorp.progressia.common.world.DefaultWorldData;
public class Collider {
@@ -36,15 +36,19 @@ public class Collider {
/**
* Dear Princess Celestia,
*
- * When {@linkplain #advanceTime(Collection, Collision, WorldData, float)
- * advancing time}, time step for all entities except currently
- * colliding bodies is the current collisions's timestamp relative to now.
- * However, currently colliding bodies (Collision.a and Collision.b) have a
- * smaller time step. This is done to make sure they don't intersect due to
- * rounding errors.
+ * When {@linkplain #advanceTime(Collection, Collision, DefaultWorldData, float)
+ * advancing time},
+ * time step for all entities except currently colliding bodies is
+ * the current
+ * collisions's timestamp relative to now. However, currently colliding
+ * bodies
+ * (Collision.a and Collision.b) have a smaller time step. This is done to
+ * make sure
+ * they don't intersect due to rounding errors.
*
* Today I learned that bad code has nothing to do with friendship, although
- * lemme tell ya: it's got some dank magic.
+ * lemme tell ya:
+ * it's got some dank magic.
*
* Your faithful student,
* Kostyl.
@@ -55,14 +59,21 @@ public class Collider {
* 1f
*/;
- public static void performCollisions(List extends Collideable> colls, WorldData world, float tickLength,
- ColliderWorkspace workspace) {
+ public static void performCollisions(
+ List extends Collideable> colls,
+ DefaultWorldData world,
+ float tickLength,
+ ColliderWorkspace workspace
+ ) {
int collisionCount = 0;
int maxCollisions = colls.size() * MAX_COLLISIONS_PER_ENTITY;
while (true) {
if (collisionCount > maxCollisions) {
- LogManager.getLogger().warn("Attempted to handle more than {} collisions", maxCollisions);
+ LogManager.getLogger().warn(
+ "Attempted to handle more than {} collisions",
+ maxCollisions
+ );
return;
}
@@ -82,8 +93,12 @@ public class Collider {
advanceTime(colls, null, world, tickLength);
}
- private static Collision getFirstCollision(List extends Collideable> colls, float tickLength, WorldData world,
- ColliderWorkspace workspace) {
+ private static Collision getFirstCollision(
+ List extends Collideable> colls,
+ float tickLength,
+ DefaultWorldData world,
+ ColliderWorkspace workspace
+ ) {
Collision result = null;
Collideable worldColl = workspace.worldCollisionHelper.getCollideable();
@@ -93,7 +108,10 @@ public class Collider {
tuneWorldCollisionHelper(a, tickLength, world, workspace);
- result = workspace.updateLatestCollision(result, getCollision(a, worldColl, tickLength, workspace));
+ result = workspace.updateLatestCollision(
+ result,
+ getCollision(a, worldColl, tickLength, workspace)
+ );
for (int j = i + 1; j < colls.size(); ++j) {
Collideable b = colls.get(j);
@@ -105,42 +123,81 @@ public class Collider {
return result;
}
- private static void tuneWorldCollisionHelper(Collideable coll, float tickLength, WorldData world,
- ColliderWorkspace workspace) {
+ private static void tuneWorldCollisionHelper(
+ Collideable coll,
+ float tickLength,
+ DefaultWorldData world,
+ ColliderWorkspace workspace
+ ) {
WorldCollisionHelper wch = workspace.worldCollisionHelper;
wch.tuneToCollideable(world, coll, tickLength);
}
- static Collision getCollision(Collideable a, Collideable b, float tickLength, ColliderWorkspace workspace) {
+ static Collision getCollision(
+ Collideable a,
+ Collideable b,
+ float tickLength,
+ ColliderWorkspace workspace
+ ) {
CollisionModel aModel = a.getCollisionModel();
CollisionModel bModel = b.getCollisionModel();
return getCollision(a, b, aModel, bModel, tickLength, workspace);
}
- static Collision getCollision(Collideable aBody, Collideable bBody, CollisionModel aModel, CollisionModel bModel,
- float tickLength, ColliderWorkspace workspace) {
+ static Collision getCollision(
+ Collideable aBody,
+ Collideable bBody,
+ CollisionModel aModel,
+ CollisionModel bModel,
+ float tickLength,
+ ColliderWorkspace workspace
+ ) {
if (aModel instanceof AABBoid && bModel instanceof AABBoid) {
- return AABBoidCollider.computeModelCollision(aBody, bBody, (AABBoid) aModel, (AABBoid) bModel, tickLength,
- workspace);
+ return AABBoidCollider.computeModelCollision(
+ aBody,
+ bBody,
+ (AABBoid) aModel,
+ (AABBoid) bModel,
+ tickLength,
+ workspace
+ );
}
if (aModel instanceof CompoundCollisionModel) {
- return AnythingWithCompoundCollider.computeModelCollision(aBody, bBody, (CompoundCollisionModel) aModel,
- bModel, tickLength, workspace);
+ return AnythingWithCompoundCollider.computeModelCollision(
+ aBody,
+ bBody,
+ (CompoundCollisionModel) aModel,
+ bModel,
+ tickLength,
+ workspace
+ );
}
if (bModel instanceof CompoundCollisionModel) {
- return AnythingWithCompoundCollider.computeModelCollision(bBody, aBody, (CompoundCollisionModel) bModel,
- aModel, tickLength, workspace);
+ return AnythingWithCompoundCollider.computeModelCollision(
+ bBody,
+ aBody,
+ (CompoundCollisionModel) bModel,
+ aModel,
+ tickLength,
+ workspace
+ );
}
throw new UnsupportedOperationException(
- "Collisions between " + aModel + " and " + bModel + " are not yet implemented");
+ "Collisions between " + aModel + " and " + bModel + " are not yet implemented"
+ );
}
- private static void collide(Collision collision,
+ private static void collide(
+ Collision collision,
- Collection extends Collideable> colls, WorldData world, float tickLength, ColliderWorkspace workspace) {
+ Collection extends Collideable> colls,
+ DefaultWorldData world,
+ float tickLength,
+ ColliderWorkspace workspace
+ ) {
advanceTime(colls, collision, world, collision.time);
boolean doNotHandle = false;
@@ -155,40 +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. Angular momentum is ignored per 3) and 4), 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 along a unit vector _ _ _ _ _ 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, 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}. 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}. Therefore: ___ _ ___ _ ___ _ 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 ), 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}.
+ * 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.
+ *
+ * Angular momentum is ignored per 3) and 4),
+ * 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
+ * along a unit vector
+ * _ _ _ _ _
+ * 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,
+ * 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}.
+ *
+ * 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}.
+ *
+ * Therefore:
+ * ___ _ ___ _ ___ _
+ * 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 ),
+ *
+ * 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}.
*/
+ // @formatter:on
private static void handlePhysics(Collision collision) {
// Fuck JGLM
Vec3 n = Vectors.grab3();
@@ -250,7 +339,12 @@ public class Collider {
Vectors.release(du_b);
}
- private static void separate(Collision collision, Vec3 normal, float aRelativeMass, float bRelativeMass) {
+ private static void separate(
+ Collision collision,
+ Vec3 normal,
+ float aRelativeMass,
+ float bRelativeMass
+ ) {
final float margin = 1e-4f;
Vec3 displacement = Vectors.grab3();
@@ -264,8 +358,12 @@ public class Collider {
Vectors.release(displacement);
}
- private static void advanceTime(Collection extends Collideable> colls, Collision exceptions, WorldData world,
- float step) {
+ private static void advanceTime(
+ Collection extends Collideable> colls,
+ Collision exceptions,
+ DefaultWorldData world,
+ float step
+ ) {
world.advanceTime(step);
Vec3 tmp = Vectors.grab3();
@@ -333,12 +431,17 @@ public class Collider {
public final Vec3 wallHeight = new Vec3();
/**
- * Time offset from the start of the tick. 0 means right now, tickLength
- * means at the end of the tick.
+ * Time offset from the start of the tick.
+ * 0 means right now, tickLength means at the end of the tick.
*/
public float time;
- public Collision set(Collideable a, Collideable b, Wall wall, float time) {
+ public Collision set(
+ Collideable a,
+ Collideable b,
+ Wall wall,
+ float time
+ ) {
this.a = a;
this.b = b;
wall.getWidth(wallWidth);
diff --git a/src/main/java/ru/windcorp/progressia/common/hacks/GuavaEventBusHijacker.java b/src/main/java/ru/windcorp/progressia/common/hacks/GuavaEventBusHijacker.java
index dada94e..8ee2c60 100644
--- a/src/main/java/ru/windcorp/progressia/common/hacks/GuavaEventBusHijacker.java
+++ b/src/main/java/ru/windcorp/progressia/common/hacks/GuavaEventBusHijacker.java
@@ -29,11 +29,14 @@ import com.google.common.util.concurrent.MoreExecutors;
import ru.windcorp.progressia.common.util.crash.CrashReports;
/**
- * This class had to be written because there is not legal way to instantiate a
+ * This class had to be written because there is no legal way to instantiate a
* non-async {@link EventBus} with both a custom identifier and a custom
* exception handler. Which is a shame. Guava maintainers know about the issue
* but have rejected solutions multiple times without a clearly stated
* reason; looks like some dirty reflection will have to do.
+ *
+ * When explicitly referencing this class, please mention its usage in
+ * implementation notes because it is unreliable long-term.
*
* @author javapony
*/
@@ -46,22 +49,30 @@ public class GuavaEventBusHijacker {
try {
Class> dispatcherClass = Class.forName("com.google.common.eventbus.Dispatcher");
- THE_CONSTRUCTOR = EventBus.class.getDeclaredConstructor(String.class, Executor.class, dispatcherClass,
- SubscriberExceptionHandler.class);
+ THE_CONSTRUCTOR = EventBus.class.getDeclaredConstructor(
+ String.class,
+ Executor.class,
+ dispatcherClass,
+ SubscriberExceptionHandler.class
+ );
THE_CONSTRUCTOR.setAccessible(true);
DISPATCHER__PER_THREAD_DISPATCH_QUEUE = dispatcherClass.getDeclaredMethod("perThreadDispatchQueue");
DISPATCHER__PER_THREAD_DISPATCH_QUEUE.setAccessible(true);
} catch (Exception e) {
- throw CrashReports.report(e,
- "Something went horribly wrong when setting up EventBus hijacking. Has Guava updated?");
+ throw CrashReports
+ .report(e, "Something went horribly wrong when setting up EventBus hijacking. Has Guava updated?");
}
}
public static EventBus newEventBus(String identifier, SubscriberExceptionHandler exceptionHandler) {
try {
- return THE_CONSTRUCTOR.newInstance(identifier, MoreExecutors.directExecutor(),
- DISPATCHER__PER_THREAD_DISPATCH_QUEUE.invoke(null), exceptionHandler);
+ return THE_CONSTRUCTOR.newInstance(
+ identifier,
+ MoreExecutors.directExecutor(),
+ DISPATCHER__PER_THREAD_DISPATCH_QUEUE.invoke(null),
+ exceptionHandler
+ );
} catch (Exception e) {
throw CrashReports.report(e, "Something went horribly wrong when hijacking EventBus. Has Guava updated?");
}
diff --git a/src/main/java/ru/windcorp/progressia/server/world/tasks/StateChange.java b/src/main/java/ru/windcorp/progressia/common/state/StateChange.java
similarity index 94%
rename from src/main/java/ru/windcorp/progressia/server/world/tasks/StateChange.java
rename to src/main/java/ru/windcorp/progressia/common/state/StateChange.java
index 4aa8c7c..38638a7 100644
--- a/src/main/java/ru/windcorp/progressia/server/world/tasks/StateChange.java
+++ b/src/main/java/ru/windcorp/progressia/common/state/StateChange.java
@@ -15,8 +15,8 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
-package ru.windcorp.progressia.server.world.tasks;
+
+package ru.windcorp.progressia.common.state;
@FunctionalInterface
public interface StateChange {
diff --git a/src/main/java/ru/windcorp/progressia/common/util/ArrayFloatRangeMap.java b/src/main/java/ru/windcorp/progressia/common/util/ArrayFloatRangeMap.java
new file mode 100644
index 0000000..4eea3f6
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/util/ArrayFloatRangeMap.java
@@ -0,0 +1,225 @@
+/*
+ * 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.util;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Objects;
+
+public class ArrayFloatRangeMap implements FloatRangeMap {
+
+ protected static class Node implements Comparable> {
+ public float pos;
+ public E value;
+
+ public Node(float pos, E value) {
+ this.pos = pos;
+ this.value = value;
+ }
+
+ @Override
+ public int compareTo(Node o) {
+ return Float.compare(pos, o.pos);
+ }
+ }
+
+ /*
+ * Expects a random-access list
+ */
+ protected final List> nodes;
+ protected int ranges = 0;
+
+ protected static final int DEFAULT_CAPACITY = 16;
+
+ public ArrayFloatRangeMap(int capacity) {
+ this.nodes = new ArrayList<>(2 * capacity);
+ }
+
+ public ArrayFloatRangeMap() {
+ this(DEFAULT_CAPACITY);
+ }
+
+ @Override
+ public int size() {
+ return this.ranges;
+ }
+
+ @Override
+ public Iterator iterator() {
+ return new Iterator() {
+
+ private int nextIndex = 0;
+
+ {
+ assert nodes.isEmpty() || nodes.get(nextIndex).value != null;
+ }
+
+ private void findNext() {
+ while (nextIndex < nodes.size()) {
+ nextIndex++;
+ Node node = nodes.get(nextIndex);
+ if (node.value != null) return;
+ }
+ }
+
+ @Override
+ public boolean hasNext() {
+ return nextIndex < nodes.size();
+ }
+
+ @Override
+ public E next() {
+ E result = nodes.get(nextIndex).value;
+ findNext();
+ return result;
+ }
+ };
+ }
+
+ /**
+ * Returns an index of the smallest {@link Node} larger than or exactly at
+ * {@code position}.
+ *
+ * @param position the position to look up
+ * @return an index in the {@link #nodes} list containing the first
+ * {@link Node} whose {@link Node#pos} is not smaller than
+ * {@code position}, or {@code nodes.size()} if no such index exists
+ */
+ protected int findCeiling(float position) {
+
+ /*
+ * Implementation based on OpenJDK's
+ * Collections.indexedBinarySearch(List, Comparator)
+ */
+
+ int low = 0;
+ int high = nodes.size() - 1;
+
+ while (low <= high) {
+ int mid = (low + high) >>> 1;
+ float midVal = nodes.get(mid).pos;
+ int cmp = Float.compare(midVal, position);
+
+ if (cmp < 0)
+ low = mid + 1;
+ else if (cmp > 0)
+ high = mid - 1;
+ else
+ return mid; // key found
+ }
+
+ return low; // the insertion point is the desired index
+ }
+
+ /**
+ * Returns an index of the largest {@link Node} smaller than or exactly at
+ * {@code position}.
+ *
+ * @param position the position to look up
+ * @return an index in the {@link #nodes} list containing the last
+ * {@link Node} whose {@link Node#pos} is not greater than
+ * {@code position}, or {@code -1} if no such index exists
+ */
+ protected int findFloor(float position) {
+
+ /*
+ * Implementation based on OpenJDK's
+ * Collections.indexedBinarySearch(List, Comparator)
+ */
+
+ int low = 0;
+ int high = nodes.size() - 1;
+
+ while (low <= high) {
+ int mid = (low + high) >>> 1;
+ float midVal = nodes.get(mid).pos;
+ int cmp = Float.compare(midVal, position);
+
+ if (cmp < 0)
+ low = mid + 1;
+ else if (cmp > 0)
+ high = mid - 1;
+ else
+ return mid; // key found
+ }
+
+ return low - 1; // the insertion point immediately follows the desired index
+ }
+
+ protected Node getEffectiveNode(float at) {
+ int effectiveNodeIndex = findFloor(at);
+ if (effectiveNodeIndex < 0) return null;
+ return nodes.get(effectiveNodeIndex);
+ }
+
+ @Override
+ public E get(float at) {
+ Node effectiveNode = getEffectiveNode(at);
+ return effectiveNode == null ? null : effectiveNode.value;
+ }
+
+ @Override
+ public void put(float min, float max, E element) {
+ Objects.requireNonNull(element, "element");
+
+ if (!(max > min)) // This funky construction also deals with NaNs since NaNs always fail any comparison
+ {
+ throw new IllegalArgumentException(max + " is not greater than " + min);
+ }
+
+ int indexOfInsertionOfMin = findCeiling(min);
+
+ nodes.add(indexOfInsertionOfMin, new Node(min, element));
+ ranges++;
+
+ ListIterator> it = nodes.listIterator(indexOfInsertionOfMin + 1);
+ E elementEffectiveImmediatelyAfterInsertedRange = null;
+
+ if (indexOfInsertionOfMin > 0) {
+ elementEffectiveImmediatelyAfterInsertedRange = nodes.get(indexOfInsertionOfMin - 1).value;
+ }
+
+ while (it.hasNext()) {
+ Node node = it.next();
+
+ if (node.pos >= max) {
+ break;
+ }
+
+ elementEffectiveImmediatelyAfterInsertedRange = node.value;
+ if (elementEffectiveImmediatelyAfterInsertedRange != null) {
+ // Removing an actual range
+ ranges--;
+ }
+ it.remove();
+ }
+
+ if (max != Float.POSITIVE_INFINITY) {
+ nodes.add(indexOfInsertionOfMin + 1, new Node(max, elementEffectiveImmediatelyAfterInsertedRange));
+
+ if (elementEffectiveImmediatelyAfterInsertedRange != null) {
+ // We might have added one right back
+ ranges++;
+ }
+ }
+
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/util/FloatRangeMap.java b/src/main/java/ru/windcorp/progressia/common/util/FloatRangeMap.java
new file mode 100644
index 0000000..303d6a8
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/util/FloatRangeMap.java
@@ -0,0 +1,36 @@
+/*
+ * 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.util;
+
+public interface FloatRangeMap extends Iterable {
+
+ void put(float min, float max, E element);
+ E get(float at);
+
+ int size();
+
+ default boolean defines(float position) {
+ return get(position) != null;
+ }
+
+ default E getOrDefault(float at, E def) {
+ E result = get(at);
+ return result == null ? def : result;
+ }
+
+}
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 18cc6be..f25368f 100644
--- a/src/main/java/ru/windcorp/progressia/common/util/VectorUtil.java
+++ b/src/main/java/ru/windcorp/progressia/common/util/VectorUtil.java
@@ -15,11 +15,13 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package ru.windcorp.progressia.common.util;
import java.util.function.Consumer;
+import glm.Glm;
+import glm.mat._3.Mat3;
import glm.mat._4.Mat4;
import glm.vec._2.Vec2;
import glm.vec._2.d.Vec2d;
@@ -36,8 +38,50 @@ 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, int y0, int z0, int x1, int y1, int z1, Consumer super Vec3i> action) {
+ public static void iterateCuboid(
+ int x0,
+ int y0,
+ int z0,
+ int x1,
+ int y1,
+ int z1,
+ Consumer super Vec3i> action
+ ) {
Vec3i cursor = Vectors.grab3i();
for (int x = x0; x < x1; ++x) {
@@ -52,12 +96,23 @@ public class VectorUtil {
Vectors.release(cursor);
}
- public static void iterateCuboid(Vec3i vMin, Vec3i vMax, Consumer super Vec3i> action) {
+ public static void iterateCuboid(
+ Vec3i vMin,
+ Vec3i vMax,
+ Consumer super Vec3i> action
+ ) {
iterateCuboid(vMin.x, vMin.y, vMin.z, vMax.x, vMax.y, vMax.z, action);
}
- public static void iterateCuboidAround(int cx, int cy, int cz, int dx, int dy, int dz,
- Consumer super Vec3i> action) {
+ public static void iterateCuboidAround(
+ int cx,
+ int cy,
+ int cz,
+ int dx,
+ int dy,
+ int dz,
+ Consumer super Vec3i> action
+ ) {
if (dx < 0)
throw new IllegalArgumentException("dx " + dx + " is negative");
if (dy < 0)
@@ -79,19 +134,37 @@ public class VectorUtil {
iterateCuboid(cx - dx, cy - dy, cz - dz, cx + dx + 1, cy + dy + 1, cz + dz + 1, action);
}
- public static void iterateCuboidAround(Vec3i center, Vec3i diameters, Consumer super Vec3i> action) {
+ public static void iterateCuboidAround(
+ Vec3i center,
+ Vec3i diameters,
+ Consumer super Vec3i> action
+ ) {
iterateCuboidAround(center.x, center.y, center.z, diameters.x, diameters.y, diameters.z, action);
}
- public static void iterateCuboidAround(int cx, int cy, int cz, int diameter, Consumer super Vec3i> action) {
+ public static void iterateCuboidAround(
+ int cx,
+ int cy,
+ int cz,
+ int diameter,
+ Consumer super Vec3i> action
+ ) {
iterateCuboidAround(cx, cy, cz, diameter, diameter, diameter, action);
}
- public static void iterateCuboidAround(Vec3i center, int diameter, Consumer super Vec3i> action) {
+ public static void iterateCuboidAround(
+ Vec3i center,
+ int diameter,
+ Consumer super Vec3i> action
+ ) {
iterateCuboidAround(center.x, center.y, center.z, diameter, action);
}
- public static void applyMat4(Vec3 in, Mat4 mat, Vec3 out) {
+ public static Vec3 applyMat4(Vec3 in, Mat4 mat, Vec3 out) {
+ if (out == null) {
+ out = new Vec3();
+ }
+
Vec4 vec4 = Vectors.grab4();
vec4.set(in, 1f);
@@ -99,27 +172,206 @@ public class VectorUtil {
out.set(vec4.x, vec4.y, vec4.z);
Vectors.release(vec4);
+
+ return out;
}
- public static void applyMat4(Vec3 inOut, Mat4 mat) {
- Vec4 vec4 = Vectors.grab4();
- vec4.set(inOut, 1f);
-
- mat.mul(vec4);
-
- inOut.set(vec4.x, vec4.y, vec4.z);
+ public static Vec3 applyMat4(Vec3 inOut, Mat4 mat) {
+ return applyMat4(inOut, mat, inOut);
+ }
+
+ public static Vec3 rotate(Vec3 in, Vec3 axis, float angle, Vec3 out) {
+ if (out == null) {
+ out = new Vec3();
+ }
+
+ Mat3 mat = Matrices.grab3();
+
+ mat.identity().rotate(angle, axis);
+ mat.mul(in, out);
+
+ Matrices.release(mat);
+
+ return out;
+ }
+
+ public static Vec3 rotate(Vec3 inOut, Vec3 axis, float angle) {
+ return rotate(inOut, axis, angle, inOut);
+ }
+
+ public static double getAngle(Vec3 from, Vec3 to, Vec3 normal) {
+ Vec3 left = Vectors.grab3();
+
+ left.set(normal).cross(from);
+ double sign = Math.signum(left.dot(to));
+
+ double result = (float) Math.acos(Glm.clamp(from.dot(to), -1, +1)) * sign;
+
+ Vectors.release(left);
+ return result;
+ }
+
+ public static Vec3 projectOnSurface(Vec3 in, Vec3 normal, Vec3 out) {
+ if (in == out) {
+ return projectOnSurface(in, normal);
+ }
+
+ if (out == null) {
+ out = new Vec3();
+ }
+
+ out.set(normal).mul(-normal.dot(in)).add(in);
+
+ return out;
+ }
+
+ public static Vec3 projectOnSurface(Vec3 inOut, Vec3 normal) {
+ Vec3 buffer = Vectors.grab3();
+
+ projectOnSurface(inOut, normal, buffer);
+ inOut.set(buffer);
+
+ Vectors.release(buffer);
+
+ return inOut;
+ }
+
+ public static Vec3 projectOnVector(Vec3 in, Vec3 vector, Vec3 out) {
+ if (out == null) {
+ out = new Vec3();
+ }
+
+ float dot = vector.dot(in);
+ out.set(vector).mul(dot);
+
+ return out;
+ }
+
+ public static Vec3 projectOnVector(Vec3 inOut, Vec3 vector) {
+ return projectOnVector(inOut, vector);
}
- public static Vec3 linearCombination(Vec3 va, float ka, Vec3 vb, float kb, Vec3 output) {
- output.set(va.x * ka + vb.x * kb, va.y * ka + vb.y * kb, va.z * ka + vb.z * kb);
+ public static Vec3 linearCombination(
+ Vec3 va,
+ float ka,
+ Vec3 vb,
+ float kb,
+ Vec3 output
+ ) {
+ if (output == null) {
+ output = new Vec3();
+ }
+
+ output.set(
+ va.x * ka + vb.x * kb,
+ va.y * ka + vb.y * kb,
+ va.z * ka + vb.z * kb
+ );
return output;
}
- public static Vec3 linearCombination(Vec3 va, float ka, Vec3 vb, float kb, Vec3 vc, float kc, Vec3 output) {
- output.set(va.x * ka + vb.x * kb + vc.x * kc, va.y * ka + vb.y * kb + vc.y * kc,
- va.z * ka + vb.z * kb + vc.z * kc);
+ public static Vec3 linearCombination(
+ Vec3 va,
+ float ka,
+ Vec3 vb,
+ float kb,
+ Vec3 vc,
+ float kc,
+ Vec3 output
+ ) {
+ if (output == null) {
+ output = new Vec3();
+ }
+
+ output.set(
+ va.x * ka + vb.x * kb + vc.x * kc,
+ va.y * ka + vb.y * kb + vc.y * kc,
+ va.z * ka + vb.z * kb + vc.z * kc
+ );
return output;
}
+
+ public static Vec3i sort(Vec3i input, Vec3i output) {
+ if (output == null) {
+ output = new Vec3i();
+ }
+
+ int ax = input.x, ay = input.y, az = input.z;
+
+ if (ax > ay) {
+ if (ax > az) {
+ output.x = ax;
+ output.y = ay > az ? ay : az;
+ output.z = ay > az ? az : ay;
+ } else {
+ output.x = az;
+ output.y = ax;
+ output.z = ay;
+ }
+ } else {
+ if (ay > az) {
+ output.x = ay;
+ output.y = ax > az ? ax : az;
+ output.z = ax > az ? az : ax;
+ } else {
+ output.x = az;
+ output.y = ay;
+ output.z = ax;
+ }
+ }
+
+ return output;
+ }
+
+ public static Vec3 sort(Vec3 input, Vec3 output) {
+ if (output == null) {
+ output = new Vec3();
+ }
+
+ float ax = input.x, ay = input.y, az = input.z;
+
+ if (ax > ay) {
+ if (ax > az) {
+ output.x = ax;
+ output.y = ay > az ? ay : az;
+ output.z = ay > az ? az : ay;
+ } else {
+ output.x = az;
+ output.y = ax;
+ output.z = ay;
+ }
+ } else {
+ if (ay > az) {
+ output.x = ay;
+ output.y = ax > az ? ax : az;
+ output.z = ax > az ? az : ax;
+ } else {
+ output.x = az;
+ output.y = ay;
+ output.z = ax;
+ }
+ }
+
+ return output;
+ }
+
+ public static Vec3i sortAfterAbs(Vec3i input, Vec3i output) {
+ if (output == null) {
+ output = new Vec3i();
+ }
+
+ input.abs(output);
+ return sort(output, output);
+ }
+
+ public static Vec3 sortAfterAbs(Vec3 input, Vec3 output) {
+ if (output == null) {
+ output = new Vec3();
+ }
+
+ input.abs(output);
+ return sort(output, output);
+ }
public static float get(Vec2 v, Axis a) {
switch (a) {
diff --git a/src/main/java/ru/windcorp/progressia/common/util/crash/ReportingEventBus.java b/src/main/java/ru/windcorp/progressia/common/util/crash/ReportingEventBus.java
index 87bb0f1..4644afa 100644
--- a/src/main/java/ru/windcorp/progressia/common/util/crash/ReportingEventBus.java
+++ b/src/main/java/ru/windcorp/progressia/common/util/crash/ReportingEventBus.java
@@ -22,11 +22,27 @@ import com.google.common.eventbus.EventBus;
import ru.windcorp.progressia.common.hacks.GuavaEventBusHijacker;
+/**
+ * A utility for creating Guava's {@link EventBus}es that
+ * {@linkplain CrashReports report} exceptions instead of suppressing them.
+ *
+ * @author javapony
+ */
public class ReportingEventBus {
private ReportingEventBus() {
}
+ /**
+ * Instantiates a new {@link EventBus} with the provided identifier that
+ * reports any unhandled exceptions with {@link CrashReports}.
+ *
+ * @param identifier the identifier of the new bus
+ * @return the created event bus
+ * @implNote This implementation relies on {@link GuavaEventBusHijacker} for
+ * creating buses with custom identifiers and uncaught exception
+ * handlers. It may break suddenly with a Guava update.
+ */
public static EventBus create(String identifier) {
return GuavaEventBusHijacker.newEventBus(identifier, (throwable, context) -> {
// Makes sense to append identifier to messageFormat because
diff --git a/src/main/java/ru/windcorp/progressia/common/util/noise/discrete/DiscreteNoise.java b/src/main/java/ru/windcorp/progressia/common/util/noise/discrete/DiscreteNoise.java
new file mode 100644
index 0000000..3e600f0
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/util/noise/discrete/DiscreteNoise.java
@@ -0,0 +1,25 @@
+/*
+ * 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.util.noise.discrete;
+
+public interface DiscreteNoise {
+
+ T get(double x, double y);
+ T get(double x, double y, double z);
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/util/noise/discrete/WorleyProceduralNoise.java b/src/main/java/ru/windcorp/progressia/common/util/noise/discrete/WorleyProceduralNoise.java
new file mode 100644
index 0000000..258e9b0
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/util/noise/discrete/WorleyProceduralNoise.java
@@ -0,0 +1,228 @@
+/*
+ * 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.util.noise.discrete;
+
+import java.util.Collection;
+import com.google.common.collect.ImmutableList;
+
+public class WorleyProceduralNoise implements DiscreteNoise {
+
+ /*
+ * Stolen from OpenJDK's Random implementation
+ * *evil cackling*
+ */
+ private static final long MULTIPLIER = 0x5DEECE66DL;
+ private static final long ADDEND = 0xBL;
+ private static final long MASK = (1L << 48) - 1;
+ private static final double DOUBLE_UNIT = 0x1.0p-53; // 1.0 / (1L << 53)
+
+ private static long permute(long seed) {
+ return (seed * MULTIPLIER + ADDEND) & MASK;
+ }
+
+ private static double getDouble(long seed) {
+ final int mask26bits = (1 << 26) - 1;
+ final int mask27bits = (1 << 27) - 1;
+
+ int randomBitsX26 = (int) (seed & 0xFFFFFFFF);
+ int randomBitsX27 = (int) ((seed >>> Integer.SIZE) & 0xFFFFFFFF);
+
+ randomBitsX26 = randomBitsX26 & mask26bits;
+ randomBitsX27 = randomBitsX27 & mask27bits;
+
+ return (((long) (randomBitsX26) << 27) + randomBitsX27) * DOUBLE_UNIT;
+ }
+
+ public static class Entry {
+ private final T value;
+ private final double chance;
+
+ public Entry(T value, double chance) {
+ this.value = value;
+ this.chance = chance;
+ }
+ }
+
+ public static class Builder {
+
+ com.google.common.collect.ImmutableList.Builder> builder = ImmutableList.builder();
+
+ public Builder add(T value, double chance) {
+ builder.add(new Entry<>(value, chance));
+ return this;
+ }
+
+ public WorleyProceduralNoise build(long seed) {
+ return new WorleyProceduralNoise<>(this, seed);
+ }
+
+ }
+
+ public static Builder builder() {
+ return new Builder<>();
+ }
+
+ private final Entry>[] entries;
+ private final long seed;
+
+ public WorleyProceduralNoise(Builder builder, long seed) {
+ this(builder.builder.build(), seed);
+ }
+
+ public WorleyProceduralNoise(Collection extends Entry extends T>> entries, long seed) {
+ this.entries = new Entry>[entries.size()];
+
+ double chancesSum = 0;
+ for (Entry extends T> entry : entries) {
+ chancesSum += entry.chance;
+ }
+
+ int i = 0;
+ for (Entry extends T> entry : entries) {
+ this.entries[i] = new Entry(entry.value, entry.chance / chancesSum);
+ i++;
+ }
+
+ this.seed = seed;
+ }
+
+ @Override
+ public T get(double x, double y) {
+
+ int ox = (int) x;
+ int oy = (int) y;
+
+ T closest = null;
+ double closestDistanceSq = Double.POSITIVE_INFINITY;
+
+ for (int cellX = ox - 1; cellX <= ox + 1; ++cellX) {
+ for (int cellY = oy - 1; cellY <= oy + 1; ++cellY) {
+
+ long cellSeed = permute(cellY ^ permute(cellX ^ seed));
+
+ int nodes = getNodeCount(cellSeed);
+ cellSeed = permute(cellSeed);
+
+ for (int i = 0; i < nodes; ++i) {
+
+ double nodeX = getDouble(cellSeed) + cellX;
+ cellSeed = permute(cellSeed);
+
+ double nodeY = getDouble(cellSeed) + cellY;
+ cellSeed = permute(cellSeed);
+
+ T value = getValue(getDouble(cellSeed));
+ cellSeed = permute(cellSeed);
+
+ double distanceSq = (x - nodeX) * (x - nodeX) + (y - nodeY) * (y - nodeY);
+ if (distanceSq < closestDistanceSq) {
+ closestDistanceSq = distanceSq;
+ closest = value;
+ }
+
+ }
+ }
+ }
+
+ return closest;
+
+ }
+
+ @Override
+ public T get(double x, double y, double z) {
+
+ int ox = (int) x;
+ int oy = (int) y;
+ int oz = (int) z;
+
+ T closest = null;
+ double closestDistanceSq = Double.POSITIVE_INFINITY;
+
+ for (int cellX = ox - 1; cellX <= ox + 1; ++cellX) {
+ for (int cellY = oy - 1; cellY <= oy + 1; ++cellY) {
+ for (int cellZ = oz - 1; cellZ <= oz + 1; ++cellZ) {
+
+ long cellSeed = permute(cellZ ^ permute(cellY ^ permute(cellX ^ seed)));
+
+ int nodes = getNodeCount(cellSeed);
+ cellSeed = permute(cellSeed);
+
+ for (int i = 0; i < nodes; ++i) {
+
+ double nodeX = getDouble(cellSeed) + cellX;
+ cellSeed = permute(cellSeed);
+
+ double nodeY = getDouble(cellSeed) + cellY;
+ cellSeed = permute(cellSeed);
+
+ double nodeZ = getDouble(cellSeed) + cellZ;
+ cellSeed = permute(cellSeed);
+
+ T value = getValue(getDouble(cellSeed));
+ cellSeed = permute(cellSeed);
+
+ double distanceSq = (x - nodeX) * (x - nodeX) + (y - nodeY) * (y - nodeY)
+ + (z - nodeZ) * (z - nodeZ);
+ if (distanceSq < closestDistanceSq) {
+ closestDistanceSq = distanceSq;
+ closest = value;
+ }
+
+ }
+ }
+ }
+ }
+
+ return closest;
+
+ }
+
+ @SuppressWarnings("unchecked")
+ private T getValue(double target) {
+ int i;
+
+ for (i = 0; i < entries.length && target > entries[i].chance; ++i) {
+ target -= entries[i].chance;
+ }
+
+ return (T) entries[i].value;
+ }
+
+ private int getNodeCount(long seed) {
+ int uniform = ((int) seed) % 8;
+
+ switch (uniform) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ return 1;
+
+ case 4:
+ case 5:
+ return 2;
+
+ case 6:
+ return 3;
+
+ default:
+ return 4;
+ }
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/BlockRay.java b/src/main/java/ru/windcorp/progressia/common/world/BlockRay.java
index e94ec08..c81e1a9 100644
--- a/src/main/java/ru/windcorp/progressia/common/world/BlockRay.java
+++ b/src/main/java/ru/windcorp/progressia/common/world/BlockRay.java
@@ -22,7 +22,7 @@ import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.util.VectorUtil.Axis;
-import ru.windcorp.progressia.common.world.block.BlockFace;
+import ru.windcorp.progressia.common.world.rels.AbsFace;
import static java.lang.Math.*;
@@ -34,14 +34,22 @@ public class BlockRay {
private float distance;
private final Vec3i block = new Vec3i();
- private BlockFace currentFace = null;
+ private AbsFace currentFace = null;
private boolean isValid = false;
public void start(Vec3 position, Vec3 direction) {
- if (!direction.any()) {
+ if (direction.x == 0 && direction.y == 0 && direction.z == 0) {
throw new IllegalArgumentException("Direction is a zero vector");
}
+
+ if (Float.isNaN(direction.x) || Float.isNaN(direction.y) || Float.isNaN(direction.z)) {
+ throw new IllegalArgumentException("Direction contains NaN: " + direction);
+ }
+
+ if (Float.isNaN(position.x) || Float.isNaN(position.y) || Float.isNaN(position.z)) {
+ throw new IllegalArgumentException("Position contains NaN: " + position);
+ }
isValid = true;
this.position.set(position).sub(0.5f); // Make sure lattice points are
@@ -75,16 +83,14 @@ public class BlockRay {
tMin = tz;
axis = Axis.Z;
}
+
+ assert tMin > 0 : "tMin is not positive (" + tMin + ")";
// block.(axis) += signum(direction.(axis))
VectorUtil.set(block, axis, VectorUtil.get(block, axis) + (int) signum(VectorUtil.get(direction, axis)));
// position += direction * tMin
- VectorUtil.linearCombination(position, 1, direction, tMin, position); // position
- // +=
- // direction
- // *
- // tMin
+ VectorUtil.linearCombination(position, 1, direction, tMin, position);
distance += tMin;
// position.(axis) = round(position.(axis))
@@ -110,18 +116,18 @@ public class BlockRay {
return (edge - c) / dir;
}
- private BlockFace computeCurrentFace(Axis axis, int sign) {
+ private AbsFace computeCurrentFace(Axis axis, int sign) {
if (sign == 0)
throw new IllegalStateException("sign is zero");
switch (axis) {
case X:
- return sign > 0 ? BlockFace.SOUTH : BlockFace.NORTH;
+ return sign > 0 ? AbsFace.NEG_X : AbsFace.POS_X;
case Y:
- return sign > 0 ? BlockFace.EAST : BlockFace.WEST;
+ return sign > 0 ? AbsFace.NEG_Y : AbsFace.POS_Y;
default:
case Z:
- return sign > 0 ? BlockFace.BOTTOM : BlockFace.TOP;
+ return sign > 0 ? AbsFace.NEG_Z : AbsFace.POS_Z;
}
}
@@ -137,7 +143,7 @@ public class BlockRay {
return output;
}
- public BlockFace getCurrentFace() {
+ public AbsFace getCurrentFace() {
return currentFace;
}
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 9a49545..fe84518 100644
--- a/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java
+++ b/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java
@@ -1,534 +1,12 @@
-/*
- * 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;
-import static ru.windcorp.progressia.common.world.block.BlockFace.*;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Objects;
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-
-import glm.vec._3.i.Vec3i;
-import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.world.block.BlockData;
-import ru.windcorp.progressia.common.world.block.BlockFace;
-import ru.windcorp.progressia.common.world.generic.GenericChunk;
+import ru.windcorp.progressia.common.world.generic.ChunkGenericWO;
import ru.windcorp.progressia.common.world.tile.TileData;
-import ru.windcorp.progressia.common.world.tile.TileDataStack;
-import ru.windcorp.progressia.common.world.tile.TileReference;
-import ru.windcorp.progressia.common.world.tile.TileStackIsFullException;
-public class ChunkData implements GenericChunk {
-
- public static final int BLOCKS_PER_CHUNK = Coordinates.CHUNK_SIZE;
-
- 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 Object generationHint = null;
-
- private final Collection listeners = Collections.synchronizedCollection(new ArrayList<>());
-
- public ChunkData(Vec3i position, WorldData world) {
- this.position.set(position.x, position.y, position.z);
- this.world = world;
- }
-
- @Override
- public Vec3i getPosition() {
- return position;
- }
-
- @Override
- public BlockData getBlock(Vec3i posInChunk) {
- return blocks[getBlockIndex(posInChunk)];
- }
-
- public void setBlock(Vec3i posInChunk, BlockData block, boolean notify) {
- BlockData previous = blocks[getBlockIndex(posInChunk)];
- blocks[getBlockIndex(posInChunk)] = block;
-
- if (notify) {
- getListeners().forEach(l -> {
- l.onChunkBlockChanged(this, posInChunk, previous, block);
- l.onChunkChanged(this);
- });
- }
- }
-
- @Override
- public TileDataStack getTilesOrNull(Vec3i blockInChunk, BlockFace face) {
- return tiles[getTileIndex(blockInChunk, face)];
- }
-
- /**
- * Internal use only. Modify a list returned by
- * {@link #getTiles(Vec3i, BlockFace)} or
- * {@link #getTilesOrNull(Vec3i, BlockFace)} to change tiles.
- */
- protected void setTiles(Vec3i blockInChunk, BlockFace face, TileDataStack tiles) {
- this.tiles[getTileIndex(blockInChunk, face)] = tiles;
- }
-
- @Override
- public boolean hasTiles(Vec3i blockInChunk, BlockFace face) {
- return getTilesOrNull(blockInChunk, face) != null;
- }
-
- @Override
- public TileDataStack getTiles(Vec3i blockInChunk, BlockFace face) {
- int index = getTileIndex(blockInChunk, face);
-
- if (tiles[index] == null) {
- createTileStack(blockInChunk, face);
- }
-
- return tiles[index];
- }
-
- private void createTileStack(Vec3i blockInChunk, BlockFace face) {
- Vec3i independentBlockInChunk = conjureIndependentBlockInChunkVec3i(blockInChunk);
- TileDataStackImpl stack = new TileDataStackImpl(independentBlockInChunk, face);
- setTiles(blockInChunk, face, stack);
- }
-
- private Vec3i conjureIndependentBlockInChunkVec3i(Vec3i blockInChunk) {
- for (int i = 0; i < BlockFace.BLOCK_FACE_COUNT; ++i) {
- TileDataStack stack = getTilesOrNull(blockInChunk, BlockFace.getFaces().get(i));
- if (stack instanceof TileDataStackImpl) {
- return ((TileDataStackImpl) stack).blockInChunk;
- }
- }
-
- return new Vec3i(blockInChunk);
- }
-
- private static int getBlockIndex(Vec3i posInChunk) {
- checkLocalCoordinates(posInChunk);
-
- return posInChunk.z * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK + posInChunk.y * BLOCKS_PER_CHUNK + posInChunk.x;
- }
-
- private static int getTileIndex(Vec3i posInChunk, BlockFace face) {
- return getBlockIndex(posInChunk) * BLOCK_FACE_COUNT + face.getId();
- }
-
- private static void checkLocalCoordinates(Vec3i posInChunk) {
- if (!isInBounds(posInChunk)) {
- throw new IllegalCoordinatesException(
- "Coordinates " + str(posInChunk) + " " + "are not legal chunk coordinates");
- }
- }
-
- public static boolean isInBounds(Vec3i posInChunk) {
- return posInChunk.x >= 0 && posInChunk.x < BLOCKS_PER_CHUNK && posInChunk.y >= 0
- && posInChunk.y < BLOCKS_PER_CHUNK && posInChunk.z >= 0 && posInChunk.z < BLOCKS_PER_CHUNK;
- }
-
- public boolean isBorder(Vec3i blockInChunk, BlockFace face) {
- final int min = 0, max = BLOCKS_PER_CHUNK - 1;
- return (blockInChunk.x == min && face == SOUTH) || (blockInChunk.x == max && face == NORTH)
- || (blockInChunk.y == min && face == EAST) || (blockInChunk.y == max && face == WEST)
- || (blockInChunk.z == min && face == BOTTOM) || (blockInChunk.z == max && face == TOP);
- }
-
- public void forEachBlock(Consumer action) {
- VectorUtil.iterateCuboid(0, 0, 0, BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK, action);
- }
-
- public void forEachTileStack(Consumer action) {
- forEachBlock(blockInChunk -> {
- for (BlockFace face : BlockFace.getFaces()) {
- TileDataStack stack = getTilesOrNull(blockInChunk, face);
- if (stack == null)
- continue;
- action.accept(stack);
- }
- });
- }
-
- /**
- * Iterates over all tiles in this chunk.
- *
- * @param action
- * the action to perform. {@code TileLocation} refers to each
- * tile using its primary block
- */
- public void forEachTile(BiConsumer action) {
- forEachTileStack(stack -> stack.forEach(tileData -> action.accept(stack, tileData)));
- }
-
- public WorldData getWorld() {
- return world;
- }
-
- public Collection getListeners() {
- return listeners;
- }
-
- public void addListener(ChunkDataListener listener) {
- this.listeners.add(listener);
- }
-
- public void removeListener(ChunkDataListener listener) {
- this.listeners.remove(listener);
- }
-
- private static String str(Vec3i v) {
- return "(" + v.x + "; " + v.y + "; " + v.z + ")";
- }
-
- protected void onLoaded() {
- getListeners().forEach(l -> l.onChunkLoaded(this));
- }
-
- protected void beforeUnloaded() {
- getListeners().forEach(l -> l.beforeChunkUnloaded(this));
- }
-
- public Object getGenerationHint() {
- return generationHint;
- }
-
- public void setGenerationHint(Object generationHint) {
- this.generationHint = generationHint;
- }
-
- /**
- * Implementation of {@link TileDataStack} used internally by
- * {@link ChunkData} to actually store the tiles. This is basically an array
- * wrapper with reporting capabilities.
- *
- * @author javapony
- */
- private class TileDataStackImpl extends TileDataStack {
- private class TileReferenceImpl implements TileReference {
- private int index;
-
- public TileReferenceImpl(int index) {
- this.index = index;
- }
-
- public void incrementIndex() {
- this.index++;
- }
-
- public void decrementIndex() {
- this.index--;
- }
-
- public void invalidate() {
- this.index = 0;
- }
-
- @Override
- public TileData get() {
- if (!isValid())
- return null;
- return TileDataStackImpl.this.get(this.index);
- }
-
- @Override
- public int getIndex() {
- return index;
- }
-
- @Override
- public TileDataStack getStack() {
- return TileDataStackImpl.this;
- }
-
- @Override
- public boolean isValid() {
- return this.index >= 0;
- }
- }
-
- private final TileData[] tiles = new TileData[TILES_PER_FACE];
- private int size = 0;
-
- private final TileReferenceImpl[] references = new TileReferenceImpl[tiles.length];
- private final int[] indicesByTag = new int[tiles.length];
- private final int[] tagsByIndex = new int[tiles.length];
-
- {
- Arrays.fill(indicesByTag, -1);
- Arrays.fill(tagsByIndex, -1);
- }
-
- /*
- * Potentially shared
- */
- private final Vec3i blockInChunk;
- private final BlockFace face;
-
- public TileDataStackImpl(Vec3i blockInChunk, BlockFace face) {
- this.blockInChunk = blockInChunk;
- this.face = face;
- }
-
- @Override
- public Vec3i getBlockInChunk(Vec3i output) {
- if (output == null)
- output = new Vec3i();
- output.set(blockInChunk.x, blockInChunk.y, blockInChunk.z);
- return output;
- }
-
- @Override
- public BlockFace getFace() {
- return face;
- }
-
- @Override
- public ChunkData getChunk() {
- return ChunkData.this;
- }
-
- @Override
- public int size() {
- return size;
- }
-
- @Override
- public TileData get(int index) {
- checkIndex(index, false);
-
- return tiles[index];
- }
-
- @Override
- public TileData set(int index, TileData tile) {
- Objects.requireNonNull(tile, "tile");
- TileData previous = get(index); // checks index
-
- tiles[index] = tile;
-
- if (references[index] != null) {
- references[index].invalidate();
- references[index] = null;
- }
-
- assert checkConsistency();
-
- report(previous, tile);
- return previous;
- }
-
- @Override
- public void add(int index, TileData tile) {
- Objects.requireNonNull(tile, "tile");
- checkIndex(index, true);
-
- if (index != size()) {
- System.arraycopy(tiles, index + 1, tiles, index + 2, size - index);
-
- for (int i = index; i < size; ++i) {
- if (references[i] != null) {
- references[i].incrementIndex();
- }
-
- indicesByTag[tagsByIndex[i]]++;
- }
-
- System.arraycopy(references, index + 1, references, index + 2, size - index);
- System.arraycopy(tagsByIndex, index + 1, tagsByIndex, index + 2, size - index);
- }
-
- size++;
- tiles[index] = tile;
- references[index] = null;
-
- for (int tag = 0; tag < indicesByTag.length; ++tag) {
- if (tagsByIndex[tag] == -1) {
- indicesByTag[tag] = index;
- tagsByIndex[index] = tag;
- break;
- }
- }
-
- modCount++;
- assert checkConsistency();
-
- report(null, tile);
- }
-
- @Override
- public void load(TileData tile, int tag) {
- addFarthest(tile);
-
- int assignedTag = getIndexByTag(tag);
-
- if (assignedTag == tag)
- return;
- if (assignedTag == -1) {
- throw new IllegalArgumentException(
- "Tag " + tag + " already used by tile at index " + getIndexByTag(tag));
- }
-
- indicesByTag[tagsByIndex[size() - 1]] = -1;
- tagsByIndex[size() - 1] = tag;
- indicesByTag[tag] = size() - 1;
-
- assert checkConsistency();
- }
-
- @Override
- public TileData remove(int index) {
- TileData previous = get(index); // checks index
-
- if (references[index] != null) {
- references[index].invalidate();
- }
-
- indicesByTag[tagsByIndex[index]] = -1;
-
- if (index != size() - 1) {
- System.arraycopy(tiles, index + 1, tiles, index, size - index - 1);
-
- for (int i = index + 1; i < size; ++i) {
- if (references[i] != null) {
- references[i].decrementIndex();
- }
-
- indicesByTag[tagsByIndex[i]]--;
- }
-
- System.arraycopy(references, index + 1, references, index, size - index - 1);
- System.arraycopy(tagsByIndex, index + 1, tagsByIndex, index, size - index - 1);
- }
-
- size--;
- tiles[size] = null;
- references[size] = null;
- tagsByIndex[size] = -1;
-
- modCount++;
- assert checkConsistency();
-
- report(previous, null);
- return previous;
- }
-
- @Override
- public TileReference getReference(int index) {
- checkIndex(index, false);
-
- if (references[index] == null) {
- references[index] = new TileReferenceImpl(index);
- }
-
- return references[index];
- }
-
- @Override
- public int getIndexByTag(int tag) {
- return indicesByTag[tag];
- }
-
- @Override
- public int getTagByIndex(int index) {
- checkIndex(index, false);
- return tagsByIndex[index];
- }
-
- @Override
- public void clear() {
- while (!isEmpty()) {
- removeFarthest();
- }
- }
-
- private void checkIndex(int index, boolean isSizeAllowed) {
- if (isSizeAllowed ? (index > size()) : (index >= size()))
- throw new IndexOutOfBoundsException("Index " + index + " is out of bounds: size is " + size);
-
- if (index < 0)
- throw new IndexOutOfBoundsException("Index " + index + " is out of bounds: index cannot be negative");
-
- if (index >= TILES_PER_FACE)
- throw new TileStackIsFullException(
- "Index " + index + " is out of bounds: maximum tile stack size is " + TILES_PER_FACE);
- }
-
- private void report(TileData previous, TileData current) {
- ChunkData.this.getListeners().forEach(l -> {
- if (previous != null) {
- l.onChunkTilesChanged(ChunkData.this, blockInChunk, face, previous, false);
- }
-
- if (current != null) {
- l.onChunkTilesChanged(ChunkData.this, blockInChunk, face, current, true);
- }
-
- l.onChunkChanged(ChunkData.this);
- });
- }
-
- private boolean checkConsistency() {
- int index;
-
- for (index = 0; index < size(); ++index) {
- if (get(index) == null)
- throw new AssertionError("get(index) is null");
-
- if (references[index] != null) {
- TileReference ref = getReference(index);
- if (ref == null)
- throw new AssertionError("references[index] is not null but getReference(index) is");
- if (!ref.isValid())
- throw new AssertionError("Reference is not valid");
- if (ref.get() != get(index))
- throw new AssertionError("Reference points to " + (ref.get() == null ? "null" : "wrong tile"));
- if (ref.getIndex() != index)
- throw new AssertionError("Reference has invalid index");
- if (ref.getStack() != this)
- throw new AssertionError("Reference has invalid TDS");
- }
-
- if (index != indicesByTag[tagsByIndex[index]])
- throw new AssertionError("Tag mapping is inconsistent");
- if (index != getIndexByTag(getTagByIndex(index)))
- throw new AssertionError("Tag methods are inconsistent with tag mapping");
- }
-
- for (; index < tiles.length; ++index) {
- if (tiles[index] != null)
- throw new AssertionError("Leftover tile detected");
- if (references[index] != null)
- throw new AssertionError("Leftover reference detected");
- if (tagsByIndex[index] != -1)
- throw new AssertionError("Leftover tags detected");
- }
-
- return true;
- }
-
- }
+public interface ChunkData
+ extends ChunkDataRO, ChunkGenericWO {
+
+ // currently empty
}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/ChunkDataListener.java b/src/main/java/ru/windcorp/progressia/common/world/ChunkDataListener.java
index f9a7fb2..22b4fb3 100644
--- a/src/main/java/ru/windcorp/progressia/common/world/ChunkDataListener.java
+++ b/src/main/java/ru/windcorp/progressia/common/world/ChunkDataListener.java
@@ -15,81 +15,78 @@
* 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;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.block.BlockData;
-import ru.windcorp.progressia.common.world.block.BlockFace;
+import ru.windcorp.progressia.common.world.rels.RelFace;
import ru.windcorp.progressia.common.world.tile.TileData;
public interface ChunkDataListener {
/**
- * Invoked after a block has changed in a chunk. This is not triggered when
- * a change is caused by chunk loading or unloading.
+ * Invoked after a block has changed in a chunk.
+ * This is not triggered when a change is caused by chunk loading or
+ * unloading.
*
- * @param chunk
- * the chunk that has changed
- * @param blockInChunk
- * the {@linkplain Coordinates#blockInChunk chunk coordinates} of
- * the change
- * @param previous
- * the previous occupant of {@code blockInChunk}
- * @param current
- * the current (new) occupant of {@code blockInChunk}
+ * @param chunk the chunk that has changed
+ * @param blockInChunk the {@linkplain Coordinates#blockInChunk chunk
+ * coordinates} of the change
+ * @param previous the previous occupant of {@code blockInChunk}
+ * @param current the current (new) occupant of {@code blockInChunk}
*/
- default void onChunkBlockChanged(ChunkData chunk, Vec3i blockInChunk, BlockData previous, BlockData current) {
+ default void onChunkBlockChanged(DefaultChunkData chunk, Vec3i blockInChunk, BlockData previous, BlockData current) {
}
/**
- * Invoked after a tile has been added or removed from a chunk. This is not
- * triggered when a change is caused by chunk loading or unloading.
+ * Invoked after a tile has been added or removed from a chunk.
+ * This is not triggered when a change is caused by chunk loading or
+ * unloading.
*
- * @param chunk
- * the chunk that has changed
- * @param blockInChunk
- * the {@linkplain Coordinates#blockInChunk chunk coordinates} of
- * the change
- * @param face
- * the face that the changed tile belongs or belonged to
- * @param tile
- * the tile that has been added or removed
- * @param wasAdded
- * {@code true} iff the tile has been added, {@code false} iff
- * the tile has been removed
+ * @param chunk the chunk that has changed
+ * @param blockInChunk the {@linkplain Coordinates#blockInChunk chunk
+ * coordinates} of the change
+ * @param face the face that the changed tile belongs or belonged to
+ * @param tile the tile that has been added or removed
+ * @param wasAdded {@code true} iff the tile has been added,
+ * {@code false} iff the tile has been removed
*/
- default void onChunkTilesChanged(ChunkData chunk, Vec3i blockInChunk, BlockFace face, TileData tile,
- boolean wasAdded) {
+ default void onChunkTilesChanged(
+ DefaultChunkData chunk,
+ Vec3i blockInChunk,
+ RelFace face,
+ TileData tile,
+ boolean wasAdded
+ ) {
}
/**
* Invoked whenever a chunk changes, loads or unloads. If some other method
- * in this {@code ChunkDataListener} are to be invoked, e.g. is the change
- * was caused by a block being removed, this method is called last.
+ * in this
+ * {@code ChunkDataListener} are to be invoked, e.g. is the change was
+ * caused by a
+ * block being removed, this method is called last.
*
- * @param chunk
- * the chunk that has changed
+ * @param chunk the chunk that has changed
*/
- default void onChunkChanged(ChunkData chunk) {
+ default void onChunkChanged(DefaultChunkData chunk) {
}
/**
* Invoked whenever a chunk has been loaded.
*
- * @param chunk
- * the chunk that has loaded
+ * @param chunk the chunk that has loaded
*/
- default void onChunkLoaded(ChunkData chunk) {
+ default void onChunkLoaded(DefaultChunkData chunk) {
}
/**
* Invoked whenever a chunk is about to be unloaded.
*
- * @param chunk
- * the chunk that is going to be loaded
+ * @param chunk the chunk that is going to be loaded
*/
- default void beforeChunkUnloaded(ChunkData chunk) {
+ default void beforeChunkUnloaded(DefaultChunkData chunk) {
}
}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/ChunkDataListeners.java b/src/main/java/ru/windcorp/progressia/common/world/ChunkDataListeners.java
index fdae0ab..63a164a 100644
--- a/src/main/java/ru/windcorp/progressia/common/world/ChunkDataListeners.java
+++ b/src/main/java/ru/windcorp/progressia/common/world/ChunkDataListeners.java
@@ -28,7 +28,7 @@ public class ChunkDataListeners {
public static WorldDataListener createAdder(Supplier listenerSupplier) {
return new WorldDataListener() {
@Override
- public void getChunkListeners(WorldData world, Vec3i chunk, Consumer chunkListenerSink) {
+ public void getChunkListeners(DefaultWorldData world, Vec3i chunk, Consumer chunkListenerSink) {
chunkListenerSink.accept(listenerSupplier.get());
}
};
diff --git a/src/main/java/ru/windcorp/progressia/common/world/ChunkDataRO.java b/src/main/java/ru/windcorp/progressia/common/world/ChunkDataRO.java
new file mode 100644
index 0000000..08ce159
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/ChunkDataRO.java
@@ -0,0 +1,12 @@
+package ru.windcorp.progressia.common.world;
+
+import ru.windcorp.progressia.common.world.block.BlockData;
+import ru.windcorp.progressia.common.world.generic.ChunkGenericRO;
+import ru.windcorp.progressia.common.world.tile.TileData;
+
+public interface ChunkDataRO
+ extends ChunkGenericRO {
+
+ // currently empty
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/Coordinates.java b/src/main/java/ru/windcorp/progressia/common/world/Coordinates.java
index bc26afe..a8a454e 100644
--- a/src/main/java/ru/windcorp/progressia/common/world/Coordinates.java
+++ b/src/main/java/ru/windcorp/progressia/common/world/Coordinates.java
@@ -18,7 +18,7 @@
package ru.windcorp.progressia.common.world;
-import static ru.windcorp.progressia.common.world.ChunkData.BLOCKS_PER_CHUNK;
+import static ru.windcorp.progressia.common.world.DefaultChunkData.BLOCKS_PER_CHUNK;
import glm.vec._3.i.Vec3i;
diff --git a/src/main/java/ru/windcorp/progressia/common/world/DefaultChunkData.java b/src/main/java/ru/windcorp/progressia/common/world/DefaultChunkData.java
new file mode 100644
index 0000000..e11509b
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/DefaultChunkData.java
@@ -0,0 +1,529 @@
+/*
+ * 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;
+
+import static ru.windcorp.progressia.common.world.rels.BlockFace.BLOCK_FACE_COUNT;
+
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+
+import glm.vec._3.i.Vec3i;
+
+import ru.windcorp.progressia.common.world.block.BlockData;
+import ru.windcorp.progressia.common.world.generic.GenericChunks;
+import ru.windcorp.progressia.common.world.rels.AbsFace;
+import ru.windcorp.progressia.common.world.rels.BlockFace;
+import ru.windcorp.progressia.common.world.rels.RelFace;
+import ru.windcorp.progressia.common.world.tile.TileData;
+import ru.windcorp.progressia.common.world.tile.TileStackIsFullException;
+
+/**
+ * The implementation of {@link ChunkData} used to store the actual game world.
+ * This class should be considered an implementation detail.
+ */
+public class DefaultChunkData implements ChunkData {
+
+ 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 DefaultWorldData 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 AbsFace up;
+
+ private Object generationHint = null;
+
+ private final Collection listeners = Collections.synchronizedCollection(new ArrayList<>());
+
+ public DefaultChunkData(Vec3i position, DefaultWorldData world) {
+ this.position.set(position.x, position.y, position.z);
+ this.world = world;
+ this.up = world.getGravityModel().getDiscreteUp(position);
+ }
+
+ @Override
+ public Vec3i getPosition() {
+ return position;
+ }
+
+ @Override
+ public AbsFace getUp() {
+ return up;
+ }
+
+ @Override
+ public BlockData getBlock(Vec3i posInChunk) {
+ return blocks[getBlockIndex(posInChunk)];
+ }
+
+ @Override
+ public void setBlock(Vec3i posInChunk, BlockData block, boolean notify) {
+ BlockData previous = blocks[getBlockIndex(posInChunk)];
+ blocks[getBlockIndex(posInChunk)] = block;
+
+ if (notify) {
+ getListeners().forEach(l -> {
+ l.onChunkBlockChanged(this, posInChunk, previous, block);
+ l.onChunkChanged(this);
+ });
+ }
+ }
+
+ @Override
+ public TileDataStack getTilesOrNull(Vec3i blockInChunk, BlockFace face) {
+ return tiles[getTileIndex(blockInChunk, face)];
+ }
+
+ /**
+ * Internal use only. Modify a list returned by
+ * {@link #getTiles(Vec3i, BlockFace)} or
+ * {@link #getTilesOrNull(Vec3i, BlockFace)}
+ * to change tiles.
+ */
+ protected void setTiles(
+ Vec3i blockInChunk,
+ BlockFace face,
+ TileDataStack tiles
+ ) {
+ this.tiles[getTileIndex(blockInChunk, face)] = tiles;
+ }
+
+ @Override
+ public boolean hasTiles(Vec3i blockInChunk, BlockFace face) {
+ return getTilesOrNull(blockInChunk, face) != null;
+ }
+
+ @Override
+ public TileDataStack getTiles(Vec3i blockInChunk, BlockFace face) {
+ int index = getTileIndex(blockInChunk, face);
+
+ if (tiles[index] == null) {
+ createTileStack(blockInChunk, face);
+ }
+
+ return tiles[index];
+ }
+
+ private void createTileStack(Vec3i blockInChunk, BlockFace face) {
+ Vec3i independentBlockInChunk = conjureIndependentBlockInChunkVec3i(blockInChunk);
+ TileDataStackImpl stack = new TileDataStackImpl(independentBlockInChunk, face);
+ setTiles(blockInChunk, face, stack);
+ }
+
+ private Vec3i conjureIndependentBlockInChunkVec3i(Vec3i blockInChunk) {
+ for (int i = 0; i < AbsFace.BLOCK_FACE_COUNT; ++i) {
+ TileDataStack stack = getTilesOrNull(blockInChunk, AbsFace.getFaces().get(i));
+ if (stack instanceof TileDataStackImpl) {
+ return ((TileDataStackImpl) stack).blockInChunk;
+ }
+ }
+
+ return new Vec3i(blockInChunk);
+ }
+
+ private static int getBlockIndex(Vec3i posInChunk) {
+ checkLocalCoordinates(posInChunk);
+
+ return posInChunk.z * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK +
+ posInChunk.y * BLOCKS_PER_CHUNK +
+ posInChunk.x;
+ }
+
+ private int getTileIndex(Vec3i posInChunk, BlockFace face) {
+ return getBlockIndex(posInChunk) * BLOCK_FACE_COUNT +
+ face.resolve(getUp()).getId();
+ }
+
+ private static void checkLocalCoordinates(Vec3i posInChunk) {
+ if (!GenericChunks.containsBiC(posInChunk)) {
+ throw new IllegalCoordinatesException(
+ "Coordinates (" + posInChunk.x + "; " + posInChunk.y + "; " + posInChunk.z + ") "
+ + "are not legal chunk coordinates"
+ );
+ }
+ }
+
+ public DefaultWorldData getWorld() {
+ return world;
+ }
+
+ public Collection getListeners() {
+ return listeners;
+ }
+
+ public void addListener(ChunkDataListener listener) {
+ this.listeners.add(listener);
+ }
+
+ public void removeListener(ChunkDataListener listener) {
+ this.listeners.remove(listener);
+ }
+
+ protected void onLoaded() {
+ getListeners().forEach(l -> l.onChunkLoaded(this));
+ }
+
+ protected void beforeUnloaded() {
+ getListeners().forEach(l -> l.beforeChunkUnloaded(this));
+ }
+
+ public Object getGenerationHint() {
+ return generationHint;
+ }
+
+ public void setGenerationHint(Object generationHint) {
+ this.generationHint = generationHint;
+ }
+
+ /**
+ * Implementation of {@link TileDataStack} used internally by
+ * {@link DefaultChunkData} to
+ * actually store the tiles. This is basically an array wrapper with
+ * reporting
+ * capabilities.
+ *
+ * @author javapony
+ */
+ private class TileDataStackImpl extends AbstractList implements TileDataStack {
+ private class TileDataReferenceImpl implements TileDataReference {
+ private int index;
+
+ public TileDataReferenceImpl(int index) {
+ this.index = index;
+ }
+
+ public void incrementIndex() {
+ this.index++;
+ }
+
+ public void decrementIndex() {
+ this.index--;
+ }
+
+ public void invalidate() {
+ this.index = -1;
+ }
+
+ @Override
+ public TileData get() {
+ if (!isValid())
+ return null;
+ return TileDataStackImpl.this.get(this.index);
+ }
+
+ @Override
+ public int getIndex() {
+ return index;
+ }
+
+ @Override
+ public TileDataStack getStack() {
+ return TileDataStackImpl.this;
+ }
+
+ @Override
+ public boolean isValid() {
+ return this.index >= 0;
+ }
+ }
+
+ private final TileData[] tiles = new TileData[TILES_PER_FACE];
+ private int size = 0;
+
+ private final TileDataReferenceImpl[] references = new TileDataReferenceImpl[tiles.length];
+ private final int[] indicesByTag = new int[tiles.length];
+ private final int[] tagsByIndex = new int[tiles.length];
+
+ {
+ Arrays.fill(indicesByTag, -1);
+ Arrays.fill(tagsByIndex, -1);
+ }
+
+ /*
+ * Potentially shared
+ */
+ private final Vec3i blockInChunk;
+ private final RelFace face;
+
+ public TileDataStackImpl(Vec3i blockInChunk, BlockFace face) {
+ this.blockInChunk = blockInChunk;
+ this.face = face.relativize(getUp());
+ }
+
+ @Override
+ public Vec3i getBlockInChunk(Vec3i output) {
+ if (output == null)
+ output = new Vec3i();
+ output.set(blockInChunk.x, blockInChunk.y, blockInChunk.z);
+ return output;
+ }
+
+ @Override
+ public RelFace getFace() {
+ return face;
+ }
+
+ @Override
+ public DefaultChunkData getChunk() {
+ return DefaultChunkData.this;
+ }
+
+ @Override
+ public int size() {
+ return size;
+ }
+
+ @Override
+ public TileData get(int index) {
+ checkIndex(index, false);
+
+ return tiles[index];
+ }
+
+ @Override
+ public TileData set(int index, TileData tile) {
+ Objects.requireNonNull(tile, "tile");
+ TileData previous = get(index); // checks index
+
+ tiles[index] = tile;
+
+ if (references[index] != null) {
+ references[index].invalidate();
+ references[index] = null;
+ }
+
+ assert checkConsistency();
+
+ report(previous, tile);
+ return previous;
+ }
+
+ @Override
+ public void add(int index, TileData tile) {
+ Objects.requireNonNull(tile, "tile");
+ checkIndex(index, true);
+
+ if (index != size()) {
+ System.arraycopy(tiles, index + 1, tiles, index + 2, size - index);
+
+ for (int i = index; i < size; ++i) {
+ if (references[i] != null) {
+ references[i].incrementIndex();
+ }
+
+ indicesByTag[tagsByIndex[i]]++;
+ }
+
+ System.arraycopy(references, index + 1, references, index + 2, size - index);
+ System.arraycopy(tagsByIndex, index + 1, tagsByIndex, index + 2, size - index);
+ }
+
+ size++;
+ tiles[index] = tile;
+ references[index] = null;
+
+ for (int tag = 0; tag < indicesByTag.length; ++tag) {
+ if (indicesByTag[tag] == -1) {
+ indicesByTag[tag] = index;
+ tagsByIndex[index] = tag;
+ break;
+ }
+ }
+
+ modCount++;
+ assert checkConsistency();
+
+ report(null, tile);
+ }
+
+ @Override
+ public void load(TileData tile, int tag) {
+ addFarthest(tile);
+
+ int assignedIndex = size() - 1;
+
+ // Skip if we already have the correct tag
+ int assignedTag = getTagByIndex(assignedIndex);
+ if (assignedTag == tag) {
+ return;
+ }
+ assert assignedTag != -1 : "Adding farthest tile resulted in -1 tag";
+
+ // Make sure we aren't trying to assign a tag already in use
+ int tileWithRequestedTag = getIndexByTag(tag);
+ if (tileWithRequestedTag != -1) {
+ throw new IllegalArgumentException(
+ "Tag " + tag + " already used by tile at index " + tileWithRequestedTag
+ );
+ }
+ assert tileWithRequestedTag != assignedIndex
+ : "tag == assignedTag yet tileWithRequestedTag != assignedIndex";
+
+ // Do the tag editing
+ indicesByTag[assignedTag] = -1; // Release assigned tag
+ tagsByIndex[assignedIndex] = tag; // Reroute assigned index to
+ // requested tag
+ indicesByTag[tag] = assignedIndex; // Claim requested tag
+ assert checkConsistency();
+ }
+
+ @Override
+ public TileData remove(int index) {
+ TileData previous = get(index); // checks index
+
+ if (references[index] != null) {
+ references[index].invalidate();
+ }
+
+ indicesByTag[tagsByIndex[index]] = -1;
+
+ if (index != size() - 1) {
+ System.arraycopy(tiles, index + 1, tiles, index, size - index - 1);
+
+ for (int i = index + 1; i < size; ++i) {
+ if (references[i] != null) {
+ references[i].decrementIndex();
+ }
+
+ indicesByTag[tagsByIndex[i]]--;
+ }
+
+ System.arraycopy(references, index + 1, references, index, size - index - 1);
+ System.arraycopy(tagsByIndex, index + 1, tagsByIndex, index, size - index - 1);
+ }
+
+ size--;
+ tiles[size] = null;
+ references[size] = null;
+ tagsByIndex[size] = -1;
+
+ modCount++;
+ assert checkConsistency();
+
+ report(previous, null);
+ return previous;
+ }
+
+ @Override
+ public TileDataReference getReference(int index) {
+ checkIndex(index, false);
+
+ if (references[index] == null) {
+ references[index] = new TileDataReferenceImpl(index);
+ }
+
+ return references[index];
+ }
+
+ @Override
+ public int getIndexByTag(int tag) {
+ return indicesByTag[tag];
+ }
+
+ @Override
+ public int getTagByIndex(int index) {
+ checkIndex(index, false);
+ return tagsByIndex[index];
+ }
+
+ @Override
+ public void clear() {
+ while (!isEmpty()) {
+ removeFarthest();
+ }
+ }
+
+ private void checkIndex(int index, boolean isSizeAllowed) {
+ if (isSizeAllowed ? (index > size()) : (index >= size()))
+ throw new IndexOutOfBoundsException("Index " + index + " is out of bounds: size is " + size);
+
+ if (index < 0)
+ throw new IndexOutOfBoundsException("Index " + index + " is out of bounds: index cannot be negative");
+
+ if (index >= TILES_PER_FACE)
+ throw new TileStackIsFullException(
+ "Index " + index + " is out of bounds: maximum tile stack size is " + TILES_PER_FACE
+ );
+ }
+
+ private void report(TileData previous, TileData current) {
+ DefaultChunkData.this.getListeners().forEach(l -> {
+ if (previous != null) {
+ l.onChunkTilesChanged(DefaultChunkData.this, blockInChunk, face, previous, false);
+ }
+
+ if (current != null) {
+ l.onChunkTilesChanged(DefaultChunkData.this, blockInChunk, face, current, true);
+ }
+
+ l.onChunkChanged(DefaultChunkData.this);
+ });
+ }
+
+ private boolean checkConsistency() {
+ int index;
+
+ for (index = 0; index < size(); ++index) {
+ if (get(index) == null)
+ throw new AssertionError("get(index) is null");
+
+ if (references[index] != null) {
+ TileDataReference ref = getReference(index);
+ if (ref == null)
+ throw new AssertionError("references[index] is not null but getReference(index) is");
+ if (!ref.isValid())
+ throw new AssertionError("Reference is not valid");
+ if (ref.get() != get(index))
+ throw new AssertionError("Reference points to " + (ref.get() == null ? "null" : "wrong tile"));
+ if (ref.getIndex() != index)
+ throw new AssertionError("Reference has invalid index");
+ if (ref.getStack() != this)
+ throw new AssertionError("Reference has invalid TDS");
+ }
+
+ if (index != indicesByTag[tagsByIndex[index]])
+ throw new AssertionError("Tag mapping is inconsistent");
+ if (index != getIndexByTag(getTagByIndex(index)))
+ throw new AssertionError("Tag methods are inconsistent with tag mapping");
+ }
+
+ for (; index < tiles.length; ++index) {
+ if (tiles[index] != null)
+ throw new AssertionError("Leftover tile detected");
+ if (references[index] != null)
+ throw new AssertionError("Leftover reference detected");
+ if (tagsByIndex[index] != -1)
+ throw new AssertionError("Leftover tags detected");
+ }
+
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/DefaultWorldData.java b/src/main/java/ru/windcorp/progressia/common/world/DefaultWorldData.java
new file mode 100644
index 0000000..1de79a6
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/DefaultWorldData.java
@@ -0,0 +1,262 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+import glm.vec._3.i.Vec3i;
+import gnu.trove.TCollections;
+import gnu.trove.map.TLongObjectMap;
+import gnu.trove.map.hash.TLongObjectHashMap;
+import gnu.trove.set.TLongSet;
+import ru.windcorp.progressia.common.collision.CollisionModel;
+import ru.windcorp.progressia.common.state.StateChange;
+import ru.windcorp.progressia.common.state.StatefulObject;
+import ru.windcorp.progressia.common.world.block.BlockData;
+import ru.windcorp.progressia.common.world.entity.EntityData;
+import ru.windcorp.progressia.common.world.generic.ChunkMap;
+import ru.windcorp.progressia.common.world.generic.ChunkSet;
+import ru.windcorp.progressia.common.world.generic.EntityGeneric;
+import ru.windcorp.progressia.common.world.rels.BlockFace;
+import ru.windcorp.progressia.common.world.generic.LongBasedChunkMap;
+
+public class DefaultWorldData implements WorldData {
+
+ private final ChunkMap chunksByPos = new LongBasedChunkMap<>(
+ TCollections.synchronizedMap(new TLongObjectHashMap<>())
+ );
+
+ private final Collection chunks = Collections.unmodifiableCollection(chunksByPos.values());
+
+ private final TLongObjectMap entitiesById = TCollections.synchronizedMap(new TLongObjectHashMap<>());
+
+ private final Collection entities = Collections.unmodifiableCollection(entitiesById.valueCollection());
+
+ private GravityModel gravityModel = null;
+
+ private float time = 0;
+
+ private final Collection listeners = Collections.synchronizedCollection(new ArrayList<>());
+
+ public DefaultWorldData() {
+
+ }
+
+ @Override
+ public DefaultChunkData getChunk(Vec3i pos) {
+ return chunksByPos.get(pos);
+ }
+
+ @Override
+ public DefaultChunkData getChunkByBlock(Vec3i blockInWorld) {
+ return (DefaultChunkData) WorldData.super.getChunkByBlock(blockInWorld);
+ }
+
+ @Override
+ public Collection getChunks() {
+ return chunks;
+ }
+
+ public ChunkSet getLoadedChunks() {
+ return chunksByPos.keys();
+ }
+
+ @Override
+ public Collection getEntities() {
+ return entities;
+ }
+
+ @Override
+ public void forEachEntity(Consumer super EntityData> action) {
+ synchronized (entitiesById) { // TODO HORRIBLY MUTILATE THE CORPSE OF
+ // TROVE4J so that
+ // gnu.trove.impl.sync.SynchronizedCollection.forEach
+ // is synchronized
+ getEntities().forEach(action);
+ }
+ }
+
+ public TLongSet getLoadedEntities() {
+ return entitiesById.keySet();
+ }
+
+ private void addChunkListeners(DefaultChunkData chunk) {
+ getListeners().forEach(l -> l.getChunkListeners(this, chunk.getPosition(), chunk::addListener));
+ }
+
+ public synchronized void addChunk(DefaultChunkData chunk) {
+ addChunkListeners(chunk);
+
+ DefaultChunkData previous = chunksByPos.get(chunk);
+ if (previous != null) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Chunk at (%d; %d; %d) already exists",
+ chunk.getPosition().x,
+ chunk.getPosition().y,
+ chunk.getPosition().z
+ )
+ );
+ }
+
+ chunksByPos.put(chunk, chunk);
+
+ chunk.onLoaded();
+ getListeners().forEach(l -> l.onChunkLoaded(this, chunk));
+ }
+
+ public synchronized void removeChunk(DefaultChunkData chunk) {
+ getListeners().forEach(l -> l.beforeChunkUnloaded(this, chunk));
+ chunk.beforeUnloaded();
+
+ chunksByPos.remove(chunk);
+ }
+
+ @Override
+ public void setBlock(Vec3i blockInWorld, BlockData block, boolean notify) {
+ DefaultChunkData chunk = getChunkByBlock(blockInWorld);
+ if (chunk == null)
+ throw new IllegalCoordinatesException(
+ "Coordinates "
+ + "(" + blockInWorld.x + "; " + blockInWorld.y + "; " + blockInWorld.z + ") "
+ + "do not belong to a loaded chunk"
+ );
+
+ chunk.setBlock(Coordinates.convertInWorldToInChunk(blockInWorld, null), block, notify);
+ }
+
+ @Override
+ public TileDataStack getTiles(Vec3i blockInWorld, BlockFace face) {
+ return WorldData.super.getTiles(blockInWorld, face);
+ }
+
+ @Override
+ public EntityData getEntity(long entityId) {
+ return entitiesById.get(entityId);
+ }
+
+ @Override
+ public void addEntity(EntityData entity) {
+ Objects.requireNonNull(entity, "entity");
+
+ EntityData previous = entitiesById.putIfAbsent(entity.getEntityId(), entity);
+
+ if (previous != null) {
+ String message = "Cannot add entity " + entity + ": ";
+
+ if (previous == entity) {
+ message += "already present";
+ } else {
+ message += "entity with the same EntityID already present (" + previous + ")";
+ }
+
+ throw new IllegalStateException(message);
+ }
+
+ getListeners().forEach(l -> l.onEntityAdded(this, entity));
+ }
+
+ @Override
+ public void removeEntity(long entityId) {
+ synchronized (entitiesById) {
+ EntityData entity = entitiesById.get(entityId);
+
+ if (entity == null) {
+ throw new IllegalArgumentException(
+ "Entity with EntityID " + EntityData.formatEntityId(entityId) + " not present"
+ );
+ } else {
+ removeEntity(entity);
+ }
+ }
+ }
+
+ @Override
+ public void removeEntity(EntityData entity) {
+ Objects.requireNonNull(entity, "entity");
+
+ getListeners().forEach(l -> l.beforeEntityRemoved(this, entity));
+ entitiesById.remove(entity.getEntityId());
+ }
+
+ @Override
+ public void changeEntity(SE entity, StateChange change) {
+ change.change(entity);
+ }
+
+ @Override
+ public float getTime() {
+ return time;
+ }
+
+ @Override
+ public void advanceTime(float change) {
+ this.time += change;
+ }
+
+ public CollisionModel getCollisionModelOfBlock(Vec3i blockInWorld) {
+ DefaultChunkData chunk = getChunkByBlock(blockInWorld);
+ if (chunk == null)
+ return null;
+
+ BlockData block = chunk.getBlock(Coordinates.convertInWorldToInChunk(blockInWorld, null));
+ if (block == null)
+ return null;
+ return block.getCollisionModel();
+ }
+
+ /**
+ * @return the gravity model
+ */
+ @Override
+ public GravityModel getGravityModel() {
+ return gravityModel;
+ }
+
+ /**
+ * @param gravityModel the gravity model to set
+ */
+ public void setGravityModel(GravityModel gravityModel) {
+ if (!chunks.isEmpty()) {
+ throw new IllegalStateException(
+ "Attempted to change gravity model to " + gravityModel + " while " + chunks.size()
+ + " chunks were loaded"
+ );
+ }
+
+ this.gravityModel = gravityModel;
+ }
+
+ public Collection getListeners() {
+ return listeners;
+ }
+
+ public void addListener(WorldDataListener e) {
+ listeners.add(e);
+ }
+
+ public void removeListener(WorldDataListener o) {
+ listeners.remove(o);
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/GravityModel.java b/src/main/java/ru/windcorp/progressia/common/world/GravityModel.java
new file mode 100644
index 0000000..917d45f
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/GravityModel.java
@@ -0,0 +1,230 @@
+/*
+ * 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;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+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, 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
+ */
+public abstract class GravityModel extends Namespaced {
+
+ public GravityModel(String id) {
+ super(id);
+ }
+
+ /**
+ * Computes the vector of gravitational acceleration at the provided
+ * location.
+ *
+ * @param pos the position to compute gravity at
+ * @param output a {@link Vec3} where the result is stored. May be
+ * {@code null}.
+ * @return the vector of gravitational acceleration. The returned object
+ * will match {@code output} parameter is it is non-null.
+ */
+ public Vec3 getGravity(Vec3 pos, Vec3 output) {
+ Objects.requireNonNull(pos, "pos");
+
+ if (output == null) {
+ output = new Vec3();
+ }
+
+ try {
+ doGetGravity(pos, output);
+ } catch (Exception e) {
+ throw CrashReports.report(e, "%s failed to compute gravity at (%d; %d; %d)", this, pos.x, pos.y, pos.z);
+ }
+
+ return output;
+ }
+
+ /**
+ * Computes the up direction at the provided location. Up vector is defined
+ * as the additive inverse of the normalized gravitational acceleration
+ * vector or {@code (0; 0; 0)} if there is no gravity.
+ *
+ * @param pos the position to compute up vector at
+ * @param output a {@link Vec3} where the result is stored. May be
+ * {@code null}.
+ * @return the up vector. The returned object will match {@code output}
+ * parameter is it is non-null.
+ */
+ public Vec3 getUp(Vec3 pos, Vec3 output) {
+ output = getGravity(pos, output);
+
+ 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
+ * methods in this class.
+ *
+ * @param pos the position to compute gravity at
+ * @param output a {@link Vec3} where the result must be stored. Never
+ * {@code null}.
+ */
+ 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);
+
+ /**
+ * Parses the settings from the provided {@link DataInput} and configures this object appropriately. This method will not necessarily exhaust the input.
+ * @param input a stream to read the settings from
+ * @throws IOException if an I/O error occurs
+ * @throws DecodingException if the settings could not be parsed from input
+ */
+ public void readSettings(DataInput input) throws IOException, DecodingException {
+ Objects.requireNonNull(input, "input");
+
+ try {
+ doReadSettings(input);
+ } catch (IOException | DecodingException e) {
+ throw e;
+ } catch (Exception e) {
+ throw CrashReports.report(
+ e,
+ "%s failed to read its settings",
+ this
+ );
+ }
+ }
+
+ /**
+ * Encodes the settings of this model into the provided {@link DataOutput}.
+ * @param output a stream to write the settings into
+ * @throws IOException if an I/O error occurs
+ */
+ public void writeSettings(DataOutput output) throws IOException {
+ Objects.requireNonNull(output, "output");
+
+ try {
+ doWriteSettings(output);
+ } catch (IOException e) {
+ throw e;
+ } catch (Exception e) {
+ throw CrashReports.report(
+ e,
+ "%s failed to write its settings",
+ this
+ );
+ }
+ }
+
+ /**
+ * Parses the settings from the provided {@link DataInput} and configures this object appropriately. This method will not necessarily exhaust the input.
+ * @param input a stream to read the settings from
+ * @throws IOException if an I/O error occurs
+ * @throws DecodingException if the settings could not be parsed from input
+ */
+ protected abstract void doReadSettings(DataInput input) throws IOException, DecodingException;
+
+ /**
+ * Encodes the settings of this model into the provided {@link DataOutput}.
+ * @param output a stream to write the settings into
+ * @throws IOException if an I/O error occurs
+ */
+ protected abstract void doWriteSettings(DataOutput output) throws IOException;
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/GravityModelRegistry.java b/src/main/java/ru/windcorp/progressia/common/world/GravityModelRegistry.java
new file mode 100644
index 0000000..776e04f
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/GravityModelRegistry.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+import ru.windcorp.progressia.common.util.namespaces.NamespacedFactoryRegistry;
+
+public class GravityModelRegistry extends NamespacedFactoryRegistry {
+
+ public static final GravityModelRegistry INSTANCE = new GravityModelRegistry();
+
+ public static GravityModelRegistry getInstance() {
+ return INSTANCE;
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/PacketAffectWorld.java b/src/main/java/ru/windcorp/progressia/common/world/PacketAffectWorld.java
index 1432f1c..64f3257 100644
--- a/src/main/java/ru/windcorp/progressia/common/world/PacketAffectWorld.java
+++ b/src/main/java/ru/windcorp/progressia/common/world/PacketAffectWorld.java
@@ -26,6 +26,6 @@ public abstract class PacketAffectWorld extends Packet {
super(id);
}
- public abstract void apply(WorldData world);
+ public abstract void apply(DefaultWorldData world);
}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/PacketRevokeChunk.java b/src/main/java/ru/windcorp/progressia/common/world/PacketRevokeChunk.java
index c1ab7e3..01e8df9 100644
--- a/src/main/java/ru/windcorp/progressia/common/world/PacketRevokeChunk.java
+++ b/src/main/java/ru/windcorp/progressia/common/world/PacketRevokeChunk.java
@@ -53,9 +53,9 @@ public class PacketRevokeChunk extends PacketAffectChunk {
}
@Override
- public void apply(WorldData world) {
+ public void apply(DefaultWorldData world) {
synchronized (world) {
- ChunkData chunk = world.getChunk(position);
+ DefaultChunkData chunk = world.getChunk(position);
if (chunk != null) {
world.removeChunk(chunk);
}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/PacketSendChunk.java b/src/main/java/ru/windcorp/progressia/common/world/PacketSendChunk.java
index 12a78c8..2615b94 100644
--- a/src/main/java/ru/windcorp/progressia/common/world/PacketSendChunk.java
+++ b/src/main/java/ru/windcorp/progressia/common/world/PacketSendChunk.java
@@ -41,7 +41,7 @@ public class PacketSendChunk extends PacketAffectChunk {
super(id);
}
- public void set(ChunkData chunk) {
+ public void set(DefaultChunkData chunk) {
this.position.set(chunk.getX(), chunk.getY(), chunk.getZ());
try {
@@ -67,7 +67,7 @@ public class PacketSendChunk extends PacketAffectChunk {
}
@Override
- public void apply(WorldData world) {
+ public void apply(DefaultWorldData world) {
try {
world.addChunk(ChunkIO.load(world, position, data.getReader(), IOContext.COMMS));
} catch (DecodingException | IOException e) {
diff --git a/src/main/java/ru/windcorp/progressia/common/world/PacketSetGravityModel.java b/src/main/java/ru/windcorp/progressia/common/world/PacketSetGravityModel.java
new file mode 100644
index 0000000..454a1fa
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/PacketSetGravityModel.java
@@ -0,0 +1,76 @@
+/*
+ * 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;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+import ru.windcorp.progressia.common.util.DataBuffer;
+import ru.windcorp.progressia.common.util.crash.CrashReports;
+
+public class PacketSetGravityModel extends PacketAffectWorld {
+
+ private String gravityModelId;
+ private final DataBuffer settings = new DataBuffer();
+
+ public PacketSetGravityModel() {
+ this("Core:SetGravityModel");
+ }
+
+ protected PacketSetGravityModel(String id) {
+ super(id);
+ }
+
+ public void set(GravityModel model) {
+ this.gravityModelId = model.getId();
+
+ try {
+ model.writeSettings(settings.getWriter());
+ } catch (IOException e) {
+ throw CrashReports.report(e, "%s has errored when writing its settings", model);
+ }
+ }
+
+ @Override
+ public void read(DataInput input) throws IOException, DecodingException {
+ gravityModelId = input.readUTF();
+ settings.fill(input, input.readInt());
+ }
+
+ @Override
+ public void write(DataOutput output) throws IOException {
+ output.writeUTF(gravityModelId);
+ output.writeInt(settings.getSize());
+ settings.flush(output);
+ }
+
+ @Override
+ public void apply(DefaultWorldData world) {
+ GravityModel model = GravityModelRegistry.getInstance().create(gravityModelId);
+ world.setGravityModel(model);
+ try {
+ model.readSettings(settings.getReader());
+ } catch (IOException e) {
+ throw CrashReports.report(e, "%s has errored when reading its settings", model);
+ } catch (DecodingException e) {
+ throw CrashReports.report(e, "%s has failed to parse its settings", model);
+ }
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/TileDataReference.java b/src/main/java/ru/windcorp/progressia/common/world/TileDataReference.java
new file mode 100644
index 0000000..ccc118b
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/TileDataReference.java
@@ -0,0 +1,10 @@
+package ru.windcorp.progressia.common.world;
+
+import ru.windcorp.progressia.common.world.block.BlockData;
+import ru.windcorp.progressia.common.world.generic.TileGenericReferenceWO;
+import ru.windcorp.progressia.common.world.tile.TileData;
+
+public interface TileDataReference extends TileDataReferenceRO,
+ TileGenericReferenceWO {
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/TileDataReferenceRO.java b/src/main/java/ru/windcorp/progressia/common/world/TileDataReferenceRO.java
new file mode 100644
index 0000000..59257ec
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/TileDataReferenceRO.java
@@ -0,0 +1,12 @@
+package ru.windcorp.progressia.common.world;
+
+import ru.windcorp.progressia.common.world.block.BlockData;
+import ru.windcorp.progressia.common.world.generic.TileGenericReferenceRO;
+import ru.windcorp.progressia.common.world.tile.TileData;
+
+public interface TileDataReferenceRO
+ extends TileGenericReferenceRO {
+
+ // currently empty
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/TileDataStack.java b/src/main/java/ru/windcorp/progressia/common/world/TileDataStack.java
new file mode 100644
index 0000000..0c17ae4
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/TileDataStack.java
@@ -0,0 +1,25 @@
+package ru.windcorp.progressia.common.world;
+
+import ru.windcorp.progressia.common.world.block.BlockData;
+import ru.windcorp.progressia.common.world.generic.TileGenericStackWO;
+import ru.windcorp.progressia.common.world.tile.TileData;
+
+public interface TileDataStack
+ extends TileDataStackRO, TileGenericStackWO {
+
+ @Override
+ default boolean isFull() {
+ return TileDataStackRO.super.isFull();
+ }
+
+ /*
+ * Method specialization
+ */
+
+ @Override
+ TileDataReference getReference(int index);
+
+ @Override
+ ChunkData getChunk();
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/TileDataStackRO.java b/src/main/java/ru/windcorp/progressia/common/world/TileDataStackRO.java
new file mode 100644
index 0000000..ca74bc4
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/TileDataStackRO.java
@@ -0,0 +1,12 @@
+package ru.windcorp.progressia.common.world;
+
+import ru.windcorp.progressia.common.world.block.BlockData;
+import ru.windcorp.progressia.common.world.generic.TileGenericStackRO;
+import ru.windcorp.progressia.common.world.tile.TileData;
+
+public interface TileDataStackRO
+ extends TileGenericStackRO {
+
+ // currently empty
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/WorldData.java b/src/main/java/ru/windcorp/progressia/common/world/WorldData.java
index 8b8db6b..65f13be 100644
--- a/src/main/java/ru/windcorp/progressia/common/world/WorldData.java
+++ b/src/main/java/ru/windcorp/progressia/common/world/WorldData.java
@@ -1,204 +1,52 @@
-/*
- * 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;
-import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
-import java.util.Objects;
-import java.util.function.Consumer;
import glm.vec._3.i.Vec3i;
-import gnu.trove.TCollections;
-import gnu.trove.map.TLongObjectMap;
-import gnu.trove.map.hash.TLongObjectHashMap;
-import gnu.trove.set.TLongSet;
-import ru.windcorp.progressia.common.collision.CollisionModel;
import ru.windcorp.progressia.common.world.block.BlockData;
import ru.windcorp.progressia.common.world.entity.EntityData;
-import ru.windcorp.progressia.common.world.generic.ChunkMap;
-import ru.windcorp.progressia.common.world.generic.ChunkSet;
-import ru.windcorp.progressia.common.world.generic.GenericWorld;
-import ru.windcorp.progressia.common.world.generic.LongBasedChunkMap;
+import ru.windcorp.progressia.common.world.generic.WorldGenericWO;
+import ru.windcorp.progressia.common.world.rels.BlockFace;
import ru.windcorp.progressia.common.world.tile.TileData;
-import ru.windcorp.progressia.common.world.tile.TileDataStack;
-public class WorldData implements GenericWorld {
-
- private final ChunkMap chunksByPos = new LongBasedChunkMap<>(
- TCollections.synchronizedMap(new TLongObjectHashMap<>()));
-
- private final Collection chunks = Collections.unmodifiableCollection(chunksByPos.values());
-
- private final TLongObjectMap entitiesById = TCollections.synchronizedMap(new TLongObjectHashMap<>());
-
- private final Collection entities = Collections.unmodifiableCollection(entitiesById.valueCollection());
-
- private float time = 0;
-
- private final Collection listeners = Collections.synchronizedCollection(new ArrayList<>());
-
- public WorldData() {
-
- }
+public interface WorldData
+ extends WorldDataRO, WorldGenericWO {
@Override
- public ChunkData getChunk(Vec3i pos) {
- return chunksByPos.get(pos);
+ default TileDataStack getTiles(Vec3i blockInWorld, BlockFace face) {
+ return (TileDataStack) WorldDataRO.super.getTiles(blockInWorld, face);
}
+ /**
+ * Increases in-game time of this world by {@code change}. Total time is
+ * decreased when {@code change} is negative.
+ *
+ * @param change the amount of time to add to current world time. May be
+ * negative.
+ * @see #getTime()
+ */
+ void advanceTime(float change);
+
+ /*
+ * Method specialization
+ */
+
@Override
- public Collection getChunks() {
- return chunks;
- }
-
- public ChunkSet getLoadedChunks() {
- return chunksByPos.keys();
- }
-
+ ChunkData getChunk(Vec3i pos);
+
@Override
- public Collection getEntities() {
- return entities;
- }
-
+ Collection extends ChunkData> getChunks();
+
+ // TODO: rename WGRO.forEachChunk -> forEachChunkRO and define WGWO.forEachChunk
+
@Override
- public void forEachEntity(Consumer super EntityData> action) {
- synchronized (entitiesById) { // TODO HORRIBLY MUTILATE THE CORPSE OF
- // TROVE4J so that
- // gnu.trove.impl.sync.SynchronizedCollection.forEach
- // is synchronized
- getEntities().forEach(action);
- }
+ default ChunkData getChunkByBlock(Vec3i blockInWorld) {
+ return (ChunkData) WorldDataRO.super.getChunkByBlock(blockInWorld);
}
-
- public TLongSet getLoadedEntities() {
- return entitiesById.keySet();
- }
-
- private void addChunkListeners(ChunkData chunk) {
- getListeners().forEach(l -> l.getChunkListeners(this, chunk.getPosition(), chunk::addListener));
- }
-
- public synchronized void addChunk(ChunkData chunk) {
- addChunkListeners(chunk);
-
- ChunkData previous = chunksByPos.get(chunk);
- if (previous != null) {
- throw new IllegalArgumentException(String.format("Chunk at (%d; %d; %d) already exists",
- chunk.getPosition().x, chunk.getPosition().y, chunk.getPosition().z));
- }
-
- chunksByPos.put(chunk, chunk);
-
- chunk.onLoaded();
- getListeners().forEach(l -> l.onChunkLoaded(this, chunk));
- }
-
- public synchronized void removeChunk(ChunkData chunk) {
- getListeners().forEach(l -> l.beforeChunkUnloaded(this, chunk));
- chunk.beforeUnloaded();
-
- chunksByPos.remove(chunk);
- }
-
- public void setBlock(Vec3i blockInWorld, BlockData block, boolean notify) {
- ChunkData chunk = getChunkByBlock(blockInWorld);
- if (chunk == null)
- throw new IllegalCoordinatesException("Coordinates " + "(" + blockInWorld.x + "; " + blockInWorld.y + "; "
- + blockInWorld.z + ") " + "do not belong to a loaded chunk");
-
- chunk.setBlock(Coordinates.convertInWorldToInChunk(blockInWorld, null), block, notify);
- }
-
- public EntityData getEntity(long entityId) {
- return entitiesById.get(entityId);
- }
-
- public void addEntity(EntityData entity) {
- Objects.requireNonNull(entity, "entity");
-
- EntityData previous = entitiesById.putIfAbsent(entity.getEntityId(), entity);
-
- if (previous != null) {
- String message = "Cannot add entity " + entity + ": ";
-
- if (previous == entity) {
- message += "already present";
- } else {
- message += "entity with the same EntityID already present (" + previous + ")";
- }
-
- throw new IllegalStateException(message);
- }
-
- getListeners().forEach(l -> l.onEntityAdded(this, entity));
- }
-
- public void removeEntity(long entityId) {
- synchronized (entitiesById) {
- EntityData entity = entitiesById.get(entityId);
-
- if (entity == null) {
- throw new IllegalArgumentException(
- "Entity with EntityID " + EntityData.formatEntityId(entityId) + " not present");
- } else {
- removeEntity(entity);
- }
- }
- }
-
- public void removeEntity(EntityData entity) {
- Objects.requireNonNull(entity, "entity");
-
- getListeners().forEach(l -> l.beforeEntityRemoved(this, entity));
- entitiesById.remove(entity.getEntityId());
- }
-
- public float getTime() {
- return time;
- }
-
- public void advanceTime(float change) {
- this.time += change;
- }
-
- public CollisionModel getCollisionModelOfBlock(Vec3i blockInWorld) {
- ChunkData chunk = getChunkByBlock(blockInWorld);
- if (chunk == null)
- return null;
-
- BlockData block = chunk.getBlock(Coordinates.convertInWorldToInChunk(blockInWorld, null));
- if (block == null)
- return null;
- return block.getCollisionModel();
- }
-
- public Collection getListeners() {
- return listeners;
- }
-
- public void addListener(WorldDataListener e) {
- listeners.add(e);
- }
-
- public void removeListener(WorldDataListener o) {
- listeners.remove(o);
+
+ @Override
+ default TileDataStack getTilesOrNull(Vec3i blockInWorld, BlockFace face) {
+ return (TileDataStack) WorldDataRO.super.getTilesOrNull(blockInWorld, face);
}
}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/WorldDataListener.java b/src/main/java/ru/windcorp/progressia/common/world/WorldDataListener.java
index 875bb93..0949566 100644
--- a/src/main/java/ru/windcorp/progressia/common/world/WorldDataListener.java
+++ b/src/main/java/ru/windcorp/progressia/common/world/WorldDataListener.java
@@ -15,7 +15,7 @@
* 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;
import java.util.function.Consumer;
@@ -26,67 +26,58 @@ import ru.windcorp.progressia.common.world.entity.EntityData;
public interface WorldDataListener {
/**
- * Invoked when a new {@link ChunkData} instance is created. This method
- * should be used to add {@link ChunkDataListener}s to a new chunk. When
- * listeners are added with this method, their
- * {@link ChunkDataListener#onChunkLoaded(ChunkData) onChunkLoaded} methods
- * will be invoked.
+ * Invoked when a new {@link DefaultChunkData} instance is created. This method
+ * should be used to add
+ * {@link ChunkDataListener}s to a new chunk. When listeners are added with
+ * this method,
+ * their {@link ChunkDataListener#onChunkLoaded(DefaultChunkData) onChunkLoaded}
+ * methods will be invoked.
*
- * @param world
- * the world instance
- * @param chunk
- * the {@linkplain Coordinates#chunk coordinates of chunk} of the
- * chunk about to load
- * @param chunkListenerSink
- * a sink for listeners. All listeners passed to its
- * {@link Consumer#accept(Object) accept} method will be added to
- * the chunk.
+ * @param world the world instance
+ * @param chunk the {@linkplain Coordinates#chunk coordinates of
+ * chunk} of the chunk about to load
+ * @param chunkListenerSink a sink for listeners. All listeners passed to
+ * its
+ * {@link Consumer#accept(Object) accept} method
+ * will be added to the chunk.
*/
- default void getChunkListeners(WorldData world, Vec3i chunk, Consumer chunkListenerSink) {
+ default void getChunkListeners(DefaultWorldData world, Vec3i chunk, Consumer chunkListenerSink) {
}
/**
* Invoked whenever a {@link Chunk} has been loaded.
*
- * @param world
- * the world instance
- * @param chunk
- * the chunk that has loaded
+ * @param world the world instance
+ * @param chunk the chunk that has loaded
*/
- default void onChunkLoaded(WorldData world, ChunkData chunk) {
+ default void onChunkLoaded(DefaultWorldData world, DefaultChunkData chunk) {
}
/**
* Invoked whenever a {@link Chunk} is about to be unloaded.
*
- * @param world
- * the world instance
- * @param chunk
- * the chunk that is going to be unloaded
+ * @param world the world instance
+ * @param chunk the chunk that is going to be unloaded
*/
- default void beforeChunkUnloaded(WorldData world, ChunkData chunk) {
+ default void beforeChunkUnloaded(DefaultWorldData world, DefaultChunkData chunk) {
}
/**
* Invoked whenever an {@link EntityData} has been added.
*
- * @param world
- * the world instance
- * @param entity
- * the entity that has been added
+ * @param world the world instance
+ * @param entity the entity that has been added
*/
- default void onEntityAdded(WorldData world, EntityData entity) {
+ default void onEntityAdded(DefaultWorldData world, EntityData entity) {
}
/**
* Invoked whenever an {@link EntityData} is about to be removed.
*
- * @param world
- * the world instance
- * @param entity
- * the entity that is going to be removed
+ * @param world the world instance
+ * @param entity the entity that is going to be removed
*/
- default void beforeEntityRemoved(WorldData world, EntityData entity) {
+ default void beforeEntityRemoved(DefaultWorldData world, EntityData entity) {
}
}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/WorldDataRO.java b/src/main/java/ru/windcorp/progressia/common/world/WorldDataRO.java
new file mode 100644
index 0000000..0fdfcbc
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/WorldDataRO.java
@@ -0,0 +1,29 @@
+package ru.windcorp.progressia.common.world;
+
+import ru.windcorp.progressia.common.world.block.BlockData;
+import ru.windcorp.progressia.common.world.entity.EntityData;
+import ru.windcorp.progressia.common.world.generic.WorldGenericRO;
+import ru.windcorp.progressia.common.world.tile.TileData;
+
+public interface WorldDataRO
+ extends WorldGenericRO {
+
+ /**
+ * Returns in-world time since creation. World time is zero before and
+ * during first tick.
+ *
+ * Game logic should assume that this value mostly increases uniformly.
+ * However, it is not guaranteed that in-world time always increments.
+ *
+ * @return time, in in-game seconds, since the world was created
+ */
+ float getTime();
+
+ /**
+ * Gets the {@link GravityModel} used by this world.
+ *
+ * @return the gravity model
+ */
+ GravityModel getGravityModel();
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/block/BlockData.java b/src/main/java/ru/windcorp/progressia/common/world/block/BlockData.java
index 2c9d352..82b0543 100644
--- a/src/main/java/ru/windcorp/progressia/common/world/block/BlockData.java
+++ b/src/main/java/ru/windcorp/progressia/common/world/block/BlockData.java
@@ -21,9 +21,9 @@ package ru.windcorp.progressia.common.world.block;
import ru.windcorp.progressia.common.collision.AABB;
import ru.windcorp.progressia.common.collision.CollisionModel;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
-import ru.windcorp.progressia.common.world.generic.GenericBlock;
+import ru.windcorp.progressia.common.world.generic.BlockGeneric;
-public class BlockData extends Namespaced implements GenericBlock {
+public class BlockData extends Namespaced implements BlockGeneric {
public BlockData(String id) {
super(id);
diff --git a/src/main/java/ru/windcorp/progressia/common/world/block/BlockFace.java b/src/main/java/ru/windcorp/progressia/common/world/block/BlockFace.java
deleted file mode 100644
index 741a4d8..0000000
--- a/src/main/java/ru/windcorp/progressia/common/world/block/BlockFace.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * 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.block;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-
-import glm.vec._3.i.Vec3i;
-
-public final class BlockFace extends BlockRelation {
-
- public static final BlockFace TOP = new BlockFace(0, 0, +1, true, "TOP"),
- BOTTOM = new BlockFace(0, 0, -1, false, "BOTTOM"), NORTH = new BlockFace(+1, 0, 0, true, "NORTH"),
- SOUTH = new BlockFace(-1, 0, 0, false, "SOUTH"), WEST = new BlockFace(0, +1, 0, false, "WEST"),
- EAST = new BlockFace(0, -1, 0, true, "EAST");
-
- private static final ImmutableList ALL_FACES = ImmutableList.of(TOP, BOTTOM, NORTH, SOUTH, WEST, EAST);
-
- static {
- link(TOP, BOTTOM);
- link(NORTH, SOUTH);
- link(WEST, EAST);
- }
-
- private static final ImmutableList PRIMARY_FACES = ALL_FACES.stream().filter(BlockFace::isPrimary)
- .collect(ImmutableList.toImmutableList());
-
- private static final ImmutableList SECONDARY_FACES = ALL_FACES.stream().filter(BlockFace::isSecondary)
- .collect(ImmutableList.toImmutableList());
-
- public static final int BLOCK_FACE_COUNT = ALL_FACES.size();
- public static final int PRIMARY_BLOCK_FACE_COUNT = PRIMARY_FACES.size();
- public static final int SECONDARY_BLOCK_FACE_COUNT = SECONDARY_FACES.size();
-
- public static ImmutableList getFaces() {
- return ALL_FACES;
- }
-
- public static ImmutableList getPrimaryFaces() {
- return PRIMARY_FACES;
- }
-
- public static ImmutableList getSecondaryFaces() {
- return SECONDARY_FACES;
- }
-
- private static void link(BlockFace a, BlockFace b) {
- a.counterFace = b;
- b.counterFace = a;
- }
-
- public static ImmutableMap mapToFaces(E top, E bottom, E north, E south, E east, E west) {
- return ImmutableMap.builderWithExpectedSize(6).put(TOP, top).put(BOTTOM, bottom).put(NORTH, north)
- .put(SOUTH, south).put(EAST, east).put(WEST, west).build();
- }
-
- private static int nextId = 0;
-
- private final int id;
- private final String name;
- private BlockFace counterFace;
- private final boolean isPrimary;
-
- private BlockFace(int x, int y, int z, boolean isPrimary, String name) {
- super(x, y, z);
- this.id = nextId++;
- this.isPrimary = isPrimary;
- this.name = name;
- }
-
- public String getName() {
- return name;
- }
-
- public boolean isPrimary() {
- return isPrimary;
- }
-
- public BlockFace getPrimary() {
- if (isPrimary)
- return this;
- else
- return counterFace;
- }
-
- public BlockFace getPrimaryAndMoveCursor(Vec3i cursor) {
- if (isPrimary)
- return this;
-
- cursor.add(getVector());
- return counterFace;
- }
-
- public boolean isSecondary() {
- return !isPrimary;
- }
-
- public BlockFace getSecondary() {
- if (isPrimary)
- return counterFace;
- else
- return this;
- }
-
- public BlockFace getSecondaryAndMoveCursor(Vec3i cursor) {
- if (!isPrimary)
- return this;
-
- cursor.add(getVector());
- return counterFace;
- }
-
- public BlockFace getCounter() {
- return counterFace;
- }
-
- public BlockFace getCounterAndMoveCursor(Vec3i cursor) {
- cursor.add(getVector());
- return counterFace;
- }
-
- public int getId() {
- return id;
- }
-
- @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/block/PacketSetBlock.java b/src/main/java/ru/windcorp/progressia/common/world/block/PacketSetBlock.java
index a29fa2e..11e4291 100644
--- a/src/main/java/ru/windcorp/progressia/common/world/block/PacketSetBlock.java
+++ b/src/main/java/ru/windcorp/progressia/common/world/block/PacketSetBlock.java
@@ -24,7 +24,7 @@ import java.io.IOException;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.DecodingException;
-import ru.windcorp.progressia.common.world.WorldData;
+import ru.windcorp.progressia.common.world.DefaultWorldData;
public class PacketSetBlock extends PacketAffectBlock {
@@ -60,7 +60,7 @@ public class PacketSetBlock extends PacketAffectBlock {
}
@Override
- public void apply(WorldData world) {
+ public void apply(DefaultWorldData world) {
BlockData block = BlockDataRegistry.getInstance().get(getBlockId());
world.setBlock(getBlockInWorld(), block, true);
}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/context/BlockDataContext.java b/src/main/java/ru/windcorp/progressia/common/world/context/BlockDataContext.java
new file mode 100644
index 0000000..e535014
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/context/BlockDataContext.java
@@ -0,0 +1,62 @@
+/*
+ * 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.context;
+
+import glm.vec._3.i.Vec3i;
+import ru.windcorp.progressia.common.world.block.BlockData;
+import ru.windcorp.progressia.common.world.entity.EntityData;
+import ru.windcorp.progressia.common.world.generic.context.BlockGenericContextWO;
+import ru.windcorp.progressia.common.world.rels.RelFace;
+import ru.windcorp.progressia.common.world.rels.RelRelation;
+import ru.windcorp.progressia.common.world.tile.TileData;
+
+public interface BlockDataContext
+ extends BlockGenericContextWO,
+ WorldDataContext,
+ BlockDataContextRO {
+
+ /*
+ * Subcontexting
+ */
+
+ @Override
+ default BlockDataContext pushRelative(int dx, int dy, int dz) {
+ return push(getLocation().add_(dx, dy, dz));
+ }
+
+ @Override
+ default BlockDataContext pushRelative(Vec3i direction) {
+ return push(getLocation().add_(direction));
+ }
+
+ @Override
+ default BlockDataContext pushRelative(RelRelation direction) {
+ return push(getLocation().add_(direction.getRelVector()));
+ }
+
+ @Override
+ default TileStackDataContext push(RelFace face) {
+ return push(getLocation(), face);
+ }
+
+ @Override
+ default TileDataContext push(RelFace face, int layer) {
+ return push(getLocation(), face, layer);
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/context/BlockDataContextRO.java b/src/main/java/ru/windcorp/progressia/common/world/context/BlockDataContextRO.java
new file mode 100644
index 0000000..621cc9a
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/context/BlockDataContextRO.java
@@ -0,0 +1,61 @@
+/*
+ * 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.context;
+
+import glm.vec._3.i.Vec3i;
+import ru.windcorp.progressia.common.world.block.BlockData;
+import ru.windcorp.progressia.common.world.entity.EntityData;
+import ru.windcorp.progressia.common.world.generic.context.BlockGenericContextRO;
+import ru.windcorp.progressia.common.world.rels.RelFace;
+import ru.windcorp.progressia.common.world.rels.RelRelation;
+import ru.windcorp.progressia.common.world.tile.TileData;
+
+public interface BlockDataContextRO
+ extends BlockGenericContextRO,
+ WorldDataContextRO {
+
+ /*
+ * Subcontexting
+ */
+
+ @Override
+ default BlockDataContextRO pushRelative(int dx, int dy, int dz) {
+ return push(getLocation().add_(dx, dy, dz));
+ }
+
+ @Override
+ default BlockDataContextRO pushRelative(Vec3i direction) {
+ return push(getLocation().add_(direction));
+ }
+
+ @Override
+ default BlockDataContextRO pushRelative(RelRelation direction) {
+ return push(getLocation().add_(direction.getRelVector()));
+ }
+
+ @Override
+ default TileStackDataContextRO push(RelFace face) {
+ return push(getLocation(), face);
+ }
+
+ @Override
+ default TileDataContextRO push(RelFace face, int layer) {
+ return push(getLocation(), face, layer);
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/context/Context.java b/src/main/java/ru/windcorp/progressia/common/world/context/Context.java
new file mode 100644
index 0000000..786e1eb
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/context/Context.java
@@ -0,0 +1,159 @@
+/*
+ * 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.context;
+
+import ru.windcorp.progressia.common.world.generic.context.AbstractContextRO;
+
+/**
+ * A cursor-like object for retrieving information about an in-game environment.
+ * A context object typically holds a reference to some sort of data structure
+ * and a cursor pointing to a location in that data structure. The exact meaning
+ * of "environment" and "location" is defined by extending interfaces. The terms
+ * relevant and implied should be understood to refer to the
+ * aforementioned location.
+ *
+ * Context objects are intended to be the primary way of interacting for in-game
+ * content. Wherever possible, context objects should be preferred over other
+ * means of accessing game structures.
+ *
Context Validity
+ * Context objects may only be used while they are valid to avoid undefined
+ * behavior. There exists no programmatic way to determine a context's validity;
+ * it is the responsibility of the programmer to avoid interacting with invalid
+ * contexts.
+ *
+ * Contexts are usually acquired as method parameters. Unless stated otherwise,
+ * the context is valid until the invoked method returns; the only exception to
+ * this rule is subcontexting (see below). Consequently, contexts should never
+ * be stored outside their intended methods.
+ *
+ * In practice, context objects are typically highly volatile. They are not
+ * thread-safe and are often pooled and reused.
+ *
+ *
Subcontexting
+ * Context objects allow subcontexting. Subcontexting is the temporary
+ * modification of the context object. Contexts use a stack approach to
+ * modification: all modifications must be reverted in the reversed order they
+ * were applied.
+ *
+ * Modification methods are usually named {@code pushXXX}. To revert
+ * the most recent non-reverted modification, use {@link #pop()}. As a general
+ * rule, a method that is given a context must always {@link #pop()} every
+ * change it has pushed. Failure to abide by this contract results in bugs that
+ * is difficult to trace.
+ *
+ * Although various push methods declare differing result types, the same object
+ * is always returned:
+ *
+ *
+ * someContext.pushXXX() == someContext
+ *
+ *
+ * Therefore invoking {@link #pop()} is valid using both the original reference
+ * and the obtained reference.
+ *
Subcontexting example
+ * Given a {@link ru.windcorp.progressia.common.world.context.BlockDataContext
+ * BlockDataContext} {@code a} one can process the tile stack on the top of the
+ * relevant block by using
+ *
+ *
+ *
+ * Notice that {@code a.pop()} and {@code b.pop()} are interchangeable.
+ *
+ * @see AbstractContextRO
+ * @author javapony
+ */
+public interface Context {
+
+ /**
+ * Tests whether the environment is "real". Any actions carried out in an
+ * environment that is not "real" should not have any side effects outside
+ * of the environment.
+ *
+ * A typical "real" environment is the world of the client that is actually
+ * displayed or a world of the server that the clients actually interact
+ * with. An example of a non-"real" environment is a fake world used by
+ * developer tools to query the properties or behaviors of in-game content.
+ * While in-game events may well trigger global-scope actions, such as
+ * writing files, this may become an unintended or even harmful byproduct in
+ * some scenarios that are not actually linked to an actual in-game world.
+ *
+ * This flag should generally only be consulted before taking action through
+ * means other than a provided changer object. The interactions with the
+ * context should otherwise remain unaltered.
+ *
+ * When querying game content for purposes other than directly applying
+ * results in-game, {@code isReal()} should return {@code false}. In all
+ * other cases, where possible, the call should be delegated to a provided
+ * context object.
+ *
+ * @return {@code false} iff side effects outside the environment should be
+ * suppressed
+ */
+ boolean isReal();
+
+ /**
+ * Reverts the more recent modification to this object that has not been
+ * reverted yet.
+ *
+ * Context objects may be modified temporarily with various push methods
+ * (see subcontexting). To revert the most
+ * recent non-reverted modification, use {@link #pop()}. As a general rule,
+ * a method that is given a context must always {@link #pop()} every change
+ * it has pushed. Failure to abide by this contract results in bugs that is
+ * difficult to trace.
+ *
+ * This method may be invoked using either the original reference or the
+ * reference provided by push method.
+ *
+ * This method fails with an {@link IllegalStateException} when there are no
+ * modifications to revert.
+ */
+ void pop();
+
+ default T popAndReturn(T result) {
+ pop();
+ return result;
+ }
+
+ default boolean popAndReturn(boolean result) {
+ pop();
+ return result;
+ }
+
+ default int popAndReturn(int result) {
+ pop();
+ return result;
+ }
+
+ default float popAndReturn(float result) {
+ pop();
+ return result;
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/context/TileDataContext.java b/src/main/java/ru/windcorp/progressia/common/world/context/TileDataContext.java
new file mode 100644
index 0000000..b424b2f
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/context/TileDataContext.java
@@ -0,0 +1,44 @@
+/*
+ * 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.context;
+
+import ru.windcorp.progressia.common.world.block.BlockData;
+import ru.windcorp.progressia.common.world.entity.EntityData;
+import ru.windcorp.progressia.common.world.generic.context.TileGenericContextWO;
+import ru.windcorp.progressia.common.world.tile.TileData;
+
+public interface TileDataContext
+ extends TileGenericContextWO,
+ TileStackDataContext,
+ TileDataContextRO {
+
+ /*
+ * Subcontexting
+ */
+
+ @Override
+ default TileDataContext pushCloser() {
+ return push(getLocation(), getFace(), getLayer() - 1);
+ }
+
+ @Override
+ default TileDataContext pushFarther() {
+ return push(getLocation(), getFace(), getLayer() + 1);
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/context/TileDataContextRO.java b/src/main/java/ru/windcorp/progressia/common/world/context/TileDataContextRO.java
new file mode 100644
index 0000000..f6979e2
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/context/TileDataContextRO.java
@@ -0,0 +1,43 @@
+/*
+ * 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.context;
+
+import ru.windcorp.progressia.common.world.block.BlockData;
+import ru.windcorp.progressia.common.world.entity.EntityData;
+import ru.windcorp.progressia.common.world.generic.context.TileGenericContextRO;
+import ru.windcorp.progressia.common.world.tile.TileData;
+
+public interface TileDataContextRO
+ extends TileGenericContextRO,
+ TileStackDataContextRO {
+
+ /*
+ * Subcontexting
+ */
+
+ @Override
+ default TileDataContextRO pushCloser() {
+ return push(getLocation(), getFace(), getLayer() - 1);
+ }
+
+ @Override
+ default TileDataContextRO pushFarther() {
+ return push(getLocation(), getFace(), getLayer() + 1);
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/context/TileStackDataContext.java b/src/main/java/ru/windcorp/progressia/common/world/context/TileStackDataContext.java
new file mode 100644
index 0000000..1751a50
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/context/TileStackDataContext.java
@@ -0,0 +1,49 @@
+/*
+ * 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.context;
+
+import ru.windcorp.progressia.common.world.block.BlockData;
+import ru.windcorp.progressia.common.world.entity.EntityData;
+import ru.windcorp.progressia.common.world.generic.context.TileStackGenericContextWO;
+import ru.windcorp.progressia.common.world.tile.TileData;
+
+public interface TileStackDataContext
+ extends TileStackGenericContextWO,
+ BlockDataContext,
+ TileStackDataContextRO {
+
+ /*
+ * Subcontexting
+ */
+
+ @Override
+ default TileDataContext push(int layer) {
+ return push(getLocation(), getFace(), layer);
+ }
+
+ @Override
+ default TileStackDataContext pushCounter() {
+ return push(getFace().getCounter());
+ }
+
+ @Override
+ default TileStackDataContext pushOpposite() {
+ return push(getLocation().add_(getFace().getRelVector()), getFace().getCounter());
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/context/TileStackDataContextRO.java b/src/main/java/ru/windcorp/progressia/common/world/context/TileStackDataContextRO.java
new file mode 100644
index 0000000..bcdb948
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/context/TileStackDataContextRO.java
@@ -0,0 +1,48 @@
+/*
+ * 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.context;
+
+import ru.windcorp.progressia.common.world.block.BlockData;
+import ru.windcorp.progressia.common.world.entity.EntityData;
+import ru.windcorp.progressia.common.world.generic.context.TileStackGenericContextRO;
+import ru.windcorp.progressia.common.world.tile.TileData;
+
+public interface TileStackDataContextRO
+ extends TileStackGenericContextRO,
+ BlockDataContextRO {
+
+ /*
+ * Subcontexting
+ */
+
+ @Override
+ default TileDataContextRO push(int layer) {
+ return push(getLocation(), getFace(), layer);
+ }
+
+ @Override
+ default TileStackDataContextRO pushCounter() {
+ return push(getFace().getCounter());
+ }
+
+ @Override
+ default TileStackDataContextRO pushOpposite() {
+ return push(getLocation().add_(getFace().getRelVector()), getFace().getCounter());
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/context/WorldDataContext.java b/src/main/java/ru/windcorp/progressia/common/world/context/WorldDataContext.java
new file mode 100644
index 0000000..9dcb0ae
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/context/WorldDataContext.java
@@ -0,0 +1,54 @@
+/*
+ * 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.context;
+
+import glm.vec._3.i.Vec3i;
+import ru.windcorp.progressia.common.world.block.BlockData;
+import ru.windcorp.progressia.common.world.entity.EntityData;
+import ru.windcorp.progressia.common.world.generic.context.WorldGenericContextWO;
+import ru.windcorp.progressia.common.world.rels.RelFace;
+import ru.windcorp.progressia.common.world.tile.TileData;
+
+public interface WorldDataContext
+ extends WorldGenericContextWO,
+ WorldDataContextRO {
+
+ /**
+ * Increases in-game time of this world by {@code change}. Total time is
+ * decreased when {@code change} is negative.
+ *
+ * @param change the amount of time to add to current world time. May be
+ * negative.
+ * @see #getTime()
+ */
+ void advanceTime(float change);
+
+ /*
+ * Subcontexting
+ */
+
+ @Override
+ BlockDataContext push(Vec3i location);
+
+ @Override
+ TileStackDataContext push(Vec3i location, RelFace face);
+
+ @Override
+ TileDataContext push(Vec3i location, RelFace face, int layer);
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/context/WorldDataContextRO.java b/src/main/java/ru/windcorp/progressia/common/world/context/WorldDataContextRO.java
new file mode 100644
index 0000000..be037bf
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/context/WorldDataContextRO.java
@@ -0,0 +1,62 @@
+/*
+ * 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.context;
+
+import glm.vec._3.i.Vec3i;
+import ru.windcorp.progressia.common.world.GravityModel;
+import ru.windcorp.progressia.common.world.block.BlockData;
+import ru.windcorp.progressia.common.world.entity.EntityData;
+import ru.windcorp.progressia.common.world.generic.context.WorldGenericContextRO;
+import ru.windcorp.progressia.common.world.rels.RelFace;
+import ru.windcorp.progressia.common.world.tile.TileData;
+
+public interface WorldDataContextRO extends WorldGenericContextRO {
+
+ /**
+ * Returns in-world time since creation. World time is zero before and
+ * during first tick.
+ *
+ * Game logic should assume that this value mostly increases uniformly.
+ * However, it is not guaranteed that in-world time always increments.
+ *
+ * @return time, in in-game seconds, since the world was created
+ */
+ float getTime();
+
+ /**
+ * Gets the {@link GravityModel} used by this world.
+ *
+ * @return the gravity model
+ */
+ GravityModel getGravityModel();
+
+ /*
+ * Subcontexting
+ */
+
+ @Override
+ BlockDataContextRO push(Vec3i location);
+
+ @Override
+ TileStackDataContextRO push(Vec3i location, RelFace face);
+
+ @Override
+ TileDataContextRO push(Vec3i location, RelFace face, int layer);
+
+}
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 3201972..bc00974 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
@@ -21,33 +21,39 @@ package ru.windcorp.progressia.common.world.entity;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
+import java.util.Objects;
-import glm.vec._2.Vec2;
+import glm.mat._3.Mat3;
import glm.vec._3.Vec3;
import ru.windcorp.jputil.chars.StringUtil;
+import ru.windcorp.progressia.common.collision.AABBRotator;
import ru.windcorp.progressia.common.collision.Collideable;
import ru.windcorp.progressia.common.collision.CollisionModel;
import ru.windcorp.progressia.common.state.IOContext;
import ru.windcorp.progressia.common.state.StatefulObject;
-import ru.windcorp.progressia.common.world.generic.GenericEntity;
+import ru.windcorp.progressia.common.util.Matrices;
+import ru.windcorp.progressia.common.world.generic.EntityGeneric;
+import ru.windcorp.progressia.common.world.rels.AbsFace;
-public class EntityData extends StatefulObject implements Collideable, GenericEntity {
+public class EntityData extends StatefulObject implements Collideable, EntityGeneric {
private final Vec3 position = new Vec3();
private final Vec3 velocity = new Vec3();
- private final Vec2 direction = new Vec2();
+ private final Vec3 lookingAt = new Vec3(1, 0, 0);
+ private final Vec3 upVector = new Vec3(0, 0, 1);
/**
* The unique {@code long} value guaranteed to never be assigned to an
- * entity as its entity ID. This can safely be used as a placeholder or a
- * sentinel value.
+ * entity as its entity ID.
+ * This can safely be used as a placeholder or a sentinel value.
*/
public static final long NULL_ENTITY_ID = 0x0000_0000_0000_0000;
private long entityId;
private CollisionModel collisionModel = null;
+ private CollisionModel rotatedCollisionModel = null;
private double age = 0;
@@ -79,22 +85,7 @@ public class EntityData extends StatefulObject implements Collideable, GenericEn
this.velocity.set(velocity);
}
- public Vec2 getDirection() {
- return direction;
- }
-
- public void setDirection(Vec2 direction) {
- this.direction.set(direction.x, direction.y);
- }
-
- public float getYaw() {
- return getDirection().x;
- }
-
- public float getPitch() {
- return getDirection().y;
- }
-
+ @Override
public long getEntityId() {
return entityId;
}
@@ -120,11 +111,16 @@ public class EntityData extends StatefulObject implements Collideable, GenericEn
@Override
public CollisionModel getCollisionModel() {
+ return rotatedCollisionModel;
+ }
+
+ public CollisionModel getOriginalCollisionModel() {
return collisionModel;
}
public void setCollisionModel(CollisionModel collisionModel) {
this.collisionModel = collisionModel;
+ this.rotatedCollisionModel = AABBRotator.rotate(this::getUpFace, this::getPosition, collisionModel);
}
@Override
@@ -152,17 +148,173 @@ public class EntityData extends StatefulObject implements Collideable, GenericEn
getVelocity().add(velocityChange);
}
- public Vec3 getLookingAtVector(Vec3 output) {
- output.set(Math.cos(getPitch()) * Math.cos(getYaw()), Math.cos(getPitch()) * Math.sin(getYaw()),
- -Math.sin(getPitch()));
+ public Vec3 getLookingAt() {
+ return lookingAt;
+ }
+ public void setLookingAt(Vec3 lookingAt) {
+ float lengthSq = lookingAt.x * lookingAt.x + lookingAt.y * lookingAt.y + lookingAt.z * lookingAt.z;
+ if (lengthSq == 1) {
+ this.lookingAt.set(lookingAt);
+ } else if (lengthSq == 0) {
+ throw new IllegalArgumentException("lookingAt is zero-length");
+ } else if (!Float.isFinite(lengthSq)) {
+ throw new IllegalArgumentException("lookingAt is not finite: " + lookingAt);
+ } else {
+ float length = (float) Math.sqrt(lengthSq);
+ this.lookingAt.set(
+ lookingAt.x / length,
+ lookingAt.y / length,
+ lookingAt.z / length
+ );
+ }
+ }
+
+ public Vec3 getUpVector() {
+ return upVector;
+ }
+
+ public AbsFace getUpFace() {
+ return AbsFace.roundToFace(getUpVector());
+ }
+
+ /**
+ * Sets this entity's up vector without updating looking at-vector.
+ *
+ * @param upVector the Vec3 to copy up vector from
+ * @see #changeUpVector(Vec3)
+ */
+ public void setUpVector(Vec3 upVector) {
+ float lengthSq = upVector.x * upVector.x + upVector.y * upVector.y + upVector.z * upVector.z;
+ if (lengthSq == 1) {
+ this.upVector.set(upVector);
+ } else if (lengthSq == 0) {
+ throw new IllegalArgumentException("upVector is zero-length");
+ } else if (!Float.isFinite(lengthSq)) {
+ throw new IllegalArgumentException("upVector is not finite: " + upVector);
+ } else {
+ float length = (float) Math.sqrt(lengthSq);
+ this.upVector.set(
+ upVector.x / length,
+ upVector.y / length,
+ upVector.z / length
+ );
+ }
+ }
+
+ /**
+ * Computes the forward vector of this entity. An entity's forward vector is
+ * defined as a normalized projection of the looking at-vector onto the
+ * plane perpendicular to up vector, or {@code (NaN; NaN; NaN)} if looking
+ * at-vector is parallel to the up vector.
+ *
+ * @param output a {@link Vec3} where the result is stored. May be
+ * {@code null}.
+ * @return the computed forward vector or {@code (NaN; NaN; NaN)}
+ */
+ public Vec3 getForwardVector(Vec3 output) {
+ if (output == null)
+ output = new Vec3();
+ output.set(getUpVector()).mul(-getUpVector().dot(getLookingAt())).add(getLookingAt()).normalize();
return output;
}
+ public double getPitch() {
+ return -Math.acos(getLookingAt().dot(getUpVector())) + Math.PI / 2;
+ }
+
+ /**
+ * Updates this entity's up vector and alters looking at-vector to match the
+ * rotation of the up vector.
+ *
+ * This method assumes that the up vector has changed due to rotation around
+ * some axis. The axis and the angle are computed, after which the same
+ * rotation is applied to the looking at-vector.
+ *
+ * @param newUpVector the Vec3 to copy up vector from. May be equal to
+ * current up vector
+ * @see #setLookingAt(Vec3)
+ */
+ public void changeUpVector(Vec3 newUpVector) {
+ Objects.requireNonNull(newUpVector, "newUpVector");
+
+ Vec3 u0 = upVector;
+ Vec3 u1 = newUpVector;
+
+ if (u1.x == 0 && u1.y == 0 && u1.z == 0) {
+ // Entering weightlessness, not changing anything
+ return;
+ }
+
+ if (u0.x == u1.x && u0.y == u1.y && u0.z == u1.z) {
+ // Nothing changed
+ return;
+ }
+
+ if (u0.x == -u1.x && u0.y == -u1.y && u0.z == -u1.z) {
+ // Welp, don't do anything stupid then
+ upVector.set(newUpVector);
+ return;
+ }
+
+ float u1LengthSq = u1.x*u1.x + u1.y*u1.y + u1.z*u1.z;
+ float u1Length = 1;
+
+ if (!Float.isFinite(u1LengthSq)) {
+ throw new IllegalArgumentException("newUpVector is not finite: " + newUpVector);
+ } else if (u1LengthSq != 1) {
+ u1Length = (float) Math.sqrt(u1LengthSq);
+ }
+
+ // u0 and u1 are now both definitely two different usable vectors
+
+ if (rotateLookingAtToMatchUpVectorRotation(u0, u1, u1Length, lookingAt)) {
+ return;
+ }
+
+ upVector.set(newUpVector).div(u1Length);
+ }
+
+ private static boolean rotateLookingAtToMatchUpVectorRotation(Vec3 u0, Vec3 u1, float u1Length, Vec3 lookingAt) {
+ // Determine rotation parameters
+ Vec3 axis = u0.cross_(u1);
+ float cos = u0.dot(u1) / u1Length;
+ float sin = axis.length() / u1Length;
+
+ if (sin == 0) {
+ return true;
+ }
+
+ axis.div(sin * u1Length); // normalize axis
+
+ float x = axis.x;
+ float y = axis.y;
+ float z = axis.z;
+
+ Mat3 matrix = Matrices.grab3();
+
+ // Don't format. @formatter:off
+ matrix.set(
+ 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
+
+ matrix.mul_(lookingAt); // bug in jglm, .mul() and .mul_() are swapped
+
+ Matrices.release(matrix);
+
+ return false;
+ }
+
@Override
public String toString() {
- return new StringBuilder(super.toString()).append(" (EntityID ").append(StringUtil.toFullHex(getEntityId()))
- .append(")").toString();
+ return new StringBuilder(super.toString())
+ .append(" (EntityID ")
+ .append(StringUtil.toFullHex(getEntityId()))
+ .append(")")
+ .toString();
}
public static String formatEntityId(long entityId) {
@@ -183,23 +335,47 @@ public class EntityData extends StatefulObject implements Collideable, GenericEn
output.writeFloat(getVelocity().y);
output.writeFloat(getVelocity().z);
- output.writeFloat(getDirection().x);
- output.writeFloat(getDirection().y);
+ output.writeFloat(getLookingAt().x);
+ output.writeFloat(getLookingAt().y);
+ output.writeFloat(getLookingAt().z);
+
+ output.writeFloat(getUpVector().x);
+ output.writeFloat(getUpVector().y);
+ output.writeFloat(getUpVector().z);
super.write(output, context);
}
@Override
public void read(DataInput input, IOContext context) throws IOException {
- Vec3 position = new Vec3(input.readFloat(), input.readFloat(), input.readFloat());
+ Vec3 position = new Vec3(
+ input.readFloat(),
+ input.readFloat(),
+ input.readFloat()
+ );
- Vec3 velocity = new Vec3(input.readFloat(), input.readFloat(), input.readFloat());
+ Vec3 velocity = new Vec3(
+ input.readFloat(),
+ input.readFloat(),
+ input.readFloat()
+ );
- Vec2 direction = new Vec2(input.readFloat(), input.readFloat());
+ Vec3 lookingAt = new Vec3(
+ input.readFloat(),
+ input.readFloat(),
+ input.readFloat()
+ );
+
+ Vec3 upVector = new Vec3(
+ input.readFloat(),
+ input.readFloat(),
+ input.readFloat()
+ );
setPosition(position);
setVelocity(velocity);
- setDirection(direction);
+ setLookingAt(lookingAt);
+ setUpVector(upVector);
super.read(input, context);
}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/entity/PacketAffectEntity.java b/src/main/java/ru/windcorp/progressia/common/world/entity/PacketAffectEntity.java
index aefd64e..b571fba 100644
--- a/src/main/java/ru/windcorp/progressia/common/world/entity/PacketAffectEntity.java
+++ b/src/main/java/ru/windcorp/progressia/common/world/entity/PacketAffectEntity.java
@@ -24,7 +24,7 @@ import java.io.IOException;
import ru.windcorp.progressia.common.world.DecodingException;
import ru.windcorp.progressia.common.world.PacketAffectWorld;
-import ru.windcorp.progressia.common.world.WorldData;
+import ru.windcorp.progressia.common.world.DefaultWorldData;
public class PacketAffectEntity extends PacketAffectWorld {
@@ -53,7 +53,7 @@ public class PacketAffectEntity extends PacketAffectWorld {
}
@Override
- public void apply(WorldData world) {
+ public void apply(DefaultWorldData world) {
world.removeEntity(this.entityId);
}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/entity/PacketChangeEntity.java b/src/main/java/ru/windcorp/progressia/common/world/entity/PacketChangeEntity.java
index 515f1f0..2d42a7a 100644
--- a/src/main/java/ru/windcorp/progressia/common/world/entity/PacketChangeEntity.java
+++ b/src/main/java/ru/windcorp/progressia/common/world/entity/PacketChangeEntity.java
@@ -26,7 +26,7 @@ import ru.windcorp.progressia.common.state.IOContext;
import ru.windcorp.progressia.common.util.DataBuffer;
import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.common.world.DecodingException;
-import ru.windcorp.progressia.common.world.WorldData;
+import ru.windcorp.progressia.common.world.DefaultWorldData;
public class PacketChangeEntity extends PacketAffectEntity {
@@ -68,7 +68,7 @@ public class PacketChangeEntity extends PacketAffectEntity {
}
@Override
- public void apply(WorldData world) {
+ public void apply(DefaultWorldData world) {
EntityData entity = world.getEntity(getEntityId());
if (entity == null) {
diff --git a/src/main/java/ru/windcorp/progressia/common/world/entity/PacketRevokeEntity.java b/src/main/java/ru/windcorp/progressia/common/world/entity/PacketRevokeEntity.java
index 98b1d88..a0ceee2 100644
--- a/src/main/java/ru/windcorp/progressia/common/world/entity/PacketRevokeEntity.java
+++ b/src/main/java/ru/windcorp/progressia/common/world/entity/PacketRevokeEntity.java
@@ -23,7 +23,7 @@ import java.io.DataOutput;
import java.io.IOException;
import ru.windcorp.progressia.common.world.DecodingException;
-import ru.windcorp.progressia.common.world.WorldData;
+import ru.windcorp.progressia.common.world.DefaultWorldData;
public class PacketRevokeEntity extends PacketAffectEntity {
@@ -51,7 +51,7 @@ public class PacketRevokeEntity extends PacketAffectEntity {
}
@Override
- public void apply(WorldData world) {
+ public void apply(DefaultWorldData world) {
world.removeEntity(getEntityId());
}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/entity/PacketSendEntity.java b/src/main/java/ru/windcorp/progressia/common/world/entity/PacketSendEntity.java
index 93d724c..71e903f 100644
--- a/src/main/java/ru/windcorp/progressia/common/world/entity/PacketSendEntity.java
+++ b/src/main/java/ru/windcorp/progressia/common/world/entity/PacketSendEntity.java
@@ -26,7 +26,7 @@ import ru.windcorp.progressia.common.state.IOContext;
import ru.windcorp.progressia.common.util.DataBuffer;
import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.common.world.DecodingException;
-import ru.windcorp.progressia.common.world.WorldData;
+import ru.windcorp.progressia.common.world.DefaultWorldData;
public class PacketSendEntity extends PacketAffectEntity {
@@ -85,7 +85,7 @@ public class PacketSendEntity extends PacketAffectEntity {
}
@Override
- public void apply(WorldData world) {
+ public void apply(DefaultWorldData world) {
EntityData entity = EntityDataRegistry.getInstance().create(getEntityTypeId());
entity.setEntityId(getEntityId());
diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/GenericBlock.java b/src/main/java/ru/windcorp/progressia/common/world/generic/BlockGeneric.java
similarity index 96%
rename from src/main/java/ru/windcorp/progressia/common/world/generic/GenericBlock.java
rename to src/main/java/ru/windcorp/progressia/common/world/generic/BlockGeneric.java
index 1d2c252..1ccd022 100644
--- a/src/main/java/ru/windcorp/progressia/common/world/generic/GenericBlock.java
+++ b/src/main/java/ru/windcorp/progressia/common/world/generic/BlockGeneric.java
@@ -18,7 +18,7 @@
package ru.windcorp.progressia.common.world.generic;
-public interface GenericBlock {
+public interface BlockGeneric {
String getId();
diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkGenericRO.java b/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkGenericRO.java
new file mode 100644
index 0000000..cd648c5
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkGenericRO.java
@@ -0,0 +1,311 @@
+/*
+ * 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.generic;
+
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+import glm.Glm;
+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.Coordinates;
+import ru.windcorp.progressia.common.world.rels.AbsFace;
+import ru.windcorp.progressia.common.world.rels.AxisRotations;
+import ru.windcorp.progressia.common.world.rels.BlockFace;
+
+/**
+ * An unmodifiable chunk representation. Per default, it is usually one of
+ * {@link ru.windcorp.progressia.common.world.DefaultChunkData ChunkData},
+ * {@link ru.windcorp.progressia.client.world.ChunkRender ChunkRender} or
+ * {@link ru.windcorp.progressia.server.world.DefaultChunkLogic ChunkLogic}, but this
+ * interface may be implemented differently for various reasons.
+ *
+ * A generic chunk contains {@linkplain BlockGeneric blocks} and
+ * {@linkplain TileGenericStackRO tile stacks} and is characterized by its
+ * location. It also bears a discrete up direction. Note that no
+ * {@linkplain WorldGenericRO world} object is directly accessible through this
+ * interface.
+ *
+ * This interface defines the most common methods for examining a chunk and
+ * implements many of them as default methods. It also contains several static
+ * methods useful when dealing with chunks. {@code GenericChunk} does not
+ * provide a way to modify a chunk; use {@link ChunkGenericWO} methods
+ * when applicable.
+ *
+ * @param a reference to itself (required to properly reference a
+ * {@link TileGenericStackRO})
+ * @param block type
+ * @param tile type
+ * @param tile stack type
+ * @author javapony
+ */
+// @formatter:off
+public interface ChunkGenericRO<
+ B extends BlockGeneric,
+ T extends TileGeneric,
+ TS extends TileGenericStackRO ,
+ TR extends TileGenericReferenceRO ,
+ C extends ChunkGenericRO
+> {
+// @formatter:on
+
+ /**
+ * The count of blocks in a side of a chunk. This is guaranteed to be a
+ * power of two. This is always equal to {@link Coordinates#CHUNK_SIZE}.
+ */
+ public static final int BLOCKS_PER_CHUNK = Coordinates.CHUNK_SIZE;
+
+ /*
+ * Abstract methods
+ */
+
+ /**
+ * Returns the position of this chunk in {@linkplain Coordinates#chunk
+ * coordinates of chunk}. The returned object must not be modified.
+ *
+ * @return this chunk's position
+ */
+ Vec3i getPosition();
+
+ /**
+ * Returns the discrete up direction for this chunk.
+ *
+ * @return this chunk's discrete up direction
+ */
+ AbsFace getUp();
+
+ /**
+ * Retrieves the block at the location specified by its
+ * {@linkplain Coordinates#blockInChunk chunk coordinates}. During chunk
+ * generation it may be {@code null}.
+ *
+ * @param blockInChunk local coordinates of the block to fetch
+ * @return the block at the requested location or {@code null}.
+ */
+ B getBlock(Vec3i blockInChunk);
+
+ TS getTiles(Vec3i blockInChunk, BlockFace face);
+
+ boolean hasTiles(Vec3i blockInChunk, BlockFace face);
+
+ default Vec3i resolve(Vec3i relativeCoords, Vec3i output) {
+ return GenericChunks.resolve(relativeCoords, getUp(), output);
+ }
+
+ default Vec3i relativize(Vec3i absoluteCoords, Vec3i output) {
+ return GenericChunks.relativize(absoluteCoords, getUp(), output);
+ }
+
+ default B getBlockRel(Vec3i relativeBlockInChunk) {
+ Vec3i absoluteBlockInChunk = Vectors.grab3i();
+ resolve(relativeBlockInChunk, absoluteBlockInChunk);
+ B result = getBlock(absoluteBlockInChunk);
+ Vectors.release(absoluteBlockInChunk);
+ return result;
+ }
+
+ default TS getTilesRel(Vec3i relativeBlockInChunk, BlockFace face) {
+ Vec3i absoluteBlockInChunk = Vectors.grab3i();
+ resolve(relativeBlockInChunk, absoluteBlockInChunk);
+ TS result = getTiles(absoluteBlockInChunk, face);
+ Vectors.release(absoluteBlockInChunk);
+ return result;
+ }
+
+ default boolean hasTilesRel(Vec3i relativeBlockInChunk, BlockFace face) {
+ Vec3i absoluteBlockInChunk = Vectors.grab3i();
+ resolve(relativeBlockInChunk, absoluteBlockInChunk);
+ boolean result = hasTiles(absoluteBlockInChunk, face);
+ Vectors.release(absoluteBlockInChunk);
+ return result;
+ }
+
+ default int getX() {
+ return getPosition().x;
+ }
+
+ default int getMinX() {
+ return Coordinates.getInWorld(getX(), 0);
+ }
+
+ default int getMaxX() {
+ return Coordinates.getInWorld(getX(), BLOCKS_PER_CHUNK - 1);
+ }
+
+ default int getY() {
+ return getPosition().y;
+ }
+
+ default int getMinY() {
+ return Coordinates.getInWorld(getY(), 0);
+ }
+
+ default int getMaxY() {
+ return Coordinates.getInWorld(getY(), BLOCKS_PER_CHUNK - 1);
+ }
+
+ default int getZ() {
+ return getPosition().z;
+ }
+
+ default int getMinZ() {
+ return Coordinates.getInWorld(getZ(), 0);
+ }
+
+ default int getMaxZ() {
+ return Coordinates.getInWorld(getZ(), BLOCKS_PER_CHUNK - 1);
+ }
+
+ default Vec3i getMinBIW(Vec3i output) {
+ if (output == null) {
+ output = new Vec3i();
+ }
+
+ output.set(getMinX(), getMinY(), getMinZ());
+
+ return output;
+ }
+
+ default Vec3i getMaxBIW(Vec3i output) {
+ if (output == null) {
+ output = new Vec3i();
+ }
+
+ output.set(getMaxX(), getMaxY(), getMaxZ());
+
+ return output;
+ }
+
+ default Vec3i getMinBIWRel(Vec3i output) {
+ if (output == null) {
+ output = new Vec3i();
+ }
+
+ Vec3i absMin = getMinBIW(Vectors.grab3i());
+ Vec3i absMax = getMaxBIW(Vectors.grab3i());
+
+ AxisRotations.relativize(absMin, getUp(), absMin);
+ AxisRotations.relativize(absMax, getUp(), absMax);
+
+ Glm.min(absMin, absMax, output);
+
+ Vectors.release(absMax);
+ Vectors.release(absMin);
+
+ return output;
+ }
+
+ default Vec3i getMaxBIWRel(Vec3i output) {
+ if (output == null) {
+ output = new Vec3i();
+ }
+
+ Vec3i absMin = getMinBIW(Vectors.grab3i());
+ Vec3i absMax = getMaxBIW(Vectors.grab3i());
+
+ AxisRotations.relativize(absMin, getUp(), absMin);
+ AxisRotations.relativize(absMax, getUp(), absMax);
+
+ Glm.max(absMin, absMax, output);
+
+ Vectors.release(absMax);
+ Vectors.release(absMin);
+
+ return output;
+ }
+
+ default boolean containsBiW(Vec3i blockInWorld) {
+ return GenericChunks.testBiC(blockInWorld, this, GenericChunks::containsBiC);
+ }
+
+ default boolean isSurfaceBiW(Vec3i blockInWorld) {
+ return GenericChunks.testBiC(blockInWorld, this, GenericChunks::isSurfaceBiC);
+ }
+
+ default boolean isEdgeBiW(Vec3i blockInWorld) {
+ return GenericChunks.testBiC(blockInWorld, this, GenericChunks::isEdgeBiC);
+ }
+
+ default boolean isVertexBiW(Vec3i blockInWorld) {
+ return GenericChunks.testBiC(blockInWorld, this, GenericChunks::isVertexBiC);
+ }
+
+ default void forEachBiW(Consumer super Vec3i> action) {
+ int minX = Coordinates.getInWorld(getX(), 0);
+ int minY = Coordinates.getInWorld(getY(), 0);
+ int minZ = Coordinates.getInWorld(getZ(), 0);
+
+ VectorUtil.iterateCuboid(
+ minX,
+ minY,
+ minZ,
+ minX + BLOCKS_PER_CHUNK,
+ minY + BLOCKS_PER_CHUNK,
+ minZ + BLOCKS_PER_CHUNK,
+ action
+ );
+ }
+
+ default TS getTilesOrNull(Vec3i blockInChunk, BlockFace face) {
+ if (hasTiles(blockInChunk, face)) {
+ return getTiles(blockInChunk, face);
+ }
+
+ return null;
+ }
+
+ default TS getTilesOrNullRel(Vec3i relativeBlockInChunk, BlockFace face) {
+ Vec3i absoluteBlockInChunk = Vectors.grab3i();
+ resolve(relativeBlockInChunk, absoluteBlockInChunk);
+
+ TS result;
+
+ if (hasTiles(absoluteBlockInChunk, face)) {
+ result = getTiles(absoluteBlockInChunk, face);
+ } else {
+ result = null;
+ }
+
+ Vectors.release(absoluteBlockInChunk);
+
+ return result;
+ }
+
+ default void forEachTileStack(Consumer action) {
+ GenericChunks.forEachBiC(blockInChunk -> {
+ for (AbsFace face : AbsFace.getFaces()) {
+ TS stack = getTilesOrNull(blockInChunk, face);
+ if (stack == null)
+ continue;
+ action.accept(stack);
+ }
+ });
+ }
+
+ /**
+ * Iterates over all tiles in this chunk.
+ *
+ * @param action the action to perform
+ */
+ default void forEachTile(BiConsumer action) {
+ forEachTileStack(stack -> stack.forEach(tileData -> action.accept(stack, tileData)));
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkGenericWO.java b/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkGenericWO.java
new file mode 100644
index 0000000..1005454
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkGenericWO.java
@@ -0,0 +1,37 @@
+/*
+ * 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.generic;
+
+import glm.vec._3.i.Vec3i;
+import ru.windcorp.progressia.common.world.rels.BlockFace;
+
+// @formatter:off
+public interface ChunkGenericWO<
+ B extends BlockGeneric,
+ T extends TileGeneric,
+ TS extends TileGenericStackWO ,
+ TR extends TileGenericReferenceWO ,
+ C extends ChunkGenericWO
+> {
+// @formatter:on
+
+ void setBlock(Vec3i posInChunk, B block, boolean notify);
+
+ TS getTiles(Vec3i blockInChunk, BlockFace face);
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkMap.java b/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkMap.java
index d298d69..695ba91 100644
--- a/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkMap.java
+++ b/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkMap.java
@@ -15,7 +15,7 @@
* 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.generic;
import java.util.Collection;
@@ -77,28 +77,30 @@ public interface ChunkMap {
// TODO implement (int, int, int) and GenericChunk versions of all of the
// above
- default boolean containsChunk(GenericChunk, ?, ?, ?> chunk) {
+ default boolean containsChunk(ChunkGenericRO, ?, ?, ?, ?> chunk) {
return containsKey(chunk.getPosition());
}
- default V get(GenericChunk, ?, ?, ?> chunk) {
+ default V get(ChunkGenericRO, ?, ?, ?, ?> chunk) {
return get(chunk.getPosition());
}
- default V put(GenericChunk, ?, ?, ?> chunk, V obj) {
+ default V put(ChunkGenericRO, ?, ?, ?, ?> chunk, V obj) {
return put(chunk.getPosition(), obj);
}
- default V remove(GenericChunk, ?, ?, ?> chunk) {
+ default V remove(ChunkGenericRO, ?, ?, ?, ?> chunk) {
return remove(chunk.getPosition());
}
- default V getOrDefault(GenericChunk, ?, ?, ?> chunk, V def) {
+ default V getOrDefault(ChunkGenericRO, ?, ?, ?, ?> chunk, V def) {
return containsChunk(chunk) ? def : get(chunk);
}
- default > V compute(C chunk,
- BiFunction super C, ? super V, ? extends V> remappingFunction) {
+ default > V compute(
+ C chunk,
+ BiFunction super C, ? super V, ? extends V> remappingFunction
+ ) {
V newValue = remappingFunction.apply(chunk, get(chunk));
if (newValue == null) {
@@ -126,8 +128,10 @@ public interface ChunkMap {
void forEach(BiConsumer super Vec3i, ? super V> action);
- default > void forEachIn(GenericWorld, ?, ?, C, ?> world,
- BiConsumer super C, ? super V> action) {
+ default > void forEachIn(
+ WorldGenericRO, ?, ?, ?, C, ?> world,
+ BiConsumer super C, ? super V> action
+ ) {
forEach((pos, value) -> {
C chunk = world.getChunk(pos);
if (chunk == null)
diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkMaps.java b/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkMaps.java
new file mode 100644
index 0000000..658f9ea
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkMaps.java
@@ -0,0 +1,261 @@
+/*
+ * 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.generic;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.BiPredicate;
+import glm.vec._3.i.Vec3i;
+import gnu.trove.map.hash.TLongObjectHashMap;
+
+public class ChunkMaps {
+
+ public static ChunkMap newHashMap() {
+ return new LongBasedChunkMap(new TLongObjectHashMap());
+ }
+
+ public static ChunkMap newSyncHashMap(Object mutex) {
+ return new SynchronizedChunkMap(new LongBasedChunkMap(new TLongObjectHashMap()), mutex);
+ }
+
+ public static ChunkMap newSyncHashMap() {
+ return newSyncHashMap(null);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static ChunkMap empty() {
+ return (ChunkMap) EMPTY_MAP;
+ }
+
+ private ChunkMaps() {
+ }
+
+ private final static ChunkMap