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: *
    *
  1. {@link #setup(ChunkRender)} is invoked to provide the {@link ChunkRender} * instance.
  2. *
  3. {@link #startRender()} is invoked. The CRO must reset its state.
  4. *
  5. {@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.
  6. *
  7. {@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 colls, WorldData world, float tickLength, - ColliderWorkspace workspace) { + public static void performCollisions( + List 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 colls, float tickLength, WorldData world, - ColliderWorkspace workspace) { + private static Collision getFirstCollision( + List 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 colls, WorldData world, float tickLength, ColliderWorkspace workspace) { + Collection 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 colls, Collision exceptions, WorldData world, - float step) { + private static void advanceTime( + Collection 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 action) { + public static void iterateCuboid( + int x0, + int y0, + int z0, + int x1, + int y1, + int z1, + Consumer 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 action) { + public static void iterateCuboid( + Vec3i vMin, + Vec3i vMax, + Consumer 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 action) { + public static void iterateCuboidAround( + int cx, + int cy, + int cz, + int dx, + int dy, + int dz, + Consumer 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 action) { + public static void iterateCuboidAround( + Vec3i center, + Vec3i diameters, + Consumer 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 action) { + public static void iterateCuboidAround( + int cx, + int cy, + int cz, + int diameter, + Consumer action + ) { iterateCuboidAround(cx, cy, cz, diameter, diameter, diameter, action); } - public static void iterateCuboidAround(Vec3i center, int diameter, Consumer action) { + public static void iterateCuboidAround( + Vec3i center, + int diameter, + Consumer 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> entries, long seed) { + this.entries = new Entry[entries.size()]; + + double chancesSum = 0; + for (Entry entry : entries) { + chancesSum += entry.chance; + } + + int i = 0; + for (Entry 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 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 getChunks(); + + // TODO: rename WGRO.forEachChunk -> forEachChunkRO and define WGWO.forEachChunk + @Override - public void forEachEntity(Consumer 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 + * + *
    + * TileStackDataContext b = a.push(RelFace.TOP);
    + * processTileStack(b);
    + * b.pop();
    + * 
    + * + * One can improve readability by eliminating the temporary variable: + * + *
    + * processTileStack(a.push(RelFace.TOP));
    + * a.pop();
    + * 
    + * + * 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 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 remappingFunction) { + default > V compute( + C chunk, + BiFunction remappingFunction + ) { V newValue = remappingFunction.apply(chunk, get(chunk)); if (newValue == null) { @@ -126,8 +128,10 @@ public interface ChunkMap { void forEach(BiConsumer action); - default > void forEachIn(GenericWorld world, - BiConsumer action) { + default > void forEachIn( + WorldGenericRO world, + BiConsumer 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 EMPTY_MAP = new ChunkMap() { + + @Override + public int size() { + return 0; + } + + @Override + public boolean containsKey(Vec3i pos) { + return false; + } + + @Override + public Object get(Vec3i pos) { + return null; + } + + @Override + public Object put(Vec3i pos, Object obj) { + throw new UnsupportedOperationException(); + } + + @Override + public Object remove(Vec3i pos) { + throw new UnsupportedOperationException(); + } + + @Override + public Collection values() { + return Collections.emptyList(); + } + + @Override + public ChunkSet keys() { + return ChunkSets.empty(); + } + + @Override + public boolean removeIf(BiPredicate condition) { + return false; + } + + @Override + public void forEach(BiConsumer action) { + // Do nothing + } + + }; + + private static class SynchronizedChunkMap implements ChunkMap { + + private final ChunkMap parent; + private final Object mutex; + + public SynchronizedChunkMap(ChunkMap parent, Object mutex) { + Objects.requireNonNull(parent, "parent"); + this.parent = parent; + + this.mutex = mutex == null ? this : mutex; + } + + @Override + public int size() { + synchronized (mutex) { + return parent.size(); + } + } + + @Override + public boolean isEmpty() { + synchronized (mutex) { + return parent.isEmpty(); + } + } + + @Override + public boolean containsKey(Vec3i pos) { + synchronized (mutex) { + return parent.containsKey(pos); + } + } + + @Override + public V get(Vec3i pos) { + synchronized (mutex) { + return parent.get(pos); + } + } + + @Override + public V put(Vec3i pos, V obj) { + synchronized (mutex) { + return parent.put(pos, obj); + } + } + + @Override + public V remove(Vec3i pos) { + synchronized (mutex) { + return parent.remove(pos); + } + } + + @Override + public boolean containsValue(V value) { + synchronized (mutex) { + return parent.containsValue(value); + } + } + + @Override + public V getOrDefault(Vec3i pos, V def) { + synchronized (mutex) { + return parent.getOrDefault(pos, def); + } + } + + @Override + public V compute(Vec3i pos, BiFunction remappingFunction) { + synchronized (mutex) { + return parent.compute(pos, remappingFunction); + } + } + + @Override + public boolean containsChunk(ChunkGenericRO chunk) { + synchronized (mutex) { + return parent.containsChunk(chunk); + } + } + + @Override + public V get(ChunkGenericRO chunk) { + synchronized (mutex) { + return parent.get(chunk); + } + } + + @Override + public V put(ChunkGenericRO chunk, V obj) { + synchronized (mutex) { + return parent.put(chunk, obj); + } + } + + @Override + public V remove(ChunkGenericRO chunk) { + synchronized (mutex) { + return parent.remove(chunk); + } + } + + @Override + public V getOrDefault(ChunkGenericRO chunk, V def) { + synchronized (mutex) { + return parent.getOrDefault(chunk, def); + } + } + + @Override + public > V compute( + C chunk, + BiFunction remappingFunction + ) { + synchronized (mutex) { + return parent.compute(chunk, remappingFunction); + } + } + + @Override + public Collection values() { + synchronized (mutex) { + return parent.values(); + } + } + + @Override + public ChunkSet keys() { + synchronized (mutex) { + return parent.keys(); + } + } + + @Override + public boolean removeIf(BiPredicate condition) { + synchronized (mutex) { + return parent.removeIf(condition); + } + } + + @Override + public void forEach(BiConsumer action) { + synchronized (mutex) { + parent.forEach(action); + } + } + + @Override + public > void forEachIn( + WorldGenericRO world, + BiConsumer action + ) { + synchronized (mutex) { + parent.forEachIn(world, action); + } + } + + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkSet.java b/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkSet.java index 9ee94af..9b75d3c 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkSet.java +++ b/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkSet.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; @@ -56,6 +56,7 @@ public interface ChunkSet extends Iterable { default boolean contains(int x, int y, int z) { Vec3i v = Vectors.grab3i(); + v.set(x, y, z); boolean result = contains(v); Vectors.release(v); return result; @@ -63,6 +64,7 @@ public interface ChunkSet extends Iterable { default boolean add(int x, int y, int z) { Vec3i v = Vectors.grab3i(); + v.set(x, y, z); boolean result = add(v); Vectors.release(v); return result; @@ -70,25 +72,28 @@ public interface ChunkSet extends Iterable { default boolean remove(int x, int y, int z) { Vec3i v = Vectors.grab3i(); + v.set(x, y, z); boolean result = remove(v); Vectors.release(v); return result; } - default boolean contains(GenericChunk chunk) { + default boolean contains(ChunkGenericRO chunk) { return contains(chunk.getPosition()); } - default boolean add(GenericChunk chunk) { + default boolean add(ChunkGenericRO chunk) { return add(chunk.getPosition()); } - default boolean remove(GenericChunk chunk) { + default boolean remove(ChunkGenericRO chunk) { return remove(chunk.getPosition()); } - default > void forEachIn(GenericWorld world, - Consumer action) { + default > void forEachIn( + WorldGenericRO world, + Consumer action + ) { forEach(position -> { C chunk = world.getChunk(position); if (chunk == null) @@ -205,7 +210,7 @@ public interface ChunkSet extends Iterable { } } - default boolean containsAllChunks(Iterable> chunks) { + default boolean containsAllChunks(Iterable> chunks) { boolean[] hasMissing = new boolean[] { false }; chunks.forEach(c -> { @@ -217,7 +222,7 @@ public interface ChunkSet extends Iterable { return hasMissing[0]; } - default boolean containsAnyChunks(Iterable> chunks) { + default boolean containsAnyChunks(Iterable> chunks) { boolean[] hasPresent = new boolean[] { false }; chunks.forEach(c -> { @@ -229,11 +234,11 @@ public interface ChunkSet extends Iterable { return hasPresent[0]; } - default void addAllChunks(Iterable> chunks) { + default void addAllChunks(Iterable> chunks) { chunks.forEach(this::add); } - default void removeAllChunks(Iterable> chunks) { + default void removeAllChunks(Iterable> chunks) { chunks.forEach(this::remove); } diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkSets.java b/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkSets.java index ac79696..8123cbc 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkSets.java +++ b/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkSets.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.Iterator; @@ -198,29 +198,31 @@ public class ChunkSets { } @Override - public boolean contains(GenericChunk chunk) { + public boolean contains(ChunkGenericRO chunk) { synchronized (mutex) { return parent.contains(chunk); } } @Override - public boolean add(GenericChunk chunk) { + public boolean add(ChunkGenericRO chunk) { synchronized (mutex) { return parent.add(chunk); } } @Override - public boolean remove(GenericChunk chunk) { + public boolean remove(ChunkGenericRO chunk) { synchronized (mutex) { return parent.remove(chunk); } } @Override - public > void forEachIn(GenericWorld world, - Consumer action) { + public > void forEachIn( + WorldGenericRO world, + Consumer action + ) { synchronized (mutex) { parent.forEachIn(world, action); } @@ -318,28 +320,28 @@ public class ChunkSets { } @Override - public boolean containsAllChunks(Iterable> chunks) { + public boolean containsAllChunks(Iterable> chunks) { synchronized (mutex) { return parent.containsAllChunks(chunks); } } @Override - public boolean containsAnyChunks(Iterable> chunks) { + public boolean containsAnyChunks(Iterable> chunks) { synchronized (mutex) { return parent.containsAnyChunks(chunks); } } @Override - public void addAllChunks(Iterable> chunks) { + public void addAllChunks(Iterable> chunks) { synchronized (mutex) { parent.addAllChunks(chunks); } } @Override - public void removeAllChunks(Iterable> chunks) { + public void removeAllChunks(Iterable> chunks) { synchronized (mutex) { parent.removeAllChunks(chunks); } diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/GenericEntity.java b/src/main/java/ru/windcorp/progressia/common/world/generic/EntityGeneric.java similarity index 95% rename from src/main/java/ru/windcorp/progressia/common/world/generic/GenericEntity.java rename to src/main/java/ru/windcorp/progressia/common/world/generic/EntityGeneric.java index ee802b9..eda5bc6 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/generic/GenericEntity.java +++ b/src/main/java/ru/windcorp/progressia/common/world/generic/EntityGeneric.java @@ -22,9 +22,11 @@ import glm.vec._3.Vec3; import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.common.world.Coordinates; -public interface GenericEntity { +public interface EntityGeneric { String getId(); + + long getEntityId(); Vec3 getPosition(); diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/GenericChunk.java b/src/main/java/ru/windcorp/progressia/common/world/generic/GenericChunk.java deleted file mode 100644 index cd93df6..0000000 --- a/src/main/java/ru/windcorp/progressia/common/world/generic/GenericChunk.java +++ /dev/null @@ -1,186 +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.generic; - -import java.util.function.Consumer; - -import glm.vec._3.i.Vec3i; -import ru.windcorp.progressia.common.util.VectorUtil; -import ru.windcorp.progressia.common.util.Vectors; -import ru.windcorp.progressia.common.world.Coordinates; -import ru.windcorp.progressia.common.world.block.BlockFace; - -public interface GenericChunk, B extends GenericBlock, T extends GenericTile, TS extends GenericTileStack> { - - public static final int BLOCKS_PER_CHUNK = Coordinates.CHUNK_SIZE; - - Vec3i getPosition(); - - B getBlock(Vec3i blockInChunk); - - TS getTiles(Vec3i blockInChunk, BlockFace face); - - boolean hasTiles(Vec3i blockInChunk, BlockFace face); - - 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 boolean containsBiC(Vec3i blockInChunk) { - return blockInChunk.x >= 0 && blockInChunk.x < BLOCKS_PER_CHUNK && blockInChunk.y >= 0 - && blockInChunk.y < BLOCKS_PER_CHUNK && blockInChunk.z >= 0 && blockInChunk.z < BLOCKS_PER_CHUNK; - } - - default boolean containsBiW(Vec3i blockInWorld) { - Vec3i v = Vectors.grab3i(); - - v = Coordinates.getInWorld(getPosition(), Vectors.ZERO_3i, v); - v = blockInWorld.sub(v, v); - - boolean result = containsBiC(v); - - Vectors.release(v); - return result; - } - - default boolean isSurfaceBiC(Vec3i blockInChunk) { - int hits = 0; - - if (Coordinates.isOnChunkBorder(blockInChunk.x)) - hits++; - if (Coordinates.isOnChunkBorder(blockInChunk.y)) - hits++; - if (Coordinates.isOnChunkBorder(blockInChunk.z)) - hits++; - - return hits >= 1; - } - - default boolean isSurfaceBiW(Vec3i blockInWorld) { - Vec3i v = Vectors.grab3i(); - - v = Coordinates.getInWorld(getPosition(), Vectors.ZERO_3i, v); - v = blockInWorld.sub(v, v); - - boolean result = isSurfaceBiC(v); - - Vectors.release(v); - return result; - } - - default boolean isEdgeBiC(Vec3i blockInChunk) { - int hits = 0; - - if (Coordinates.isOnChunkBorder(blockInChunk.x)) - hits++; - if (Coordinates.isOnChunkBorder(blockInChunk.y)) - hits++; - if (Coordinates.isOnChunkBorder(blockInChunk.z)) - hits++; - - return hits >= 2; - } - - default boolean isEdgeBiW(Vec3i blockInWorld) { - Vec3i v = Vectors.grab3i(); - - v = Coordinates.getInWorld(getPosition(), Vectors.ZERO_3i, v); - v = blockInWorld.sub(v, v); - - boolean result = isEdgeBiC(v); - - Vectors.release(v); - return result; - } - - default boolean isVertexBiC(Vec3i blockInChunk) { - int hits = 0; - - if (Coordinates.isOnChunkBorder(blockInChunk.x)) - hits++; - if (Coordinates.isOnChunkBorder(blockInChunk.y)) - hits++; - if (Coordinates.isOnChunkBorder(blockInChunk.z)) - hits++; - - return hits == 3; - } - - default boolean isVertexBiW(Vec3i blockInWorld) { - Vec3i v = Vectors.grab3i(); - - v = Coordinates.getInWorld(getPosition(), Vectors.ZERO_3i, v); - v = blockInWorld.sub(v, v); - - boolean result = isVertexBiC(v); - - Vectors.release(v); - return result; - } - - default void forEachBiC(Consumer action) { - VectorUtil.iterateCuboid(0, 0, 0, BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK, action); - } - - default void forEachBiW(Consumer action) { - VectorUtil.iterateCuboid(Coordinates.getInWorld(getX(), 0), Coordinates.getInWorld(getY(), 0), - Coordinates.getInWorld(getZ(), 0), BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK, action); - } - - default TS getTilesOrNull(Vec3i blockInChunk, BlockFace face) { - if (hasTiles(blockInChunk, face)) { - return getTiles(blockInChunk, face); - } - - return null; - } - -} diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/GenericChunks.java b/src/main/java/ru/windcorp/progressia/common/world/generic/GenericChunks.java new file mode 100644 index 0000000..83ba89a --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/generic/GenericChunks.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.common.world.generic; + +import java.util.function.Consumer; +import java.util.function.Predicate; + +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; + +public class GenericChunks { + + public static Vec3i resolve(Vec3i relativeCoords, AbsFace up, Vec3i output) { + if (output == null) { + output = new Vec3i(); + } + + final int offset = ChunkGenericRO.BLOCKS_PER_CHUNK - 1; + + output.set(relativeCoords.x, relativeCoords.y, relativeCoords.z); + output.mul(2).sub(offset); + + AxisRotations.resolve(output, up, output); + + output.add(offset).div(2); + + return output; + } + + public static Vec3i relativize(Vec3i absoluteCoords, AbsFace up, Vec3i output) { + if (output == null) { + output = new Vec3i(); + } + + final int offset = ChunkGenericRO.BLOCKS_PER_CHUNK - 1; + + output.set(absoluteCoords.x, absoluteCoords.y, absoluteCoords.z); + output.mul(2).sub(offset); + + AxisRotations.relativize(output, up, output); + + output.add(offset).div(2); + + return output; + } + + private static int getBorderHits(Vec3i blockInChunk) { + int hits = 0; + + if (Coordinates.isOnChunkBorder(blockInChunk.x)) hits++; + if (Coordinates.isOnChunkBorder(blockInChunk.y)) hits++; + if (Coordinates.isOnChunkBorder(blockInChunk.z)) hits++; + + return hits; + } + + static boolean testBiC(Vec3i blockInWorld, ChunkGenericRO chunk, Predicate test) { + Vec3i v = Vectors.grab3i(); + + v = Coordinates.getInWorld(chunk.getPosition(), Vectors.ZERO_3i, v); + v = blockInWorld.sub(v, v); + + boolean result = test.test(v); + + Vectors.release(v); + + return result; + } + + public static boolean containsBiC(Vec3i blockInChunk) { + return blockInChunk.x >= 0 && blockInChunk.x < ChunkGenericRO.BLOCKS_PER_CHUNK && + blockInChunk.y >= 0 && blockInChunk.y < ChunkGenericRO.BLOCKS_PER_CHUNK && + blockInChunk.z >= 0 && blockInChunk.z < ChunkGenericRO.BLOCKS_PER_CHUNK; + } + + public static boolean isSurfaceBiC(Vec3i blockInChunk) { + return GenericChunks.getBorderHits(blockInChunk) >= 1; + } + + public static boolean isEdgeBiC(Vec3i blockInChunk) { + return GenericChunks.getBorderHits(blockInChunk) >= 2; + } + + public static boolean isVertexBiC(Vec3i blockInChunk) { + return GenericChunks.getBorderHits(blockInChunk) == 3; + } + + public static void forEachBiC(Consumer action) { + VectorUtil.iterateCuboid( + 0, + 0, + 0, + ChunkGenericRO.BLOCKS_PER_CHUNK, + ChunkGenericRO.BLOCKS_PER_CHUNK, + ChunkGenericRO.BLOCKS_PER_CHUNK, + action + ); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/LongBasedChunkSet.java b/src/main/java/ru/windcorp/progressia/common/world/generic/LongBasedChunkSet.java index f5112c8..64819ae 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/generic/LongBasedChunkSet.java +++ b/src/main/java/ru/windcorp/progressia/common/world/generic/LongBasedChunkSet.java @@ -45,7 +45,7 @@ public class LongBasedChunkSet implements ChunkSet { addAll(copyFrom); } - public LongBasedChunkSet(TLongSet impl, GenericWorld copyFrom) { + public LongBasedChunkSet(TLongSet impl, WorldGenericRO copyFrom) { this(impl); addAllChunks(copyFrom.getChunks()); } diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/GenericTile.java b/src/main/java/ru/windcorp/progressia/common/world/generic/TileGeneric.java similarity index 96% rename from src/main/java/ru/windcorp/progressia/common/world/generic/GenericTile.java rename to src/main/java/ru/windcorp/progressia/common/world/generic/TileGeneric.java index 73bf63b..d99737f 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/generic/GenericTile.java +++ b/src/main/java/ru/windcorp/progressia/common/world/generic/TileGeneric.java @@ -18,7 +18,7 @@ package ru.windcorp.progressia.common.world.generic; -public interface GenericTile { +public interface TileGeneric { String getId(); diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/TileGenericReferenceRO.java b/src/main/java/ru/windcorp/progressia/common/world/generic/TileGenericReferenceRO.java new file mode 100644 index 0000000..6f49630 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/generic/TileGenericReferenceRO.java @@ -0,0 +1,93 @@ +/* + * 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; + +/** + * A reference to a single tile in a tile stack. A {@code TileReference} remains + * valid until the tile is removed from its stack. + *

    + * Tile reference objects may be reused for other tiles; {@link #isValid()} only + * shows if there exists some tile that this object references; it may + * or may not be the tile this reference was acquired for. It is the + * responsibility of the programmer to discard references when the tile is + * removed. + */ +// @formatter:off +public interface TileGenericReferenceRO< + B extends BlockGeneric, + T extends TileGeneric, + TS extends TileGenericStackRO , + TR extends TileGenericReferenceRO , + C extends ChunkGenericRO +> { +// @formatter:on + + /** + * Gets the index that the referenced tile currently occupies. This value + * may change as tiles are added to or removed from the stack. + * + * @return the index of the tile or {@code -1} if this reference is invalid + */ + int getIndex(); + + /** + * Gets the tile stack that contains the referenced tile. + * + * @return the tile stack of the relevant tile or {@code null} if this + * reference is invalid. + */ + TS getStack(); + + /** + * Gets the tile that this object references. + * + * @return the relevant tile or {@code null} if this reference is invalid + */ + default T get() { + return getStack().get(getIndex()); + } + + /** + * Checks whether this reference is valid. A reference is valid if it points + * to some tile; it may or may not be the tile that this reference + * was acquired for. (A tile reference can only change the referenced tile + * after the previous tile is removed from the stack.) + * + * @return {@code true} iff there exists a tile that this reference points + * to. + */ + default boolean isValid() { + return get() != null; + } + + /** + * Gets the tag of the referenced tile. + * + * @return the tag or {@code -1} iff this reference is invalid. + */ + default int getTag() { + TS tileStack = getStack(); + if (tileStack == null) { + return -1; + } else { + return tileStack.getTagByIndex(getIndex()); + } + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/TileGenericReferenceWO.java b/src/main/java/ru/windcorp/progressia/common/world/generic/TileGenericReferenceWO.java new file mode 100644 index 0000000..f31eca8 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/generic/TileGenericReferenceWO.java @@ -0,0 +1,33 @@ +/* + * 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; + +// @formatter:off +public interface TileGenericReferenceWO< + B extends BlockGeneric, + T extends TileGeneric, + TS extends TileGenericStackWO , + TR extends TileGenericReferenceWO , + C extends ChunkGenericWO +> { +// @formatter:on + + // currently empty + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/GenericTileStack.java b/src/main/java/ru/windcorp/progressia/common/world/generic/TileGenericStackRO.java similarity index 66% rename from src/main/java/ru/windcorp/progressia/common/world/generic/GenericTileStack.java rename to src/main/java/ru/windcorp/progressia/common/world/generic/TileGenericStackRO.java index 32a3786..0a08fac 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/generic/GenericTileStack.java +++ b/src/main/java/ru/windcorp/progressia/common/world/generic/TileGenericStackRO.java @@ -15,20 +15,27 @@ * 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.AbstractList; +import java.util.List; import java.util.Objects; import java.util.RandomAccess; import java.util.function.Consumer; import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.common.world.Coordinates; -import ru.windcorp.progressia.common.world.block.BlockFace; +import ru.windcorp.progressia.common.world.rels.RelFace; -public abstract class GenericTileStack, T extends GenericTile, C extends GenericChunk> - extends AbstractList implements RandomAccess { +// @formatter:off +public interface TileGenericStackRO< + B extends BlockGeneric, + T extends TileGeneric, + TS extends TileGenericStackRO , + TR extends TileGenericReferenceRO , + C extends ChunkGenericRO +> extends List, RandomAccess { +// @formatter:on public static interface TSConsumer { void accept(int layer, T tile); @@ -36,30 +43,36 @@ public abstract class GenericTileStack public static final int TILES_PER_FACE = 8; - public abstract Vec3i getBlockInChunk(Vec3i output); + Vec3i getBlockInChunk(Vec3i output); - public abstract C getChunk(); + C getChunk(); - public abstract BlockFace getFace(); + RelFace getFace(); - public Vec3i getBlockInWorld(Vec3i output) { + TR getReference(int index); + + int getIndexByTag(int tag); + + int getTagByIndex(int index); + + default Vec3i getBlockInWorld(Vec3i output) { // This is safe return Coordinates.getInWorld(getChunk().getPosition(), getBlockInChunk(output), output); } - public boolean isFull() { + default boolean isFull() { return size() >= TILES_PER_FACE; } - public T getClosest() { + default T getClosest() { return get(0); } - public T getFarthest() { + default T getFarthest() { return get(size() - 1); } - public void forEach(TSConsumer action) { + default void forEach(TSConsumer action) { Objects.requireNonNull(action, "action"); for (int i = 0; i < size(); ++i) { action.accept(i, get(i)); @@ -67,14 +80,14 @@ public abstract class GenericTileStack } @Override - public void forEach(Consumer action) { + default void forEach(Consumer action) { Objects.requireNonNull(action, "action"); for (int i = 0; i < size(); ++i) { action.accept(get(i)); } } - public T findClosest(String id) { + default T findClosest(String id) { Objects.requireNonNull(id, "id"); for (int i = 0; i < size(); ++i) { @@ -87,7 +100,7 @@ public abstract class GenericTileStack return null; } - public T findFarthest(String id) { + default T findFarthest(String id) { Objects.requireNonNull(id, "id"); for (int i = 0; i < size(); ++i) { @@ -100,8 +113,12 @@ public abstract class GenericTileStack return null; } - public boolean contains(String id) { + default boolean contains(String id) { return findClosest(id) != null; } + default B getHost() { + return getChunk().getBlock(getBlockInChunk(null)); + } + } diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/TileGenericStackWO.java b/src/main/java/ru/windcorp/progressia/common/world/generic/TileGenericStackWO.java new file mode 100644 index 0000000..ec5073e --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/generic/TileGenericStackWO.java @@ -0,0 +1,165 @@ +/* + * 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.List; +import java.util.RandomAccess; + +// @formatter:off +public interface TileGenericStackWO< + B extends BlockGeneric, + T extends TileGeneric, + TS extends TileGenericStackWO , + TR extends TileGenericReferenceWO , + C extends ChunkGenericWO +> + extends List, RandomAccess { +// @formatter:on + + /** + * Inserts the specified tile at the specified position in this stack. + * Shifts the tile currently at that position (if any) and any tiles above + * to + * the top (adds one to their indices). + * + * @param index index at which the specified tile is to be inserted + * @param tile tile to be inserted + * @throws TileStackIsFullException if this stack is {@linkplain #isFull() + * full} + */ + /* + * Impl note: AbstractList provides a useless implementation of this method, + * make sure to override it in subclass + */ + @Override + void add(int index, T tile); + + /** + * Adds the specified tile at the end of this stack assigning it the + * provided tag. + * This method is useful for copying stacks when preserving tags is + * necessary. + * + * @param tile the tile to add + * @param tag the tag to assign the new tile + * @throws IllegalArgumentException if this stack already contains a tile + * with the + * provided tag + */ + void load(T tile, int tag); + + /** + * Replaces the tile at the specified position in this stack with the + * specified tile. + * + * @param index index of the tile to replace + * @param tile tile to be stored at the specified position + * @return the tile previously at the specified position + */ + /* + * Impl note: AbstractList provides a useless implementation of this method, + * make sure to override it in subclass + */ + @Override + T set(int index, T tile); + + /** + * Removes the tile at the specified position in this list. Shifts any + * subsequent tiles + * to the left (subtracts one from their indices). Returns the tile that was + * removed + * from the list. + * + * @param index the index of the tile to be removed + * @return the tile previously at the specified position + */ + /* + * Impl note: AbstractList provides a useless implementation of this method, + * make sure to override it in subclass + */ + @Override + T remove(int index); + + /* + * Aliases and overloads + */ + + default void addClosest(T tile) { + add(0, tile); + } + + default void addFarthest(T tile) { + add(size(), tile); + } + + boolean isFull(); + + /** + * Attempts to {@link #add(int, TileData) add} the provided {@code tile} + * at {@code index}. If the stack is {@linkplain #isFull() full}, does + * nothing. + * + * @param index the index to insert the tile at + * @param tile the tile to try to add + * @return {@code true} iff this stack has changed + */ + default boolean offer(int index, T tile) { + if (isFull()) + return false; + add(index, tile); + return true; + } + + default boolean offerClosest(T tile) { + return offer(0, tile); + } + + default boolean offerFarthest(T tile) { + return offer(size(), tile); + } + + default T removeClosest() { + return remove(0); + } + + default T removeFarthest() { + return remove(size() - 1); + } + + default T poll(int index) { + if (size() <= index) + return null; + return remove(index); + } + + default T pollClosest() { + return poll(0); + } + + default T pollFarthest() { + return poll(size() - 1); + } + + @Override + default boolean add(T tile) { + addFarthest(tile); + return true; + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/GenericWorld.java b/src/main/java/ru/windcorp/progressia/common/world/generic/WorldGenericRO.java similarity index 83% rename from src/main/java/ru/windcorp/progressia/common/world/generic/GenericWorld.java rename to src/main/java/ru/windcorp/progressia/common/world/generic/WorldGenericRO.java index e1b71fa..f4cb845 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/generic/GenericWorld.java +++ b/src/main/java/ru/windcorp/progressia/common/world/generic/WorldGenericRO.java @@ -26,16 +26,28 @@ import glm.vec._3.Vec3; import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.common.util.Vectors; import ru.windcorp.progressia.common.world.Coordinates; -import ru.windcorp.progressia.common.world.block.BlockFace; +import ru.windcorp.progressia.common.world.rels.AbsFace; +import ru.windcorp.progressia.common.world.rels.BlockFace; -public interface GenericWorld, C extends GenericChunk, E extends GenericEntity> { +// @formatter:off +public interface WorldGenericRO< + B extends BlockGeneric, + T extends TileGeneric, + TS extends TileGenericStackRO , + TR extends TileGenericReferenceRO , + C extends ChunkGenericRO , + E extends EntityGeneric +> { +// @formatter:on - Collection getChunks(); + Collection getChunks(); C getChunk(Vec3i pos); Collection getEntities(); + E getEntity(long entityId); + /* * Chunks */ @@ -47,6 +59,10 @@ public interface GenericWorld layer; + } + default boolean isChunkLoaded(Vec3i chunkPos) { return getChunk(chunkPos) != null; } - default boolean isBlockLoaded(Vec3i blockInWorld) { + default boolean isLocationLoaded(Vec3i blockInWorld) { return getChunkByBlock(blockInWorld) != null; } diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/WorldGenericWO.java b/src/main/java/ru/windcorp/progressia/common/world/generic/WorldGenericWO.java new file mode 100644 index 0000000..3b9a81b --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/generic/WorldGenericWO.java @@ -0,0 +1,57 @@ +/* + * 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.state.StateChange; +import ru.windcorp.progressia.common.state.StatefulObject; +import ru.windcorp.progressia.common.world.rels.BlockFace; + +//@formatter:off +public interface WorldGenericWO< + B extends BlockGeneric, + T extends TileGeneric, + TS extends TileGenericStackWO , + TR extends TileGenericReferenceWO , + C extends ChunkGenericWO , + E extends EntityGeneric +> { +//@formatter:on + + void setBlock(Vec3i blockInWorld, B block, boolean notify); + + TS getTiles(Vec3i blockInWorld, BlockFace face); + + void addEntity(E entity); + + void removeEntity(long entityId); + + default void removeEntity(E entity) { + removeEntity(entity.getEntityId()); + } + + /** + * Requests that the specified change is applied to the given entity. The + * {@code change} object provided may be stored until the change is applied. + * + * @param entity the entity to change + * @param change the change to apply + */ + void changeEntity(SE entity, StateChange change); + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/context/AbstractContextRO.java b/src/main/java/ru/windcorp/progressia/common/world/generic/context/AbstractContextRO.java new file mode 100644 index 0000000..0657a3e --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/generic/context/AbstractContextRO.java @@ -0,0 +1,102 @@ +/* + * 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.context; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.util.StashingStack; +import ru.windcorp.progressia.common.world.generic.BlockGeneric; +import ru.windcorp.progressia.common.world.generic.EntityGeneric; +import ru.windcorp.progressia.common.world.generic.TileGeneric; +import ru.windcorp.progressia.common.world.rels.RelFace; + +//@formatter:off +public abstract class AbstractContextRO< + B extends BlockGeneric, + T extends TileGeneric, + E extends EntityGeneric +> implements TileGenericContextRO { +//@formatter:on + + public static final int MAX_SUBCONTEXTS = 64; + + protected class Frame { + + public final Vec3i location = new Vec3i(); + public RelFace face; + public int layer; + + @Override + public String toString() { + return "Frame [x=" + location.x + ", y=" + location.y + ", z=" + location.z + ", face=" + face + ", layer=" + + layer + "]"; + } + + } + + protected Frame frame = null; + + private final StashingStack frameStack = new StashingStack<>(MAX_SUBCONTEXTS, Frame::new); + + @Override + public void pop() { + if (!isSubcontexting()) { + throw new IllegalStateException("Cannot pop(): already top frame"); + } + + frameStack.pop(); + frame = frameStack.peek(); + } + + @Override + public BlockGenericContextRO push(Vec3i location) { + frame = frameStack.push(); + + frame.location.set(location.x, location.y, location.z); + frame.face = null; + frame.layer = -1; + + return this; + } + + @Override + public TileStackGenericContextRO push(Vec3i location, RelFace face) { + frame = frameStack.push(); + + frame.location.set(location.x, location.y, location.z); + frame.face = face; + frame.layer = -1; + + return this; + } + + @Override + public TileGenericContextRO push(Vec3i location, RelFace face, int layer) { + frame = frameStack.push(); + + frame.location.set(location.x, location.y, location.z); + frame.face = face; + frame.layer = layer; + + return this; + } + + public boolean isSubcontexting() { + return !frameStack.isEmpty(); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/context/BlockGenericContextRO.java b/src/main/java/ru/windcorp/progressia/common/world/generic/context/BlockGenericContextRO.java new file mode 100644 index 0000000..8984ad3 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/generic/context/BlockGenericContextRO.java @@ -0,0 +1,164 @@ +/* + * 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.context; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.context.Context; +import ru.windcorp.progressia.common.world.generic.*; +import ru.windcorp.progressia.common.world.rels.RelFace; +import ru.windcorp.progressia.common.world.rels.RelRelation; + +/** + * A {@link Context} referencing a world with a block location specified. The + * location may or may not be loaded. + */ +//@formatter:off +public interface BlockGenericContextRO< + B extends BlockGeneric, + T extends TileGeneric, + E extends EntityGeneric +> extends WorldContexts.Block, WorldGenericContextRO { +//@formatter:on + + /** + * Determines whether the location relevant to this context is currently + * loaded. + * + * @return {@code true} iff the location is loaded + */ + default boolean isLoaded() { + return isLocationLoaded(getLocation()); + } + + /** + * Retrieves the block at the relevant location. This method may return + * {@code null} in one of two cases: + *

      + *
    • the location that the block would occupy is not loaded, or + *
    • the corresponding chunk's terrain has not yet generated. + *
    + * + * @return the block or {@code null} if the location is not loaded + */ + default B getBlock() { + return getBlock(getLocation()); + } + + /** + * Determines whether the specified position has a tile. Block location is + * implied by the context. + * + * @param face the face of the block that the tile occupies + * @param layer the layer of the tile + * @return {@code true} iff the tile exists + */ + default boolean hasTile(RelFace face, int layer) { + return hasTile(getLocation(), face, layer); + } + + /** + * Determines whether the specified position has a tile with the given tag. + * Block location is implied by context. + * + * @param face the face of the block that the tile occupies + * @param tag the tag of the tile + * @return {@code true} iff the tile exists + */ + default boolean isTagValid(RelFace face, int tag) { + return isTagValid(getLocation(), face, tag); + } + + /** + * Retrieves the tile at the specified position. Block location is implied + * by context. This method may return {@code null} in one of three cases: + *
      + *
    • the location is not loaded, + *
    • there is no tile stack on the specified face, or + *
    • {@code layer} is not less than the amount of tiles in the tile stack. + *
    + * + * @param face the face of the block that the tile occupies + * @param layer the layer of the tile stack that the tile occupies + * @return the tile or {@code null} if the position does not contain a tile + */ + default T getTile(RelFace face, int layer) { + return getTile(getLocation(), face, layer); + } + + /** + * Retrieves the tile at the specified position and the tile's tag. Block + * location is implied by the context. This + * method may return {@code null} in one of three cases: + *
      + *
    • the location is not loaded, + *
    • there is no tile stack on the specified face, or + *
    • there is no tile with the specified tag in the tile stack. + *
    + * + * @param face the face of the block that the tile occupies + * @param tag the tag of the tile + * @return the tile or {@code null} if the position does not contain a tile + */ + default T getTileByTag(RelFace face, int tag) { + return getTileByTag(getLocation(), face, tag); + } + + /** + * Counts the amount of tiles in the specified tile stack. Block location is + * implied by the context + *

    + * This method returns {@code 0} in case the location is not loaded. + * + * @param face the face of the block that the tile stack occupies + * @return the count of tiles in the tile stack or {@code -1} if the tile + * stack could not exist + */ + default int getTileCount(RelFace face) { + return getTileCount(face); + } + + /* + * Subcontexting + */ + + @Override + default BlockGenericContextRO pushRelative(int dx, int dy, int dz) { + return push(getLocation().add_(dx, dy, dz)); + } + + @Override + default BlockGenericContextRO pushRelative(Vec3i direction) { + return push(getLocation().add_(direction)); + } + + @Override + default BlockGenericContextRO pushRelative(RelRelation direction) { + return push(getLocation().add_(direction.getRelVector())); + } + + @Override + default TileStackGenericContextRO push(RelFace face) { + return push(getLocation(), face); + } + + @Override + default TileGenericContextRO push(RelFace face, int layer) { + return push(getLocation(), face, layer); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/context/BlockGenericContextWO.java b/src/main/java/ru/windcorp/progressia/common/world/generic/context/BlockGenericContextWO.java new file mode 100644 index 0000000..3555c09 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/generic/context/BlockGenericContextWO.java @@ -0,0 +1,107 @@ +/* + * 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.context; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.context.Context; +import ru.windcorp.progressia.common.world.generic.*; +import ru.windcorp.progressia.common.world.rels.RelFace; +import ru.windcorp.progressia.common.world.rels.RelRelation; + +/** + * A writable {@link Context} referencing a world with a block location + * specified. This context provides methods for affecting the world. The + * application of requested changes may or may not be immediate, see + * {@link #isImmediate()}. The location may or may not be loaded. + */ +//@formatter:off +public interface BlockGenericContextWO< + B extends BlockGeneric, + T extends TileGeneric, + E extends EntityGeneric +> extends WorldContexts.Block, WorldGenericContextWO { +//@formatter:on + + /** + * Requests that a block is changed. The object provided may be stored until + * the change is applied. The location of the block is implied by the + * context. + * + * @param block the new block + * @see #isImmediate() + */ + default void setBlock(B block) { + setBlock(getLocation(), block); + } + + /** + * Requests that a tile is added to the top of the tile stack at the given + * location. The object provided may be stored until the change is applied. + * If the tile could not be added at the time of application this method + * fails silently. The location of the block is implied by the context. + * + * @param face the face of the block to add the tile to + * @param tile the tile to add + */ + default void addTile(RelFace face, T tile) { + addTile(getLocation(), face, tile); + } + + /** + * Requests that a tile identified by its tag is removed from the specified + * tile stack. If the tile could not be found at the time of application + * this method fails silently. The location of the block is implied by the + * context. + * + * @param face the of the block to remove the tile from + * @param tag the tag of the tile to remove + */ + default void removeTile(RelFace face, int tag) { + removeTile(getLocation(), face, tag); + } + + /* + * Subcontexting + */ + + @Override + default BlockGenericContextWO pushRelative(int dx, int dy, int dz) { + return push(getLocation().add_(dx, dy, dz)); + } + + @Override + default BlockGenericContextWO pushRelative(Vec3i direction) { + return push(getLocation().add_(direction)); + } + + @Override + default BlockGenericContextWO pushRelative(RelRelation direction) { + return push(getLocation().add_(direction.getRelVector())); + } + + @Override + default TileStackGenericContextWO push(RelFace face) { + return push(getLocation(), face); + } + + @Override + default TileGenericContextWO push(RelFace face, int layer) { + return push(getLocation(), face, layer); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/context/TileGenericContextRO.java b/src/main/java/ru/windcorp/progressia/common/world/generic/context/TileGenericContextRO.java new file mode 100644 index 0000000..1088a16 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/generic/context/TileGenericContextRO.java @@ -0,0 +1,74 @@ +/* + * 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.context; + +import ru.windcorp.progressia.common.world.context.Context; +import ru.windcorp.progressia.common.world.generic.*; + +/** + * A {@link Context} referencing a world with a block location, a block face and + * a tile layer specified, effectively pointing to a single tile. The tile may + * or may not actually exist. + */ +//@formatter:off +public interface TileGenericContextRO< + B extends BlockGeneric, + T extends TileGeneric, + E extends EntityGeneric +> extends WorldContexts.Tile, TileStackGenericContextRO { +//@formatter:on + + /** + * Determines whether the relevant position has a tile. + * + * @return {@code true} iff the tile exists + */ + default boolean hasTile() { + return hasTile(getLocation(), getFace(), getLayer()); + } + + /** + * Retrieves the tile at the relevant position. This method may return + * {@code null} in one of three cases: + *

      + *
    • the location is not loaded, + *
    • there is no tile stack on the relevant face, or + *
    • {@code layer} is not less than the amount of tiles in the tile stack. + *
    + * + * @return the tile or {@code null} if the position does not contain a tile + */ + default T getTile() { + return getTile(getLocation(), getFace(), getLayer()); + } + + /* + * Subcontexting + */ + + @Override + default TileGenericContextRO pushCloser() { + return push(getLocation(), getFace(), getLayer() - 1); + } + + @Override + default TileGenericContextRO pushFarther() { + return push(getLocation(), getFace(), getLayer() + 1); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/context/TileGenericContextWO.java b/src/main/java/ru/windcorp/progressia/common/world/generic/context/TileGenericContextWO.java new file mode 100644 index 0000000..1193f51 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/generic/context/TileGenericContextWO.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.generic.context; + +import ru.windcorp.progressia.common.world.context.Context; +import ru.windcorp.progressia.common.world.generic.*; + +/** + * A writable {@link Context} referencing a world with a block location, a block + * face and a tile layer specified, effectively pointing to a single tile. This + * context provides methods for affecting the world. The application of + * requested changes may or may not be immediate, see {@link #isImmediate()}. + * The tile may or may not actually exist. + */ +//@formatter:off +public interface TileGenericContextWO< + B extends BlockGeneric, + T extends TileGeneric, + E extends EntityGeneric +> extends WorldContexts.Tile, TileStackGenericContextWO { +//@formatter:on + + /** + * Requests that the tile relevant to this context be removed from its tile + * stack. If the tile could not be found at the time of application this + * method fails silently. + */ + default void removeTile() { + removeTile(getLocation(), getFace(), getTag()); + } + + /* + * Subcontexting + */ + + @Override + default TileGenericContextWO pushCloser() { + return push(getLocation(), getFace(), getLayer() - 1); + } + + @Override + default TileGenericContextWO pushFarther() { + return push(getLocation(), getFace(), getLayer() + 1); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/context/TileStackGenericContextRO.java b/src/main/java/ru/windcorp/progressia/common/world/generic/context/TileStackGenericContextRO.java new file mode 100644 index 0000000..2a5cd3c --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/generic/context/TileStackGenericContextRO.java @@ -0,0 +1,123 @@ +/* + * 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.context; + +import ru.windcorp.progressia.common.world.context.Context; +import ru.windcorp.progressia.common.world.generic.*; + +/** + * A {@link Context} referencing a world with a block location and a block face + * specified, effectively pointing to a tile stack. The tile stack may or may + * not actually exist. + */ +//@formatter:off +public interface TileStackGenericContextRO< + B extends BlockGeneric, + T extends TileGeneric, + E extends EntityGeneric +> extends WorldContexts.TileStack, BlockGenericContextRO { +//@formatter:on + + /** + * Determines whether the specified position has a tile. Block location and + * block face are implied by the context. + * + * @param layer the layer of the tile + * @return {@code true} iff the tile exists + */ + default boolean hasTile(int layer) { + return hasTile(getLocation(), getFace(), layer); + } + + /** + * Determines whether the specified position has a tile with the given tag. + * Block location and block face are implied by the context. + * + * @param tag the tag of the tile + * @return {@code true} iff the tile exists + */ + default boolean isTagValid(int tag) { + return isTagValid(getLocation(), getFace(), tag); + } + + /** + * Retrieves the tile at the specified position. Block location and block + * face are implied by the context. This method may return {@code null} in + * one of three cases: + *
      + *
    • the location is not loaded, + *
    • there is no tile stack on the relevant face, or + *
    • {@code layer} is not less than the amount of tiles in the tile stack. + *
    + * + * @return the tile or {@code null} if the position does not contain a tile + */ + default T getTile(int layer) { + return getTile(getLocation(), getFace(), layer); + } + + /** + * Retrieves the tile at the specified position and the tile's tag. Block + * location and block face are implied by the context. This + * method may return {@code null} in one of three cases: + *
      + *
    • the location is not loaded, + *
    • there is no tile stack on the relevant face, or + *
    • there is no tile with the specified tag in the tile stack. + *
    + * + * @param tag the tag of the tile + * @return the tile or {@code null} if the position does not contain a tile + */ + default T getTileByTag(int tag) { + return getTileByTag(getLocation(), getFace(), tag); + } + + /** + * Counts the amount of tiles in the specified tile stack. Block location + * and block face are implied by the context. + *

    + * This method returns {@code 0} in case the location is not loaded. + * + * @return the count of tiles in the tile stack or {@code -1} if the tile + * stack could not exist + */ + default int getTileCount() { + return getTileCount(getLocation(), getFace()); + } + + /* + * Subcontexting + */ + + @Override + default TileGenericContextRO push(int layer) { + return push(getLocation(), getFace(), layer); + } + + @Override + default TileStackGenericContextRO pushCounter() { + return push(getFace().getCounter()); + } + + @Override + default TileStackGenericContextRO pushOpposite() { + return push(getLocation().add_(getFace().getRelVector()), getFace().getCounter()); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/context/TileStackGenericContextWO.java b/src/main/java/ru/windcorp/progressia/common/world/generic/context/TileStackGenericContextWO.java new file mode 100644 index 0000000..7a618b9 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/generic/context/TileStackGenericContextWO.java @@ -0,0 +1,82 @@ +/* + * 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.context; + +import ru.windcorp.progressia.common.world.context.Context; +import ru.windcorp.progressia.common.world.generic.*; + +/** + * A writable {@link Context} referencing a world with a block location and a + * block face specified, effectively pointing to a tile stack. This context + * provides methods for affecting the world. The application of requested + * changes may or may not be immediate, see {@link #isImmediate()}. The tile + * stack may or may not actually exist. + */ +//@formatter:off +public interface TileStackGenericContextWO< + B extends BlockGeneric, + T extends TileGeneric, + E extends EntityGeneric +> extends WorldContexts.TileStack, BlockGenericContextWO { +//@formatter:on + + /** + * Requests that a tile is added to the top of the tile stack at the given + * location. The object provided may be stored until the change is applied. + * If the tile could not be added at the time of application this method + * fails silently. The location and the face of the block are implied by the + * context. + * + * @param tile the tile to add + */ + default void addTile(T tile) { + addTile(getLocation(), getFace(), tile); + } + + /** + * Requests that a tile identified by its tag is removed from the specified + * tile stack. If the tile could not be found at the time of application + * this method fails silently. The location and the face of the block are + * implied by the context. + * + * @param tag the tag of the tile to remove + */ + default void removeTile(int tag) { + removeTile(getLocation(), getFace(), tag); + } + + /* + * Subcontexting + */ + + @Override + default TileGenericContextWO push(int layer) { + return push(getLocation(), getFace(), layer); + } + + @Override + default TileStackGenericContextWO pushCounter() { + return push(getFace().getCounter()); + } + + @Override + default TileStackGenericContextWO pushOpposite() { + return push(getLocation().add_(getFace().getRelVector()), getFace().getCounter()); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/context/WorldContexts.java b/src/main/java/ru/windcorp/progressia/common/world/generic/context/WorldContexts.java new file mode 100644 index 0000000..f8953d3 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/generic/context/WorldContexts.java @@ -0,0 +1,368 @@ +/* + * 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.context; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.context.Context; +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.rels.RelRelation; + +/** + * This class defines several {@link Context} subinterfaces that are further + * extended by Generic contexts. These interfaces declare methods for + * determining which location is "relevant" to the context and the basic + * subcontexting methods. Since they are not Java generics they can safely be + * extended more than once. + *

    + * Do not reuse these interfaces outside the Generic contexts' package; consider + * them to be an implementation detail. + * + * @author javapony + */ +class WorldContexts { + + /** + * A {@link Context} with a world instance. This interface should not be + * implemented directly; see {@link WorldGenericContextRO} or + * {@link WorldGenericContextWO}. + * + * @author javapony + */ + public static interface World extends Context { + + /** + * Assigns the specified location to this context. Block face and tile + * layer information is discarded if it was present. See + * {@linkplain Context#subcontexting subcontexting} for more details. + * + * @param location the new location to use + * @return this object + * @see #pop() + */ + Block push(Vec3i location); + + /** + * Assigns the specified location and block face to this context. Tile + * layer information is discarded if it was present. See + * {@linkplain Context#subcontexting subcontexting} for more details. + * + * @param location the new location to use + * @param face the new block face to use + * @return this object + * @see #pop() + */ + TileStack push(Vec3i location, RelFace face); + + /** + * Assigns the specified position to this context. See + * {@linkplain Context#subcontexting subcontexting} for more details. + * + * @param location the new location to use + * @param face the new block face to use + * @param layer the new tile layer to use + * @return this object + * @see #pop() + */ + Tile push(Vec3i location, RelFace face, int layer); + + /** + * Converts the provided location given in the context's coordinate + * space to the underlying absolute coordinate space. + *

    + * The definition of "absolute coordinate space" for contexts that are + * not {@linkplain #isReal() real} may be arbitrary, but methods + * {@link #toAbsolute(Vec3i, Vec3i)}, {@link #toAbsolute(BlockFace)}, + * {@link #toContext(Vec3i, Vec3i)} and {@link #toContext(AbsFace)} are + * guaranteed to be consistent for all contexts. + * + * @param contextLocation the location expressed in context coordinate + * system + * @param output the {@link Vec3i} object to output the result + * into, or {@code null} + * @return The location expressed in absolute coordinate system. If + * {@code output} is not {@code null}, {@code output} is + * returned; otherwise, a new {@link Vec3i} is instantiated and + * returned + */ + Vec3i toAbsolute(Vec3i contextLocation, Vec3i output); + + /** + * Converts the provided location given in the absolute coordinate + * space to the context coordinate space. + *

    + * The definition of "absolute coordinate space" for contexts that are + * not {@linkplain #isReal() real} may be arbitrary, but methods + * {@link #toAbsolute(Vec3i, Vec3i)}, {@link #toAbsolute(BlockFace)}, + * {@link #toContext(Vec3i, Vec3i)} and {@link #toContext(AbsFace)} are + * guaranteed to be consistent for all contexts. + * + * @param contextLocation the location expressed in absolute coordinate + * system + * @param output the {@link Vec3i} object to output the result + * into, or {@code null} + * @return The location expressed in context coordinate system. If + * {@code output} is not {@code null}, {@code output} is + * returned; otherwise, a new {@link Vec3i} is instantiated and + * returned + */ + Vec3i toContext(Vec3i absoluteLocation, Vec3i output); + + /** + * Converts the provided face given in the context's coordinate + * space to the underlying absolute coordinate space. + *

    + * The definition of "absolute coordinate space" for contexts that are + * not {@linkplain #isReal() real} may be arbitrary, but methods + * {@link #toAbsolute(Vec3i, Vec3i)}, {@link #toAbsolute(BlockFace)}, + * {@link #toContext(Vec3i, Vec3i)} and {@link #toContext(AbsFace)} are + * guaranteed to be consistent for all contexts. + * + * @param contextLocation the face expressed in context coordinate + * system + * @return the face expressed in absolute coordinate system + */ + AbsFace toAbsolute(RelFace contextFace); + + /** + * Converts the provided face given in the absolute coordinate + * space to the context coordinate space. + *

    + * The definition of "absolute coordinate space" for contexts that are + * not {@linkplain #isReal() real} may be arbitrary, but methods + * {@link #toAbsolute(Vec3i, Vec3i)}, {@link #toAbsolute(BlockFace)}, + * {@link #toContext(Vec3i, Vec3i)} and {@link #toContext(AbsFace)} are + * guaranteed to be consistent for all contexts. + * + * @param contextLocation the face expressed in absolute coordinate + * system + * @return the face expressed in context coordinate system + */ + RelFace toContext(AbsFace absoluteFace); + + } + + /** + * A {@link Context} with a world instance and a block location. This + * interface + * should not be implemented directly; see {@link BlockGenericContextRO} or + * {@link BlockGenericContextWO}. + * + * @author javapony + */ + public static interface Block extends World { + + /** + * Returns the location of the block. + *

    + * The coordinate system in use is not specified, but it is consistent + * across + * all methods of this context. + *

    + * The object returned by this method must not be modified. It is only + * valid + * while the context is {@linkplain valid}. + * + * @return a vector describing the block's position + */ + Vec3i getLocation(); + + /** + * Shifts the location in the specified direction. Block face and tile + * layer information is discarded if it was present. See + * {@linkplain Context#subcontexting subcontexting} for more details. + * + * @param dx the change of the x component + * @param dy the change of the y component + * @param dz the change of the z component + * @return this object + * @see #pop() + */ + default Block pushRelative(int dx, int dy, int dz) { + return push(getLocation().add_(dx, dy, dz)); + } + + /** + * Shifts the location in the specified direction. Block face and tile + * layer information is discarded if it was present. See + * {@linkplain Context#subcontexting subcontexting} for more details. + * + * @param direction the change added to the current location + * @return this object + * @see #pop() + */ + default Block pushRelative(Vec3i direction) { + return push(getLocation().add_(direction)); + } + + /** + * Shifts the location in the specified direction. Block face and tile + * layer information is discarded if it was present. See + * {@linkplain Context#subcontexting subcontexting} for more details. + * + * @param direction the change added to the current location + * @return this object + * @see #pop() + */ + default Block pushRelative(RelRelation direction) { + return push(getLocation().add_(direction.getRelVector())); + } + + /** + * Assigns the specified block face to this context. Tile layer + * information is discarded if it was present. See + * {@linkplain Context#subcontexting subcontexting} for more details. + * + * @param face the new block face to use + * @return this object + * @see #pop() + */ + default TileStack push(RelFace face) { + return push(getLocation(), face); + } + + /** + * Assigns the specified block face and tile layer to this context. See + * {@linkplain Context#subcontexting subcontexting} for more details. + * + * @param face the new block face to use + * @param layer the new tile layer to use + * @return this object + * @see #pop() + */ + default Tile push(RelFace face, int layer) { + return push(getLocation(), face, layer); + } + + } + + /** + * A {@link Context} with a world instance, a block location and a block + * face + * (block side). This interface should not be implemented directly; see + * {@link TileStackGenericContextRO} or {@link TileStackGenericContextWO}. + * + * @author javapony + */ + public static interface TileStack extends Block { + + /** + * Returns the face relevant to this context. + * + * @return the block face + */ + RelFace getFace(); + + /** + * Assigns the specified tile layer to this context. See + * {@linkplain Context#subcontexting subcontexting} for more details. + * + * @param layer the new tile layer to use + * @return this object + * @see #pop() + */ + default Tile push(int layer) { + return push(getLocation(), getFace(), layer); + } + + /** + * Assigns the counter face (the face on the opposite side of this + * block) to this context. Tile layer information is discarded if it was + * present. See {@linkplain Context#subcontexting subcontexting} for + * more details. + * + * @return this object + * @see #pop() + */ + default TileStack pushCounter() { + return push(getFace().getCounter()); + } + + /** + * Assigns the face opposite to the current face to this context. The + * current face and its opposite are the only two tile stacks occupying + * the gap between the two respective blocks. Tile layer information is + * discarded if it was present. See {@linkplain Context#subcontexting + * subcontexting} for + * more details. + * + * @return this object + * @see #pop() + */ + default TileStack pushOpposite() { + return push(getLocation().add_(getFace().getRelVector()), getFace().getCounter()); + } + + } + + /** + * A {@link Context} with a world instance, a block location, a block face + * (block side) and a tile layer. This interface should not be implemented + * directly; see {@link TileGenericContextRO} or + * {@link TileGenericContextWO}. + * + * @author javapony + */ + public static interface Tile extends TileStack { + + /** + * Returns the tile layer relevant to this context. + * + * @return the tile layer + */ + int getLayer(); + + /** + * Gets the tag of the tile at the relevant position. + * + * @return the tag of the tile or {@code -1} if the location is not + * loaded + * or the tile does not exist + */ + int getTag(); + + /** + * Assigns the tile layer closer to the host block to this context. See + * {@linkplain Context#subcontexting subcontexting} for more details. + * + * @return this object + * @see #pop() + */ + default Tile pushCloser() { + return push(getLocation(), getFace(), getLayer() - 1); + } + + /** + * Assigns the tile layer farther to the host block to this context. See + * {@linkplain Context#subcontexting subcontexting} for more details. + * + * @return this object + * @see #pop() + */ + default Tile pushFarther() { + return push(getLocation(), getFace(), getLayer() + 1); + } + + } + + WorldContexts() { + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/context/WorldGenericContextRO.java b/src/main/java/ru/windcorp/progressia/common/world/generic/context/WorldGenericContextRO.java new file mode 100644 index 0000000..ec188d1 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/generic/context/WorldGenericContextRO.java @@ -0,0 +1,157 @@ +/* + * 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.context; + +import java.util.Collection; +import java.util.function.Consumer; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.context.Context; +import ru.windcorp.progressia.common.world.generic.*; +import ru.windcorp.progressia.common.world.rels.RelFace; + +/** + * A {@link Context} with a world instance. + *

    + * This interfaces defines the entirety of world query methods supported by the + * default contexts. + */ +// @formatter:off +public interface WorldGenericContextRO< + B extends BlockGeneric, + T extends TileGeneric, + E extends EntityGeneric +> extends WorldContexts.World { +// @formatter:on + + /** + * Retrieves the block at the specified location. This method may return + * {@code null} in one of two cases: + *

      + *
    • the location that the block would occupy is not loaded, or + *
    • the corresponding chunk's terrain has not yet generated. + *
    + * + * @param location the location to query + * @return the block or {@code null} if the location is not loaded + */ + B getBlock(Vec3i location); + + /** + * Determines whether the specified location is loaded. + * + * @param location the location to query + * @return {@code true} iff the location is loaded + */ + boolean isLocationLoaded(Vec3i location); + + /** + * Retrieves the tile at the specified position. This method may return + * {@code null} in one of three cases: + *
      + *
    • the location is not loaded, + *
    • there is no tile stack on the specified face, or + *
    • {@code layer} is not less than the amount of tiles in the tile stack. + *
    + * + * @param location location of the host block + * @param face the face of the block that the tile occupies + * @param layer the layer of the tile stack that the tile occupies + * @return the tile or {@code null} if the position does not contain a tile + */ + T getTile(Vec3i location, RelFace face, int layer); + + /** + * Retrieves the tile at the specified position and the tile's tag. This + * method may return {@code null} in one of three cases: + *
      + *
    • the location is not loaded, + *
    • there is no tile stack on the specified face, or + *
    • there is no tile with the specified tag in the tile stack. + *
    + * + * @param location location of the host block + * @param face the face of the block that the tile occupies + * @param tag the tag of the tile + * @return the tile or {@code null} if the position does not contain a tile + */ + T getTileByTag(Vec3i location, RelFace face, int tag); + + /** + * Determines whether the specified position has a tile. + * + * @param location location of the host block + * @param face the face of the block that the tile occupies + * @param layer the layer of the tile + * @return {@code true} iff the tile exists + */ + boolean hasTile(Vec3i location, RelFace face, int layer); + + /** + * Determines whether the specified position has a tile with the given tag. + * + * @param location location of the host block + * @param face the face of the block that the tile occupies + * @param tag the tag of the tile + * @return {@code true} iff the tile exists + */ + boolean isTagValid(Vec3i location, RelFace face, int tag); + + /** + * Counts the amount of tiles in the specified tile stack. + *

    + * This method returns {@code 0} in case the location is not loaded. + * + * @param location location of the host block + * @param face the face of the block that the tile stack occupies + * @return the count of tiles in the tile stack or {@code -1} if the tile + * stack could not exist + */ + int getTileCount(Vec3i location, RelFace face); + + /** + * Retrieves a listing of all entities. {@link #forEachEntity(Consumer)} + * should be used to iterate the collection. The collection is not + * modifiable. + * + * @return all loaded entities + */ + Collection getEntities(); + + /** + * Retrieves the entity with the specified entity ID. + * + * @param entityId the entity ID to look up + * @return the entity found or {@code null} + */ + E getEntity(long entityId); + + /* + * Subcontexting + */ + + @Override + BlockGenericContextRO push(Vec3i location); + + @Override + TileStackGenericContextRO push(Vec3i location, RelFace face); + + @Override + TileGenericContextRO push(Vec3i location, RelFace face, int layer); + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/context/WorldGenericContextWO.java b/src/main/java/ru/windcorp/progressia/common/world/generic/context/WorldGenericContextWO.java new file mode 100644 index 0000000..d1dac01 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/generic/context/WorldGenericContextWO.java @@ -0,0 +1,164 @@ +/* + * 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.context; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.state.StateChange; +import ru.windcorp.progressia.common.state.StatefulObject; +import ru.windcorp.progressia.common.world.context.Context; +import ru.windcorp.progressia.common.world.generic.*; +import ru.windcorp.progressia.common.world.rels.RelFace; + +/** + * A writable {@link Context} with a world instance. This context provides + * methods for affecting the world. The application of requested changes may or + * may not be immediate, see {@link #isImmediate()}. + *

    + * This interfaces defines the entirety of world modification methods supported + * by the default contexts. + */ +// @formatter:off +public interface WorldGenericContextWO< + B extends BlockGeneric, + T extends TileGeneric, + E extends EntityGeneric +> extends WorldContexts.World { +// @formatter:on + + /** + * Queries whether changes requested with this context are guaranteed to be + * applied immediately. + *

    + * When the changes are applied immediately, all subsequent queries will + * reflect the change. When the changes are not applied immediately, none of + * the subsequent queries will be affected by the requests while the context + * is {@linkplain Context#validity valid}. Immediate mode does not change + * while the context is valid. + * + * @return {@code true} iff changes are visible immediately + */ + boolean isImmediate(); + + /** + * Requests that a block is changed. The object provided may be stored until + * the change is applied. + * + * @param location the location of the change + * @param block the new block + * @see #isImmediate() + */ + void setBlock(Vec3i location, B block); + + /** + * Requests that a tile is added to the top of the tile stack at the given + * location. The object provided may be stored until the change is applied. + * If the tile could not be added at the time of application this method + * fails silently. + * + * @param location the location of the block to which the tile is to be + * added + * @param face the face of the block to add the tile to + * @param tile the tile to add + */ + void addTile(Vec3i location, RelFace face, T tile); + + /** + * Requests that a tile identified by its tag is removed from the specified + * tile stack. If the tile could not be found at the time of application + * this method fails silently. + * + * @param location the location of the block from which the tile is to be + * removed + * @param face the of the block to remove the tile from + * @param tag the tag of the tile to remove + */ + void removeTile(Vec3i location, RelFace face, int tag); + + /** + * Requests that the referenced tile is removed from its tile stack. If the + * tile could not be found at the time of application this method fails + * silently. + * + * @param tileReference a reference to the tile + */ + default void removeTile(TileGenericReferenceRO tileReference) { + TileGenericStackRO tileStack = tileReference.getStack(); + + if (tileStack == null) { + return; + } + + removeTile(tileStack.getBlockInWorld(null), tileStack.getFace(), tileReference.getTag()); + } + + /** + * Requests that an entity is added to the world. The object provided may be + * stored until the change is applied. If the entity was already added to + * the world at the time of application this method does nothing. + * + * @param entity the entity to add + * @see #isImmediate() + */ + void addEntity(E entity); + + /** + * Requests that an entity with the given entity ID is removed from the + * world. If the entity did not exist at the time of application this method + * fails silently. + * + * @param entityId the ID of the entity to remove + * @see #isImmediate() + * @see #removeEntity(EntityGeneric) + */ + void removeEntity(long entityId); + + /** + * Requests that the entity is removed from the world. If the entity did not + * exist at the time of application this method fails silently. + * + * @param entity the entity to remove + * @see #isImmediate() + * @see #removeEntity(long) + */ + default void removeEntity(E entity) { + removeEntity(entity.getEntityId()); + } + + /** + * Requests that the specified change is applied to the given entity. The + * {@code change} object provided may be stored until the change is applied. + * + * @param entity the entity to change + * @param change the change to apply + */ + void changeEntity(SE entity, StateChange change); + + /* + * Subcontexting + */ + + @Override + BlockGenericContextWO push(Vec3i location); + + @Override + TileStackGenericContextWO push(Vec3i location, RelFace face); + + @Override + TileGenericContextWO push(Vec3i location, RelFace face, int layer); + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/io/ChunkCodec.java b/src/main/java/ru/windcorp/progressia/common/world/io/ChunkCodec.java index 46207bb..df47640 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/io/ChunkCodec.java +++ b/src/main/java/ru/windcorp/progressia/common/world/io/ChunkCodec.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.io; import java.io.DataInputStream; @@ -25,9 +25,9 @@ import java.io.IOException; import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.common.state.IOContext; import ru.windcorp.progressia.common.util.namespaces.Namespaced; -import ru.windcorp.progressia.common.world.ChunkData; +import ru.windcorp.progressia.common.world.DefaultChunkData; import ru.windcorp.progressia.common.world.DecodingException; -import ru.windcorp.progressia.common.world.WorldData; +import ru.windcorp.progressia.common.world.DefaultWorldData; public abstract class ChunkCodec extends Namespaced { @@ -46,11 +46,12 @@ public abstract class ChunkCodec extends Namespaced { return signature; } - public abstract ChunkData decode(WorldData world, Vec3i position, DataInputStream input, IOContext context) - throws DecodingException, IOException; + public abstract DefaultChunkData decode(DefaultWorldData world, Vec3i position, DataInputStream input, IOContext context) + throws DecodingException, + IOException; - public abstract boolean shouldEncode(ChunkData chunk, IOContext context); + public abstract boolean shouldEncode(DefaultChunkData chunk, IOContext context); - public abstract void encode(ChunkData chunk, DataOutputStream output, IOContext context) throws IOException; + public abstract void encode(DefaultChunkData chunk, DataOutputStream output, IOContext context) throws IOException; } diff --git a/src/main/java/ru/windcorp/progressia/common/world/io/ChunkIO.java b/src/main/java/ru/windcorp/progressia/common/world/io/ChunkIO.java index b02104d..b2a1cd2 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/io/ChunkIO.java +++ b/src/main/java/ru/windcorp/progressia/common/world/io/ChunkIO.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.io; import java.io.DataInputStream; @@ -31,17 +31,18 @@ import gnu.trove.map.TByteObjectMap; import gnu.trove.map.hash.TByteObjectHashMap; import ru.windcorp.progressia.common.state.IOContext; import ru.windcorp.progressia.common.util.crash.CrashReports; -import ru.windcorp.progressia.common.world.ChunkData; +import ru.windcorp.progressia.common.world.DefaultChunkData; import ru.windcorp.progressia.common.world.DecodingException; -import ru.windcorp.progressia.common.world.WorldData; +import ru.windcorp.progressia.common.world.DefaultWorldData; public class ChunkIO { private static final TByteObjectMap CODECS_BY_ID = new TByteObjectHashMap<>(); private static final List CODECS_BY_PRIORITY = new ArrayList<>(); - public static ChunkData load(WorldData world, Vec3i position, DataInputStream data, IOContext context) - throws DecodingException, IOException { + public static DefaultChunkData load(DefaultWorldData world, Vec3i position, DataInputStream data, IOContext context) + throws DecodingException, + IOException { if (CODECS_BY_ID.isEmpty()) throw new IllegalStateException("No codecs registered"); @@ -52,7 +53,8 @@ public class ChunkIO { ChunkCodec codec = getCodec((byte) signature); if (codec == null) { throw new DecodingException( - "Unknown codec signature " + Integer.toHexString(signature) + "; is it from the future?"); + "Unknown codec signature " + Integer.toHexString(signature) + "; is it from the future?" + ); } try { @@ -60,12 +62,19 @@ public class ChunkIO { } catch (IOException | DecodingException e) { throw e; } catch (Throwable t) { - throw CrashReports.report(t, "Codec %s has failed to decode chunk (%d; %d; %d)", codec.getId(), position.x, - position.y, position.z); + throw CrashReports.report( + t, + "Codec %s has failed to decode chunk (%d; %d; %d)", + codec.getId(), + position.x, + position.y, + position.z + ); } } - public static void save(ChunkData chunk, DataOutputStream output, IOContext context) throws IOException { + public static void save(DefaultChunkData chunk, DataOutputStream output, IOContext context) + throws IOException { ChunkCodec codec = getCodec(chunk, context); try { @@ -74,8 +83,14 @@ public class ChunkIO { } catch (IOException e) { throw e; } catch (Throwable t) { - throw CrashReports.report(t, "Codec %s has failed to encode chunk (%d; %d; %d)", codec.getId(), - chunk.getPosition().x, chunk.getPosition().y, chunk.getPosition().z); + throw CrashReports.report( + t, + "Codec %s has failed to encode chunk (%d; %d; %d)", + codec.getId(), + chunk.getPosition().x, + chunk.getPosition().y, + chunk.getPosition().z + ); } } @@ -85,7 +100,7 @@ public class ChunkIO { return CODECS_BY_ID.get(signature); } - public static ChunkCodec getCodec(ChunkData chunk, IOContext context) { + public static ChunkCodec getCodec(DefaultChunkData chunk, IOContext context) { for (ChunkCodec codec : CODECS_BY_PRIORITY) { if (codec.shouldEncode(chunk, context)) { return codec; diff --git a/src/main/java/ru/windcorp/progressia/common/world/rels/AbsFace.java b/src/main/java/ru/windcorp/progressia/common/world/rels/AbsFace.java new file mode 100644 index 0000000..d741bac --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/rels/AbsFace.java @@ -0,0 +1,322 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package ru.windcorp.progressia.common.world.rels; + +import java.util.Objects; +import java.util.function.Function; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +import glm.vec._3.Vec3; +import glm.vec._3.i.Vec3i; + +public final class AbsFace extends AbsRelation implements BlockFace { + + // @formatter:off + public static final AbsFace + POS_Z = new AbsFace( 0, 0, +1, true, "POS_Z"), + NEG_Z = new AbsFace( 0, 0, -1, false, "NEG_Z"), + POS_X = new AbsFace(+1, 0, 0, true, "POS_X"), + NEG_X = new AbsFace(-1, 0, 0, false, "NEG_X"), + POS_Y = new AbsFace( 0, +1, 0, false, "POS_Y"), + NEG_Y = new AbsFace( 0, -1, 0, true, "NEG_Y"); + // @formatter:on + + private static final ImmutableList ALL_FACES = ImmutableList.of(POS_Z, NEG_Z, POS_X, NEG_X, POS_Y, NEG_Y); + + static { + link(POS_Z, NEG_Z); + link(POS_X, NEG_X); + link(POS_Y, NEG_Y); + } + + private static final ImmutableList PRIMARY_FACES = ALL_FACES.stream().filter(AbsFace::isPrimary) + .collect(ImmutableList.toImmutableList()); + + private static final ImmutableList SECONDARY_FACES = ALL_FACES.stream().filter(AbsFace::isSecondary) + .collect(ImmutableList.toImmutableList()); + + 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(AbsFace a, AbsFace b) { + a.counterFace = b; + b.counterFace = a; + } + + public static ImmutableMap mapToFaces( + E posZ, + E negZ, + E posX, + E negX, + E negY, + E posY + ) { + return ImmutableMap.builderWithExpectedSize(6) + .put(POS_Z, posZ) + .put(NEG_Z, negZ) + .put(POS_X, posX) + .put(NEG_X, negX) + .put(NEG_Y, negY) + .put(POS_Y, posY) + .build(); + } + + public static ImmutableMap mapToFaces(Function generator) { + return mapToFaces( + generator.apply(POS_Z), + generator.apply(NEG_Z), + generator.apply(POS_X), + generator.apply(NEG_X), + generator.apply(NEG_Y), + generator.apply(POS_Y) + ); + } + + /** + * Rounds the provided vector to one of {@link AbsFace}s. The returned face + * is pointing in the same general direction as the provided vector. The + * result is undefined for arguments where two largest in absolute values + * coordinates are equal (e.g. for {@code (5; -5; 2)}). For a zero vector + * the result is {@code null}. Infinite vectors are handled correctly. + * + * @param vector the vector to round + * @return the face most adequately describing the provided vector, or + * {@code null} iff {@code vector.x = vector.y = vector.z = 0} + * @throws IllegalArgumentException if one of the coordinates is a NaN + */ + public static AbsFace roundToFace(Vec3 vector) { + Objects.requireNonNull(vector, "vector"); + return roundToFace(vector.x, vector.y, vector.z); + } + + /** + * Rounds the provided vector to one of {@link AbsFace}s. The returned face + * is pointing in the same general direction as the provided vector. The + * result is undefined for arguments where two largest in absolute values + * coordinates are equal (e.g. for {@code (5; -5; 2)}). For a zero vector + * the result is {@code null}. Infinite arguments are handled correctly. + * + * @param x the X coordinate + * @param y the Y coordinate + * @param z the Z coordinate + * @return the face most adequately describing the provided vector, or + * {@code null} iff {@code x = y = z = 0} + * @throws IllegalArgumentException if one of the coordinates is a NaN + */ + public static AbsFace roundToFace(float x, float y, float z) { + if (x == 0 && y == 0 && z == 0) { + return null; + } + + if (Float.isNaN(x) || Float.isNaN(y) || Float.isNaN(z)) { + throw new IllegalArgumentException("Vector contains NaN: (" + x + "; " + y + "; " + z + ")"); + } + + // The following code handles infinite x, y or z properly + + float absX = Math.abs(x); + float absY = Math.abs(y); + float absZ = Math.abs(z); + + if (absX > absY) { + if (absX > absZ) { + return x > 0 ? POS_X : NEG_X; + } else { + // Z is the answer; exit decision tree + } + } else { + if (absY > absZ) { + return y > 0 ? POS_Y : NEG_Y; + } else { + // Z is the answer; exit decision tree + } + } + + return z > 0 ? POS_Z : NEG_Z; + } + + /** + * Rounds the provided vector to one of {@link AbsFace}s. The returned face + * is pointing in the same general direction as the provided vector. The + * result is undefined for arguments where two largest in absolute values + * coordinates are equal (e.g. for {@code (5; -5; 2)}). For a zero vector + * the result is {@code null}. + * + * @param vector the vector to round + * @return the face most adequately describing the provided vector, or + * {@code null} iff {@code vector.x = vector.y = vector.z = 0} + */ + public static AbsFace roundToFace(Vec3i vector) { + Objects.requireNonNull(vector, "vector"); + return roundToFace(vector.x, vector.y, vector.z); + } + + /** + * Rounds the provided vector to one of {@link AbsFace}s. The returned face + * is pointing in the same general direction as the provided vector. The + * result is undefined for arguments where two largest in absolute values + * coordinates are equal (e.g. for {@code (5; -5; 2)}). For a zero vector + * the result is {@code null}. + * + * @param x the X coordinate + * @param y the Y coordinate + * @param z the Z coordinate + * @return the face most adequately describing the provided vector, or + * {@code null} iff {@code x = y = z = 0} + */ + public static AbsFace roundToFace(int x, int y, int z) { + if (x == 0 && y == 0 && z == 0) { + return null; + } + + int absX = Math.abs(x); + int absY = Math.abs(y); + int absZ = Math.abs(z); + + if (absX > absY) { + if (absX > absZ) { + return x > 0 ? POS_X : NEG_X; + } else { + // Z is the answer; exit decision tree + } + } else { + if (absY > absZ) { + return y > 0 ? POS_Y : NEG_Y; + } else { + // Z is the answer; exit decision tree + } + } + + return z > 0 ? POS_Z : NEG_Z; + } + + private static int nextId = 0; + + private final int id; + private final String name; + private AbsFace counterFace; + private final boolean isPrimary; + + private AbsFace(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; + } + + @Override + public AbsFace resolve(AbsFace up) { + return this; + } + + @Override + public RelFace relativize(AbsFace up) { + return BlockFaceResolver.relativize(this, up); + } + + public boolean isPrimary() { + return isPrimary; + } + + public AbsFace getPrimary() { + if (isPrimary) + return this; + else + return counterFace; + } + + public AbsFace getPrimaryAndMoveCursor(Vec3i cursor) { + if (isPrimary) + return this; + + cursor.add(getVector()); + return counterFace; + } + + public boolean isSecondary() { + return !isPrimary; + } + + public AbsFace getSecondary() { + if (isPrimary) + return counterFace; + else + return this; + } + + public AbsFace getSecondaryAndMoveCursor(Vec3i cursor) { + if (!isPrimary) + return this; + + cursor.add(getVector()); + return counterFace; + } + + public AbsFace getCounter() { + return counterFace; + } + + public AbsFace 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/rels/AbsRelation.java b/src/main/java/ru/windcorp/progressia/common/world/rels/AbsRelation.java new file mode 100644 index 0000000..b14abdd --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/rels/AbsRelation.java @@ -0,0 +1,88 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package ru.windcorp.progressia.common.world.rels; + +import glm.vec._3.Vec3; +import glm.vec._3.i.Vec3i; + +public class AbsRelation extends BlockRelation { + + private final Vec3i vector = new Vec3i(); + private final Vec3 floatVector = new Vec3(); + private final Vec3 normalized = new Vec3(); + + public AbsRelation(int x, int y, int z) { + vector.set(x, y, z); + floatVector.set(x, y, z); + normalized.set(x, y, z); + + if (x != 0 || y != 0 || z != 0) { + normalized.normalize(); + } + } + + public AbsRelation(Vec3i vector) { + this(vector.x, vector.y, vector.z); + } + + public static AbsRelation of(Vec3i vector) { + return of(vector.x, vector.y, vector.z); + } + + public static AbsRelation of(int x, int y, int z) { + if (Math.abs(x) + Math.abs(y) + Math.abs(z) == 1) { + return AbsFace.roundToFace(x, y, z); + } + + return new AbsRelation(x, y, z); + } + + @Override + public AbsRelation resolve(AbsFace up) { + return this; + } + + @Override + public Vec3i getVector(AbsFace up) { + return vector; + } + + @Override + public Vec3 getFloatVector(AbsFace up) { + return floatVector; + } + + @Override + public Vec3 getNormalized(AbsFace up) { + return normalized; + } + + public Vec3i getVector() { + return vector; + } + + public Vec3 getFloatVector() { + return floatVector; + } + + public Vec3 getNormalized() { + return normalized; + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/rels/AxisRotations.java b/src/main/java/ru/windcorp/progressia/common/world/rels/AxisRotations.java new file mode 100644 index 0000000..2d13c19 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/rels/AxisRotations.java @@ -0,0 +1,193 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ru.windcorp.progressia.common.world.rels; + +import static ru.windcorp.progressia.common.util.VectorUtil.SignedAxis.NEG_X; +import static ru.windcorp.progressia.common.util.VectorUtil.SignedAxis.NEG_Y; +import static ru.windcorp.progressia.common.util.VectorUtil.SignedAxis.NEG_Z; +import static ru.windcorp.progressia.common.util.VectorUtil.SignedAxis.POS_X; +import static ru.windcorp.progressia.common.util.VectorUtil.SignedAxis.POS_Y; +import static ru.windcorp.progressia.common.util.VectorUtil.SignedAxis.POS_Z; + +import java.util.Map; + +import glm.mat._3.Mat3; +import glm.mat._4.Mat4; +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.SignedAxis; + +public class AxisRotations { + + private static class Rotation { + private static class MyMat3i { + private final int m00, m01, m02, m10, m11, m12, m20, m21, m22; + + public MyMat3i(Mat3 integerMatrix) { + this.m00 = (int) integerMatrix.m00; + this.m01 = (int) integerMatrix.m01; + this.m02 = (int) integerMatrix.m02; + this.m10 = (int) integerMatrix.m10; + this.m11 = (int) integerMatrix.m11; + this.m12 = (int) integerMatrix.m12; + this.m20 = (int) integerMatrix.m20; + this.m21 = (int) integerMatrix.m21; + this.m22 = (int) integerMatrix.m22; + } + + public Vec3i mul(Vec3i right, Vec3i res) { + res.set( + m00 * right.x + m10 * right.y + m20 * right.z, + m01 * right.x + m11 * right.y + m21 * right.z, + m02 * right.x + m12 * right.y + m22 * right.z + ); + return res; + } + } + + private final Mat3 resolutionMatrix3 = new Mat3(); + private final Mat4 resolutionMatrix4 = new Mat4(); + private final MyMat3i resolutionMatrix3i; + + private final Mat3 relativizationMatrix3 = new Mat3(); + private final Mat4 relativizationMatrix4 = new Mat4(); + private final MyMat3i relativizationMatrix3i; + + private Rotation(SignedAxis northDestination, SignedAxis westDestination, SignedAxis upDestination) { + resolutionMatrix3.c0(computeUnitVectorAlong(northDestination)); + resolutionMatrix3.c1(computeUnitVectorAlong(westDestination)); + resolutionMatrix3.c2(computeUnitVectorAlong(upDestination)); + + resolutionMatrix3.toMat4(resolutionMatrix4); + resolutionMatrix3i = new MyMat3i(resolutionMatrix3); + + relativizationMatrix3.set(resolutionMatrix3).transpose(); + relativizationMatrix4.set(resolutionMatrix4).transpose(); + relativizationMatrix3i = new MyMat3i(relativizationMatrix3); + } + + private static Vec3 computeUnitVectorAlong(SignedAxis signedAxis) { + Vec3 result = new Vec3(0, 0, 0); + VectorUtil.set(result, signedAxis.getAxis(), signedAxis.getSign()); + return result; + } + + /** + * @return the resolutionMatrix3 + */ + public Mat3 getResolutionMatrix3() { + return resolutionMatrix3; + } + + /** + * @return the resolutionMatrix4 + */ + public Mat4 getResolutionMatrix4() { + return resolutionMatrix4; + } + + /** + * @return the relativizationMatrix3 + */ + public Mat3 getRelativizationMatrix3() { + return relativizationMatrix3; + } + + /** + * @return the relativizationMatrix4 + */ + public Mat4 getRelativizationMatrix4() { + return relativizationMatrix4; + } + + public Vec3i resolve(Vec3i output, Vec3i input) { + if (output == null) { + output = new Vec3i(); + } + resolutionMatrix3i.mul(input, output); + return output; + } + + public Vec3i relativize(Vec3i output, Vec3i input) { + if (output == null) { + output = new Vec3i(); + } + relativizationMatrix3i.mul(input, output); + return output; + } + + public Vec3 resolve(Vec3 output, Vec3 input) { + if (output == null) { + output = new Vec3(); + } + resolutionMatrix3.mul(input, output); + return output; + } + + public Vec3 relativize(Vec3 output, Vec3 input) { + if (output == null) { + output = new Vec3(); + } + relativizationMatrix3.mul(input, output); + return output; + } + } + + private final static Map TRANSFORMATIONS = AbsFace.mapToFaces( + new Rotation(POS_X, POS_Y, POS_Z), + new Rotation(POS_X, NEG_Y, NEG_Z), + new Rotation(POS_Z, NEG_Y, POS_X), + new Rotation(POS_Z, POS_Y, NEG_X), + new Rotation(POS_Z, NEG_X, NEG_Y), + new Rotation(POS_Z, POS_X, POS_Y) + ); + + public static Vec3i resolve(Vec3i relative, AbsFace up, Vec3i output) { + return TRANSFORMATIONS.get(up).resolve(output, relative); + } + + public static Vec3 resolve(Vec3 relative, AbsFace up, Vec3 output) { + return TRANSFORMATIONS.get(up).resolve(output, relative); + } + + public static Vec3i relativize(Vec3i absolute, AbsFace up, Vec3i output) { + return TRANSFORMATIONS.get(up).relativize(output, absolute); + } + + public static Vec3 relativize(Vec3 absolute, AbsFace up, Vec3 output) { + return TRANSFORMATIONS.get(up).relativize(output, absolute); + } + + public static Mat3 getResolutionMatrix3(AbsFace up) { + return TRANSFORMATIONS.get(up).getResolutionMatrix3(); + } + + public static Mat4 getResolutionMatrix4(AbsFace up) { + return TRANSFORMATIONS.get(up).getResolutionMatrix4(); + } + + public static Mat3 getRelativizationMatrix3(AbsFace up) { + return TRANSFORMATIONS.get(up).getRelativizationMatrix3(); + } + + public static Mat4 getRelativizationMatrix4(AbsFace up) { + return TRANSFORMATIONS.get(up).getRelativizationMatrix4(); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/rels/BlockFace.java b/src/main/java/ru/windcorp/progressia/common/world/rels/BlockFace.java new file mode 100644 index 0000000..bdbccaf --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/rels/BlockFace.java @@ -0,0 +1,42 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ru.windcorp.progressia.common.world.rels; + +import glm.vec._3.Vec3; +import glm.vec._3.i.Vec3i; + +public interface BlockFace { + + public static final int BLOCK_FACE_COUNT = 6; + + AbsFace resolve(AbsFace up); + RelFace relativize(AbsFace up); + + public default Vec3i getVector(AbsFace up) { + return resolve(up).getVector(); + } + + public default Vec3 getFloatVector(AbsFace up) { + return resolve(up).getFloatVector(); + } + + public default Vec3 getNormalized(AbsFace up) { + return resolve(up).getNormalized(); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/rels/BlockFaceResolver.java b/src/main/java/ru/windcorp/progressia/common/world/rels/BlockFaceResolver.java new file mode 100644 index 0000000..e1f469e --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/rels/BlockFaceResolver.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.common.world.rels; + +import java.util.Objects; + +import static ru.windcorp.progressia.common.world.rels.AbsFace.BLOCK_FACE_COUNT; + +public class BlockFaceResolver { + + /** + * A mapping from (up; relative) to absolute. Face IDs are used as keys. + */ + private static final AbsFace[][] RESOLUTION_TABLE = new AbsFace[BLOCK_FACE_COUNT][BLOCK_FACE_COUNT]; + + /** + * A mapping from (up; absolute) to relative. Face IDs are used as keys. + */ + private static final RelFace[][] RELATIVIZATION_TABLE = new RelFace[BLOCK_FACE_COUNT][BLOCK_FACE_COUNT]; + + static { + for (AbsFace up : AbsFace.getFaces()) { + for (RelFace relative : RelFace.getFaces()) { + + AbsFace absolute = (AbsFace) AbsRelation.of(AxisRotations.resolve(relative.getRelVector(), up, null)); + + RESOLUTION_TABLE[up.getId()][relative.getId()] = absolute; + RELATIVIZATION_TABLE[up.getId()][absolute.getId()] = relative; + + } + } + } + + public static AbsFace resolve(RelFace relative, AbsFace up) { + Objects.requireNonNull(relative, "relative"); + Objects.requireNonNull(up, "up"); + + if (relative == RelFace.UP) { + return up; + } else if (relative == RelFace.DOWN) { + return up.getCounter(); + } + + return RESOLUTION_TABLE[up.getId()][relative.getId()]; + } + + public static RelFace relativize(AbsFace absolute, AbsFace up) { + Objects.requireNonNull(absolute, "absolute"); + Objects.requireNonNull(up, "up"); + + if (absolute == up) { + return RelFace.UP; + } else if (absolute.getCounter() == up) { + return RelFace.DOWN; + } + + return RELATIVIZATION_TABLE[up.getId()][absolute.getId()]; + } + + private BlockFaceResolver() { + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/block/BlockRelation.java b/src/main/java/ru/windcorp/progressia/common/world/rels/BlockRelation.java similarity index 71% rename from src/main/java/ru/windcorp/progressia/common/world/block/BlockRelation.java rename to src/main/java/ru/windcorp/progressia/common/world/rels/BlockRelation.java index ce6997e..f235fc4 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/block/BlockRelation.java +++ b/src/main/java/ru/windcorp/progressia/common/world/rels/BlockRelation.java @@ -15,8 +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.block; +package ru.windcorp.progressia.common.world.rels; import static java.lang.Math.abs; import static java.lang.Math.max; @@ -24,34 +23,26 @@ import static java.lang.Math.max; import glm.vec._3.Vec3; import glm.vec._3.i.Vec3i; -public class BlockRelation { +public abstract class BlockRelation { - private final Vec3i vector = new Vec3i(); - private final Vec3 floatVector = new Vec3(); - private final Vec3 normalized = new Vec3(); - - public BlockRelation(int x, int y, int z) { - vector.set(x, y, z); - floatVector.set(x, y, z); - normalized.set(x, y, z).normalize(); + public abstract AbsRelation resolve(AbsFace up); + + public Vec3i getVector(AbsFace up) { + return resolve(up).getVector(); } - - public BlockRelation(Vec3i vector) { - this(vector.x, vector.y, vector.z); + + public Vec3 getFloatVector(AbsFace up) { + return resolve(up).getFloatVector(); } - - public Vec3i getVector() { - return vector; + + public Vec3 getNormalized(AbsFace up) { + return resolve(up).getNormalized(); } - - public Vec3 getFloatVector() { - return floatVector; + + protected Vec3i getSample() { + return getVector(AbsFace.POS_Z); } - - public Vec3 getNormalized() { - return normalized; - } - + /** * Returns the distance between the source and destination blocks, as * defined by the Euclidean space. Your everyday distance. @@ -59,19 +50,20 @@ public class BlockRelation { * @return square root of the sum of the squares of the coordinates */ public float getEuclideanDistance() { - return vector.length(); + return getSample().length(); } /** * Returns the Manhattan distance, also known as the taxicab distance, * between the source and the destination blocks. Manhattan distance is - * defined as the sum of the absolute values of the coordinates, which is - * also the minimum amount of block faces that need to be crossed to move - * from source to destination. + * defined as the sum of the absolute values of the coordinates, + * which is also the minimum amount of block faces that need to be crossed + * to move from source to destination. * * @return the sum of the absolute values of the coordinates */ public int getManhattanDistance() { + Vec3i vector = getSample(); return abs(vector.x) + abs(vector.y) + abs(vector.z); } @@ -83,7 +75,8 @@ public class BlockRelation { * @return the maximum of the absolute values of the coordinates */ public int getChebyshevDistance() { + Vec3i vector = getSample(); return max(abs(vector.x), max(abs(vector.y), abs(vector.z))); } - + } diff --git a/src/main/java/ru/windcorp/progressia/common/world/rels/RelFace.java b/src/main/java/ru/windcorp/progressia/common/world/rels/RelFace.java new file mode 100644 index 0000000..99e97fb --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/rels/RelFace.java @@ -0,0 +1,155 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ru.windcorp.progressia.common.world.rels; + +import java.util.function.Function; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +import glm.vec._3.i.Vec3i; + +public class RelFace extends RelRelation implements BlockFace { + + // @formatter:off + public static final RelFace + UP = new RelFace( 0, 0, +1, "UP"), + DOWN = new RelFace( 0, 0, -1, "DOWN"), + NORTH = new RelFace(+1, 0, 0, "NORTH"), + SOUTH = new RelFace(-1, 0, 0, "SOUTH"), + WEST = new RelFace( 0, +1, 0, "WEST"), + EAST = new RelFace( 0, -1, 0, "EAST"); + // @formatter:on + + private static final ImmutableList ALL_FACES = ImmutableList.of(UP, DOWN, NORTH, SOUTH, WEST, EAST); + + static { + link(UP, DOWN); + link(NORTH, SOUTH); + link(WEST, EAST); + } + + public static ImmutableList getFaces() { + return ALL_FACES; + } + + private static void link(RelFace a, RelFace b) { + a.counterFace = b; + b.counterFace = a; + } + + public static ImmutableMap mapToFaces( + E up, + E down, + E north, + E south, + E west, + E east + ) { + return ImmutableMap.builderWithExpectedSize(6) + .put(UP, up) + .put(DOWN, down) + .put(NORTH, north) + .put(SOUTH, south) + .put(WEST, west) + .put(EAST, east) + .build(); + } + + public static ImmutableMap mapToFaces(Function generator) { + return mapToFaces( + generator.apply(UP), + generator.apply(DOWN), + generator.apply(NORTH), + generator.apply(SOUTH), + generator.apply(WEST), + generator.apply(EAST) + ); + } + + private static int nextId = 0; + + private final int id; + private final String name; + private RelFace counterFace; + + private RelFace(int x, int y, int z, String name) { + super(x, y, z, false); + this.id = nextId++; + this.name = name; + } + + public String getName() { + return name; + } + + @Override + public AbsFace resolve(AbsFace up) { + return BlockFaceResolver.resolve(this, up); + } + + @Override + public RelFace relativize(AbsFace up) { + return this; + } + + public RelFace rotate(AbsFace fromUp, AbsFace toUp) { + if (fromUp == toUp) { + return this; + } + + return resolve(fromUp).relativize(toUp); + } + + /** + * @return the id + */ + public int getId() { + return id; + } + + public RelFace getCounter() { + return counterFace; + } + + public RelFace getCounterAndMoveCursor(Vec3i cursor) { + cursor.add(getRelVector()); + return counterFace; + } + + @Override + public float getEuclideanDistance() { + return 1.0f; + } + + @Override + public int getChebyshevDistance() { + return 1; + } + + @Override + public int getManhattanDistance() { + return 1; + } + + @Override + public String toString() { + return getName(); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/rels/RelRelation.java b/src/main/java/ru/windcorp/progressia/common/world/rels/RelRelation.java new file mode 100644 index 0000000..6eec211 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/rels/RelRelation.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.common.world.rels; + +import glm.vec._3.Vec3; +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.util.Vectors; + +/** + * Name stands for Relative Relation + */ +public class RelRelation extends BlockRelation { + + private final Vec3i vector = new Vec3i(); + private final Vec3 floatVector = new Vec3(); + private final Vec3 normalized = new Vec3(); + + private AbsRelation[] resolved = null; + + public RelRelation(int north, int west, int up) { + this(north, west, up, false); + } + + public RelRelation(Vec3i vector) { + this(vector.x, vector.y, vector.z, false); + } + + protected RelRelation(int north, int west, int up, boolean precomputeAllResolutions) { + vector.set(north, west, up); + floatVector.set(north, west, up); + normalized.set(north, west, up); + + if (normalized.any()) { + normalized.normalize(); + } + + if (precomputeAllResolutions) { + for (AbsFace face : AbsFace.getFaces()) { + resolve(face); + } + } + } + + public static RelRelation of(Vec3i vector) { + return of(vector.x, vector.y, vector.z); + } + + public static RelRelation of(int north, int west, int up) { + if (Math.abs(north) + Math.abs(west) + Math.abs(up) == 1) { + if (up == 1) { + return RelFace.UP; + } else if (up == -1) { + return RelFace.DOWN; + } else if (north == 1) { + return RelFace.NORTH; + } else if (north == -1) { + return RelFace.SOUTH; + } else if (west == 1) { + return RelFace.WEST; + } else { + assert west == -1; + return RelFace.EAST; + } + } + + return new RelRelation(north, west, up); + } + + /** + * @return the relative vector (northward, westward, upward) + */ + public Vec3i getRelVector() { + return vector; + } + + public Vec3 getRelFloatVector() { + return floatVector; + } + + public Vec3 getRelNormalized() { + return normalized; + } + + public int getNorthward() { + return vector.x; + } + + public int getWestward() { + return vector.y; + } + + public int getUpward() { + return vector.z; + } + + public int getSouthward() { + return -getNorthward(); + } + + public int getEastward() { + return -getWestward(); + } + + public int getDownward() { + return -getUpward(); + } + + @Override + public AbsRelation resolve(AbsFace up) { + if (resolved == null) { + resolved = new AbsRelation[AbsFace.BLOCK_FACE_COUNT]; + } + + if (resolved[up.getId()] == null) { + resolved[up.getId()] = computeResolution(up); + } + + return resolved[up.getId()]; + } + + private AbsRelation computeResolution(AbsFace up) { + Vec3i resolution = Vectors.grab3i(); + AxisRotations.resolve(vector, up, resolution); + AbsRelation result = AbsRelation.of(resolution); + Vectors.release(resolution); + return result; + } + + @Override + protected Vec3i getSample() { + return getRelVector(); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/tile/PacketAddTile.java b/src/main/java/ru/windcorp/progressia/common/world/tile/PacketAddTile.java index bfc47c2..a7fa26e 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/tile/PacketAddTile.java +++ b/src/main/java/ru/windcorp/progressia/common/world/tile/PacketAddTile.java @@ -24,8 +24,8 @@ 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.block.BlockFace; +import ru.windcorp.progressia.common.world.DefaultWorldData; +import ru.windcorp.progressia.common.world.rels.AbsFace; public class PacketAddTile extends PacketAffectTile { @@ -43,8 +43,8 @@ public class PacketAddTile extends PacketAffectTile { return tileId; } - public void set(TileData tile, Vec3i blockInWorld, BlockFace face) { - super.set(blockInWorld, face, -1); + public void set(TileData tile, Vec3i blockInWorld, AbsFace face) { + super.set(blockInWorld, face, TAG_NOT_APPLICABLE); this.tileId = tile.getId(); } @@ -61,7 +61,7 @@ public class PacketAddTile extends PacketAffectTile { } @Override - public void apply(WorldData world) { + public void apply(DefaultWorldData world) { TileData tile = TileDataRegistry.getInstance().get(getTileId()); world.getTiles(getBlockInWorld(), getFace()).add(tile); } diff --git a/src/main/java/ru/windcorp/progressia/common/world/tile/PacketAffectTile.java b/src/main/java/ru/windcorp/progressia/common/world/tile/PacketAffectTile.java index 07d9105..747dfa6 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/tile/PacketAffectTile.java +++ b/src/main/java/ru/windcorp/progressia/common/world/tile/PacketAffectTile.java @@ -26,14 +26,20 @@ import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.common.world.Coordinates; import ru.windcorp.progressia.common.world.DecodingException; import ru.windcorp.progressia.common.world.PacketAffectChunk; -import ru.windcorp.progressia.common.world.block.BlockFace; +import ru.windcorp.progressia.common.world.rels.AbsFace; public abstract class PacketAffectTile extends PacketAffectChunk { private final Vec3i blockInWorld = new Vec3i(); - private BlockFace face; + private AbsFace face; private int tag; + /** + * Indicates to the safeguards in {@link #set(Vec3i, AbsFace, int)} that the + * concept of a tile tag is not applicable to this action. + */ + protected static final int TAG_NOT_APPLICABLE = -2; + public PacketAffectTile(String id) { super(id); } @@ -42,7 +48,7 @@ public abstract class PacketAffectTile extends PacketAffectChunk { return blockInWorld; } - public BlockFace getFace() { + public AbsFace getFace() { return face; } @@ -50,7 +56,11 @@ public abstract class PacketAffectTile extends PacketAffectChunk { return tag; } - public void set(Vec3i blockInWorld, BlockFace face, int tag) { + public void set(Vec3i blockInWorld, AbsFace face, int tag) { + if (tag < 0 && tag != TAG_NOT_APPLICABLE) { + throw new IllegalArgumentException("Cannot affect tile with tag " + tag); + } + this.blockInWorld.set(blockInWorld.x, blockInWorld.y, blockInWorld.z); this.face = face; this.tag = tag; @@ -59,7 +69,7 @@ public abstract class PacketAffectTile extends PacketAffectChunk { @Override public void read(DataInput input) throws IOException, DecodingException { this.blockInWorld.set(input.readInt(), input.readInt(), input.readInt()); - this.face = BlockFace.getFaces().get(input.readByte()); + this.face = AbsFace.getFaces().get(input.readByte()); this.tag = input.readInt(); } diff --git a/src/main/java/ru/windcorp/progressia/common/world/tile/PacketRemoveTile.java b/src/main/java/ru/windcorp/progressia/common/world/tile/PacketRemoveTile.java index 7e9fb47..f98dc63 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/tile/PacketRemoveTile.java +++ b/src/main/java/ru/windcorp/progressia/common/world/tile/PacketRemoveTile.java @@ -25,8 +25,9 @@ import java.io.IOException; import glm.vec._3.i.Vec3i; 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.block.BlockFace; +import ru.windcorp.progressia.common.world.DefaultWorldData; +import ru.windcorp.progressia.common.world.TileDataStack; +import ru.windcorp.progressia.common.world.rels.AbsFace; public class PacketRemoveTile extends PacketAffectTile { @@ -39,7 +40,7 @@ public class PacketRemoveTile extends PacketAffectTile { } @Override - public void set(Vec3i blockInWorld, BlockFace face, int tag) { + public void set(Vec3i blockInWorld, AbsFace face, int tag) { super.set(blockInWorld, face, tag); } @@ -54,7 +55,7 @@ public class PacketRemoveTile extends PacketAffectTile { } @Override - public void apply(WorldData world) { + public void apply(DefaultWorldData world) { TileDataStack stack = world.getTiles(getBlockInWorld(), getFace()); int index = stack.getIndexByTag(getTag()); diff --git a/src/main/java/ru/windcorp/progressia/common/world/tile/TileData.java b/src/main/java/ru/windcorp/progressia/common/world/tile/TileData.java index b8f42aa..8397be9 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/tile/TileData.java +++ b/src/main/java/ru/windcorp/progressia/common/world/tile/TileData.java @@ -19,9 +19,9 @@ package ru.windcorp.progressia.common.world.tile; import ru.windcorp.progressia.common.util.namespaces.Namespaced; -import ru.windcorp.progressia.common.world.generic.GenericTile; +import ru.windcorp.progressia.common.world.generic.TileGeneric; -public class TileData extends Namespaced implements GenericTile { +public class TileData extends Namespaced implements TileGeneric { public TileData(String id) { super(id); diff --git a/src/main/java/ru/windcorp/progressia/common/world/tile/TileDataStack.java b/src/main/java/ru/windcorp/progressia/common/world/tile/TileDataStack.java deleted file mode 100644 index d9a60b5..0000000 --- a/src/main/java/ru/windcorp/progressia/common/world/tile/TileDataStack.java +++ /dev/null @@ -1,168 +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.tile; - -import ru.windcorp.progressia.common.world.ChunkData; -import ru.windcorp.progressia.common.world.block.BlockData; -import ru.windcorp.progressia.common.world.generic.GenericTileStack; - -public abstract class TileDataStack extends GenericTileStack { - - /** - * Inserts the specified tile at the specified position in this stack. - * Shifts the tile currently at that position (if any) and any tiles above - * to the top (adds one to their indices). - * - * @param index - * index at which the specified tile is to be inserted - * @param tile - * tile to be inserted - * @throws TileStackIsFullException - * if this stack is {@linkplain #isFull() full} - */ - /* - * Impl note: AbstractList provides a useless implementation of this method, - * make sure to override it in subclass - */ - @Override - public abstract void add(int index, TileData tile); - - /** - * Adds the specified tile at the end of this stack assigning it the - * provided tag. This method is useful for copying stacks when preserving - * tags is necessary. - * - * @param tile - * the tile to add - * @param tag - * the tag to assign the new tile - * @throws IllegalArgumentException - * if this stack already contains a tile with the provided tag - */ - public abstract void load(TileData tile, int tag); - - /** - * Replaces the tile at the specified position in this stack with the - * specified tile. - * - * @param index - * index of the tile to replace - * @param tile - * tile to be stored at the specified position - * @return the tile previously at the specified position - */ - /* - * Impl note: AbstractList provides a useless implementation of this method, - * make sure to override it in subclass - */ - @Override - public abstract TileData set(int index, TileData tile); - - /** - * Removes the tile at the specified position in this list. Shifts any - * subsequent tiles to the left (subtracts one from their indices). Returns - * the tile that was removed from the list. - * - * @param index - * the index of the tile to be removed - * @return the tile previously at the specified position - */ - /* - * Impl note: AbstractList provides a useless implementation of this method, - * make sure to override it in subclass - */ - @Override - public abstract TileData remove(int index); - - public abstract TileReference getReference(int index); - - public abstract int getIndexByTag(int tag); - - public abstract int getTagByIndex(int index); - - /* - * Aliases and overloads - */ - - public void addClosest(TileData tile) { - add(0, tile); - } - - public void addFarthest(TileData tile) { - add(size(), tile); - } - - /** - * Attempts to {@link #add(int, TileData) add} the provided {@code tile} at - * {@code index}. If the stack is {@linkplain #isFull() full}, does nothing. - * - * @param index - * the index to insert the tile at - * @param tile - * the tile to try to add - * @return {@code true} iff this stack has changed - */ - public boolean offer(int index, TileData tile) { - if (isFull()) - return false; - add(index, tile); - return true; - } - - public boolean offerClosest(TileData tile) { - return offer(0, tile); - } - - public boolean offerFarthest(TileData tile) { - return offer(size(), tile); - } - - public TileData removeClosest() { - return remove(0); - } - - public TileData removeFarthest() { - return remove(size() - 1); - } - - public TileData poll(int index) { - if (size() <= index) - return null; - return remove(index); - } - - public TileData pollClosest() { - return poll(0); - } - - public TileData pollFarthest() { - return poll(size() - 1); - } - - @Override - public boolean add(TileData tile) { - addFarthest(tile); - return true; - } - - public BlockData getHost() { - return getChunk().getBlock(getBlockInChunk(null)); - } - -} diff --git a/src/main/java/ru/windcorp/progressia/server/ChunkManager.java b/src/main/java/ru/windcorp/progressia/server/ChunkManager.java deleted file mode 100644 index 685fe51..0000000 --- a/src/main/java/ru/windcorp/progressia/server/ChunkManager.java +++ /dev/null @@ -1,222 +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.server; - -import java.util.Collections; -import java.util.Map; -import java.util.WeakHashMap; - -import glm.vec._3.i.Vec3i; -import ru.windcorp.progressia.common.world.ChunkData; -import ru.windcorp.progressia.common.world.PacketRevokeChunk; -import ru.windcorp.progressia.common.world.PacketSendChunk; -import ru.windcorp.progressia.common.world.WorldData; -import ru.windcorp.progressia.common.world.generic.ChunkSet; -import ru.windcorp.progressia.common.world.generic.ChunkSets; -import ru.windcorp.progressia.test.TestWorldDiskIO; - -public class ChunkManager { - - private class PlayerVision { - - private final ChunkSet visible = ChunkSets.newSyncHashSet(); - private final ChunkSet requested = ChunkSets.newHashSet(); - private final ChunkSet toSend = ChunkSets.newHashSet(); - private final ChunkSet toRevoke = ChunkSets.newHashSet(); - - public boolean isChunkVisible(Vec3i chunkPos) { - return visible.contains(chunkPos); - } - - public void gatherRequests(Player player) { - requested.clear(); - player.requestChunksToLoad(requested::add); - } - - public void updateQueues(Player player) { - toSend.clear(); - - requested.forEachIn(server.getWorld(), chunk -> { - if (!chunk.isReady()) - return; - if (visible.contains(chunk)) - return; - toSend.add(chunk); - }); - - toRevoke.clear(); - toRevoke.addAll(visible); - toRevoke.removeIf(v -> loaded.contains(v) && requested.contains(v)); - } - - public void processQueues(Player player) { - toRevoke.forEach(chunkPos -> revokeChunk(player, chunkPos)); - toRevoke.clear(); - - toSend.forEach(chunkPos -> sendChunk(player, chunkPos)); - toSend.clear(); - } - - } - - private final Server server; - - private final ChunkSet loaded; - private final ChunkSet requested = ChunkSets.newHashSet(); - private final ChunkSet toLoad = ChunkSets.newHashSet(); - private final ChunkSet toUnload = ChunkSets.newHashSet(); - - // TODO replace with a normal Map managed by some sort of PlayerListener, - // weak maps are weak - private final Map visions = Collections.synchronizedMap(new WeakHashMap<>()); - - public ChunkManager(Server server) { - this.server = server; - this.loaded = server.getWorld().getData().getLoadedChunks(); - } - - public void tick() { - synchronized (getServer().getWorld().getData()) { - synchronized (visions) { - gatherRequests(); - updateQueues(); - processQueues(); - } - } - } - - private void gatherRequests() { - requested.clear(); - - server.getPlayerManager().getPlayers().forEach(p -> { - PlayerVision vision = getVision(p, true); - vision.gatherRequests(p); - requested.addAll(vision.requested); - }); - } - - private void updateQueues() { - toLoad.clear(); - toLoad.addAll(requested); - toLoad.removeAll(loaded); - - toUnload.clear(); - toUnload.addAll(loaded); - toUnload.removeAll(requested); - - visions.forEach((p, v) -> { - v.updateQueues(p); - }); - } - - private void processQueues() { - toUnload.forEach(this::unloadChunk); - toUnload.clear(); - toLoad.forEach(this::loadChunk); - toLoad.clear(); - - visions.forEach((p, v) -> { - v.processQueues(p); - }); - } - - private PlayerVision getVision(Player player, boolean createIfMissing) { - return createIfMissing ? visions.computeIfAbsent(player, k -> new PlayerVision()) : visions.get(player); - } - - public void loadChunk(Vec3i chunkPos) { - - WorldData world = getServer().getWorld().getData(); - - ChunkData chunk = TestWorldDiskIO.tryToLoad(chunkPos, world, getServer()); - if (chunk != null) { - world.addChunk(chunk); - } else { - getServer().getWorld().generate(chunkPos); - } - - } - - public void unloadChunk(Vec3i chunkPos) { - - WorldData world = getServer().getWorld().getData(); - - ChunkData chunk = world.getChunk(chunkPos); - if (chunk == null) { - throw new IllegalStateException( - String.format("Chunk (%d; %d; %d) not loaded, cannot unload", chunkPos.x, chunkPos.y, chunkPos.z)); - } - - world.removeChunk(chunk); - - TestWorldDiskIO.saveChunk(chunk, getServer()); - - } - - public void sendChunk(Player player, Vec3i chunkPos) { - ChunkData chunk = server.getWorld().getData().getChunk(chunkPos); - - if (chunk == null) { - throw new IllegalStateException( - String.format("Chunk (%d; %d; %d) is not loaded, cannot send", chunkPos.x, chunkPos.y, chunkPos.z)); - } - - PacketSendChunk packet = new PacketSendChunk(); - packet.set(chunk); - player.getClient().sendPacket(packet); - - getVision(player, true).visible.add(chunkPos); - } - - public void revokeChunk(Player player, Vec3i chunkPos) { - PacketRevokeChunk packet = new PacketRevokeChunk(); - packet.set(chunkPos); - player.getClient().sendPacket(packet); - - PlayerVision vision = getVision(player, false); - if (vision != null) { - vision.visible.remove(chunkPos); - } - } - - public boolean isChunkVisible(Vec3i chunkPos, Player player) { - PlayerVision vision = getVision(player, false); - - if (vision == null) { - return false; - } - - return vision.isChunkVisible(chunkPos); - } - - public ChunkSet getVisibleChunks(Player player) { - PlayerVision vision = getVision(player, false); - - if (vision == null) { - return ChunkSets.empty(); - } - - return vision.visible; - } - - public Server getServer() { - return server; - } - -} diff --git a/src/main/java/ru/windcorp/progressia/server/EntityManager.java b/src/main/java/ru/windcorp/progressia/server/EntityManager.java deleted file mode 100644 index 0e2a211..0000000 --- a/src/main/java/ru/windcorp/progressia/server/EntityManager.java +++ /dev/null @@ -1,196 +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.server; - -import java.util.Collections; -import java.util.Map; -import java.util.WeakHashMap; - -import glm.vec._3.i.Vec3i; -import gnu.trove.TCollections; -import gnu.trove.iterator.TLongIterator; -import gnu.trove.set.TLongSet; -import gnu.trove.set.hash.TLongHashSet; -import ru.windcorp.jputil.chars.StringUtil; -import ru.windcorp.progressia.common.util.Vectors; -import ru.windcorp.progressia.common.world.entity.EntityData; -import ru.windcorp.progressia.common.world.entity.PacketRevokeEntity; -import ru.windcorp.progressia.common.world.entity.PacketSendEntity; -import ru.windcorp.progressia.common.world.generic.ChunkSet; - -public class EntityManager { - - private class PlayerVision { - - private final TLongSet visible = TCollections.synchronizedSet(new TLongHashSet()); - private final TLongSet requested = new TLongHashSet(); - private final TLongSet toSend = new TLongHashSet(); - private final TLongSet toRevoke = new TLongHashSet(); - - public boolean isEntityVisible(long entityId) { - return visible.contains(entityId); - } - - public void gatherRequests(Player player) { - requested.clear(); - - ChunkSet visibleChunks = player.getClient().getVisibleChunks(); - Vec3i v = Vectors.grab3i(); - - getServer().getWorld().forEachEntity(entity -> { - if (visibleChunks.contains(entity.getChunkCoords(v))) { - requested.add(entity.getEntityId()); - } - }); - - Vectors.release(v); - } - - public void updateQueues(Player player) { - toSend.clear(); - toSend.addAll(requested); - toSend.removeAll(visible); - toSend.retainAll(loaded); - - toRevoke.clear(); - - for (TLongIterator it = visible.iterator(); it.hasNext();) { - long entityId = it.next(); - if (!loaded.contains(entityId) || !requested.contains(entityId)) { - toRevoke.add(entityId); - } - } - } - - public void processQueues(Player player) { - toRevoke.forEach(entityId -> { - revokeEntity(player, entityId); - return true; - }); - toRevoke.clear(); - - toSend.forEach(entityId -> { - sendEntity(player, entityId); - return true; - }); - toSend.clear(); - } - - } - - private final Server server; - - private final TLongSet loaded; - - // TODO replace with a normal Map managed by some sort of PlayerListener, - // weak maps are weak - private final Map visions = Collections.synchronizedMap(new WeakHashMap<>()); - - public EntityManager(Server server) { - this.server = server; - this.loaded = server.getWorld().getData().getLoadedEntities(); - } - - public void tick() { - synchronized (getServer().getWorld().getData()) { - synchronized (visions) { - gatherRequests(); - updateQueues(); - processQueues(); - } - } - } - - private void gatherRequests() { - server.getPlayerManager().getPlayers().forEach(p -> { - PlayerVision vision = getVision(p, true); - vision.gatherRequests(p); - }); - } - - private void updateQueues() { - visions.forEach((p, v) -> { - v.updateQueues(p); - }); - } - - private void processQueues() { - visions.forEach((p, v) -> { - v.processQueues(p); - }); - } - - private PlayerVision getVision(Player player, boolean createIfMissing) { - return createIfMissing ? visions.computeIfAbsent(player, k -> new PlayerVision()) : visions.get(player); - } - - public void sendEntity(Player player, long entityId) { - - EntityData entity = server.getWorld().getData().getEntity(entityId); - - if (entity == null) { - throw new IllegalStateException("Entity with entity ID " + new String(StringUtil.toFullHex(entityId)) - + " is not loaded, cannot send"); - } - - PacketSendEntity packet = new PacketSendEntity(); - packet.set(entity); - player.getClient().sendPacket(packet); - - getVision(player, true).visible.add(entityId); - } - - public void revokeEntity(Player player, long entityId) { - PacketRevokeEntity packet = new PacketRevokeEntity(); - packet.set(entityId); - player.getClient().sendPacket(packet); - - PlayerVision vision = getVision(player, false); - if (vision != null) { - vision.visible.remove(entityId); - } - } - - public boolean isEntityVisible(long entityId, Player player) { - PlayerVision vision = getVision(player, false); - - if (vision == null) { - return false; - } - - return vision.isEntityVisible(entityId); - } - - private static final TLongSet EMPTY_LONG_SET = TCollections.unmodifiableSet(new TLongHashSet()); - - public TLongSet getVisibleEntities(Player player) { - PlayerVision vision = getVision(player, false); - - if (vision == null) { - return EMPTY_LONG_SET; - } - - return vision.visible; - } - - public Server getServer() { - return server; - } - -} diff --git a/src/main/java/ru/windcorp/progressia/server/Player.java b/src/main/java/ru/windcorp/progressia/server/Player.java index 9b29918..89fbebc 100644 --- a/src/main/java/ru/windcorp/progressia/server/Player.java +++ b/src/main/java/ru/windcorp/progressia/server/Player.java @@ -20,9 +20,13 @@ package ru.windcorp.progressia.server; import java.util.function.Consumer; +import glm.mat._3.Mat3; +import glm.vec._3.Vec3; import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.common.Units; -import ru.windcorp.progressia.common.world.ChunkData; +import ru.windcorp.progressia.common.util.Matrices; +import ru.windcorp.progressia.common.util.Vectors; +import ru.windcorp.progressia.common.world.DefaultChunkData; import ru.windcorp.progressia.common.world.Coordinates; import ru.windcorp.progressia.common.world.PlayerData; import ru.windcorp.progressia.common.world.entity.EntityData; @@ -55,15 +59,40 @@ public class Player extends PlayerData implements ChunkLoader { Coordinates.convertInWorldToChunk(start, start); Vec3i cursor = new Vec3i(); - float radius = getServer().getLoadDistance(this) / Units.get(ChunkData.BLOCKS_PER_CHUNK, "m"); + float radius = getServer().getLoadDistance(this) / Units.get(DefaultChunkData.BLOCKS_PER_CHUNK, "m"); float radiusSq = radius * radius; int iRadius = (int) Math.ceil(radius); + // The sphere around the player is stretched by this factor vertically + // (along the player's up vector) + final float verticalStretching = 0.4f; + + float factor = (1/verticalStretching - 1); + Vec3 up = getServer().getWorld().getData().getGravityModel().getUp(getEntity().getPosition(), null); + + Mat3 transform = Matrices.grab3(); + + //@formatter:off + transform.set( + 1 + factor * up.x * up.x, 0 + factor * up.x * up.y, 0 + factor * up.x * up.z, + 0 + factor * up.y * up.x, 1 + factor * up.y * up.y, 0 + factor * up.y * up.z, + 0 + factor * up.z * up.x, 0 + factor * up.z * up.y, 1 + factor * up.z * up.z + ); + //@formatter:on + + Vec3 transformedCursor = Vectors.grab3(); + for (cursor.x = -iRadius; cursor.x <= +iRadius; ++cursor.x) { for (cursor.y = -iRadius; cursor.y <= +iRadius; ++cursor.y) { for (cursor.z = -iRadius; cursor.z <= +iRadius; ++cursor.z) { - if (cursor.x * cursor.x + cursor.y * cursor.y + (cursor.z * 2) * (cursor.z * 2) <= radiusSq) { + + transformedCursor.set(cursor.x, cursor.y, cursor.z); + + // .mul(Vec3) is cursed + transform.mul(transformedCursor, transformedCursor); + + if (transformedCursor.dot(transformedCursor) <= radiusSq) { cursor.add(start); chunkConsumer.accept(cursor); @@ -73,6 +102,9 @@ public class Player extends PlayerData implements ChunkLoader { } } } + + Matrices.release(transform); + Vectors.release(transformedCursor); } public String getLogin() { diff --git a/src/main/java/ru/windcorp/progressia/server/PlayerManager.java b/src/main/java/ru/windcorp/progressia/server/PlayerManager.java index 3e7bd40..f762778 100644 --- a/src/main/java/ru/windcorp/progressia/server/PlayerManager.java +++ b/src/main/java/ru/windcorp/progressia/server/PlayerManager.java @@ -15,17 +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.server; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import glm.vec._2.Vec2; +import glm.vec._3.Vec3; import ru.windcorp.progressia.common.util.crash.CrashReports; import ru.windcorp.progressia.common.world.entity.EntityData; import ru.windcorp.progressia.common.world.entity.EntityDataRegistry; +import ru.windcorp.progressia.server.events.PlayerJoinedEvent; import ru.windcorp.progressia.test.TestContent; public class PlayerManager { @@ -44,6 +45,7 @@ public class PlayerManager { public void addPlayer(Player player) { this.players.add(player); + getServer().postEvent(new PlayerJoinedEvent.Immutable(getServer(), player)); } public EntityData conjurePlayerEntity(String login) { @@ -60,13 +62,19 @@ public class PlayerManager { EntityData player = EntityDataRegistry.getInstance().create("Test:Player"); player.setEntityId(TestContent.PLAYER_ENTITY_ID); - player.setPosition(TestContent.SPAWN); - player.setDirection(new Vec2(Math.toRadians(40), Math.toRadians(10))); + player.setPosition(getServer().getWorld().getGenerator().suggestSpawnLocation()); + + player.setUpVector(new Vec3(0, 0, 1)); + player.setLookingAt(new Vec3(2, 1, 0)); getServer().getWorld().getData().addEntity(player); return player; } + + public Object getMutex() { + return players; + } public Server getServer() { return server; diff --git a/src/main/java/ru/windcorp/progressia/server/Server.java b/src/main/java/ru/windcorp/progressia/server/Server.java index fba1857..e975b90 100644 --- a/src/main/java/ru/windcorp/progressia/server/Server.java +++ b/src/main/java/ru/windcorp/progressia/server/Server.java @@ -19,19 +19,37 @@ package ru.windcorp.progressia.server; import java.util.function.Consumer; +import java.util.function.Function; import org.apache.logging.log4j.LogManager; +import com.google.common.eventbus.EventBus; + +import glm.vec._3.i.Vec3i; import ru.windcorp.jputil.functions.ThrowingRunnable; import ru.windcorp.progressia.common.Units; import ru.windcorp.progressia.common.util.TaskQueue; -import ru.windcorp.progressia.common.world.WorldData; +import ru.windcorp.progressia.common.util.crash.ReportingEventBus; +import ru.windcorp.progressia.common.world.DefaultWorldData; +import ru.windcorp.progressia.common.world.rels.AbsFace; +import ru.windcorp.progressia.common.world.rels.AxisRotations; import ru.windcorp.progressia.server.comms.ClientManager; -import ru.windcorp.progressia.server.world.WorldLogic; +import ru.windcorp.progressia.server.events.ServerEvent; +import ru.windcorp.progressia.server.management.load.ChunkRequestDaemon; +import ru.windcorp.progressia.server.management.load.EntityRequestDaemon; +import ru.windcorp.progressia.server.management.load.LoadManager; +import ru.windcorp.progressia.server.world.DefaultWorldLogic; +import ru.windcorp.progressia.server.world.context.ServerBlockContext; +import ru.windcorp.progressia.server.world.context.ServerTileContext; +import ru.windcorp.progressia.server.world.context.ServerWorldContext; +import ru.windcorp.progressia.server.world.context.impl.DefaultServerContext; +import ru.windcorp.progressia.server.world.context.impl.ReportingServerContext; +import ru.windcorp.progressia.server.world.context.impl.RotatingServerContext; +import ru.windcorp.progressia.server.world.generation.WorldGenerator; import ru.windcorp.progressia.server.world.tasks.WorldAccessor; import ru.windcorp.progressia.server.world.ticking.Change; import ru.windcorp.progressia.server.world.ticking.Evaluation; -import ru.windcorp.progressia.test.gen.TestWorldGenerator; +import ru.windcorp.progressia.server.world.ticking.TickerCoordinator; public class Server { @@ -45,43 +63,103 @@ public class Server { return ServerThread.getCurrentServer(); } - private final WorldLogic world; + private final DefaultWorldLogic world; private final WorldAccessor worldAccessor = new WorldAccessor(this); private final ServerThread serverThread; private final ClientManager clientManager; private final PlayerManager playerManager; - private final ChunkManager chunkManager; - private final EntityManager entityManager; + private final LoadManager loadManager; private final TaskQueue taskQueue = new TaskQueue(this::isServerThread); + private final EventBus eventBus = ReportingEventBus.create("ServerEvents"); + private final TickingSettings tickingSettings = new TickingSettings(); - public Server(WorldData world) { - this.world = new WorldLogic(world, this, TestWorldGenerator::new); + public Server(DefaultWorldData world, Function generatorCreator) { + this.world = new DefaultWorldLogic( + world, + this, + generatorCreator.apply(this), + worldAccessor + ); this.serverThread = new ServerThread(this); this.clientManager = new ClientManager(this); this.playerManager = new PlayerManager(this); - this.chunkManager = new ChunkManager(this); - this.entityManager = new EntityManager(this); + this.loadManager = new LoadManager(this); + schedule(new ChunkRequestDaemon(loadManager.getChunkManager())::tick); + schedule(new EntityRequestDaemon(loadManager.getEntityManager())::tick); + + // Must run after request daemons so it only schedules chunks that + // hadn't unloaded schedule(this::scheduleWorldTicks); - schedule(chunkManager::tick); - schedule(entityManager::tick); } /** * Returns this server's world. * - * @return this server's {@link WorldLogic} + * @return this server's {@link DefaultWorldLogic} */ - public WorldLogic getWorld() { + public DefaultWorldLogic getWorld() { return world; } + /** + * Instantiates and returns an new {@link ServerWorldContext} instance + * suitable for read and write access to the server's world. This context + * uses the absolute coordinate space (not rotated to match positive Z = + * up). + * + * @return the context + * @see #createContext(AbsFace) + */ + public ServerWorldContext createAbsoluteContext() { + return doCreateAbsoluteContext(); + } + + private ServerTileContext doCreateAbsoluteContext() { + return new ReportingServerContext(DefaultServerContext.empty().inRealWorldOf(this).build()) + .withListener(worldAccessor).setPassToParent(false); + } + + /** + * Instantiates and returns an new {@link ServerWorldContext} instance + * suitable for read and write access to the server's world. This is the + * preferred way to query or change the world. This context uses the + * coordinate space in which positive Z = {@code up}. + * + * @param up the desired up direction + * @return the context + * @see #createContext(Vec3i) + * @see #createAbsoluteContext() + */ + public ServerWorldContext createContext(AbsFace up) { + return new RotatingServerContext(doCreateAbsoluteContext(), up); + } + + /** + * Instantiates and returns an new {@link ServerBlockContext} instance + * suitable for read and write access to the server's world. The context is + * initialized to point to the provided block. This is the preferred way to + * query or change the world. This context uses the coordinate space in + * which positive Z matches the discrete up direction of the provided + * location. + * + * @param up the desired up direction + * @return the context + * @see #createContext(AbsFace) + * @see #createAbsoluteContext() + */ + public ServerBlockContext createContext(Vec3i blockInWorld) { + AbsFace up = getWorld().getUp(blockInWorld); + Vec3i relativeBlockInWorld = AxisRotations.relativize(blockInWorld, up, null); + return new RotatingServerContext(doCreateAbsoluteContext(), up).push(relativeBlockInWorld); + } + /** * Returns this server's {@link ClientManager}. Use this to deal with * communications, e.g. send packets. @@ -96,8 +174,8 @@ public class Server { return playerManager; } - public ChunkManager getChunkManager() { - return chunkManager; + public LoadManager getLoadManager() { + return loadManager; } /** @@ -118,8 +196,7 @@ public class Server { * happen in the main server thread, such as initialization tasks or * reconfiguration. * - * @param task - * the task to run + * @param task the task to run * @see #invokeNow(Runnable) * @see #schedule(Consumer) */ @@ -137,8 +214,7 @@ public class Server { * Use this method to make sure that a piece of code is run in the main * server thread. * - * @param task - * the task to run + * @param task the task to run * @see #invokeLater(Runnable) * @see #schedule(Consumer) */ @@ -158,13 +234,55 @@ public class Server { schedule(() -> task.accept(this)); } - public void requestChange(Change change) { + /** + * Delayed + */ + public void scheduleChange(Change change) { serverThread.getTicker().requestChange(change); } - - public void requestEvaluation(Evaluation evaluation) { + + /** + * Delayed + */ + public void scheduleEvaluation(Evaluation evaluation) { serverThread.getTicker().requestEvaluation(evaluation); } + + /** + * Immediate if possible, otherwise delayed + */ + public void requestChange(Change change) { + if (serverThread.getTicker().getPhase() == TickerCoordinator.TickPhase.SYNCHRONOUS) { + change.affect(this); + } else { + serverThread.getTicker().requestChange(change); + } + } + + /** + * Immediate if possible, otherwise delayed + */ + public void requestEvaluation(Evaluation evaluation) { + if (serverThread.getTicker().getPhase() == TickerCoordinator.TickPhase.SYNCHRONOUS) { + evaluation.evaluate(this); + } else { + serverThread.getTicker().requestEvaluation(evaluation); + } + } + + public void subscribe(Object object) { + eventBus.register(object); + } + + public void unsubscribe(Object object) { + eventBus.unregister(object); + } + + public void postEvent(ServerEvent event) { + event.setServer(this); + eventBus.post(event); + event.setServer(null); + } /** * Returns the duration of the last server tick. Server logic should assume @@ -192,19 +310,6 @@ public class Server { return this.serverThread.getTicker().getUptimeTicks(); } - /** - * Returns the {@link WorldAccessor} object for this server. Use the - * provided accessor to request common {@link Evaluation}s and - * {@link Change}s. - * - * @return a {@link WorldAccessor} - * @see #requestChange(Change) - * @see #requestEvaluation(Evaluation) - */ - public WorldAccessor getWorldAccessor() { - return worldAccessor; - } - /** * Returns the ticking settings for this server. * @@ -215,7 +320,7 @@ public class Server { } public float getLoadDistance(Player player) { - return Units.get(150.0f, "m"); + return Units.get(100.5f, "m"); } /** @@ -237,8 +342,8 @@ public class Server { * Shuts the server down, disconnecting the clients with the provided * message. This method blocks until the shutdown is complete. * - * @param message - * the message to send to the clients as the disconnect reason + * @param message the message to send to the clients as the disconnect + * reason */ public void shutdown(String message) { LogManager.getLogger().warn("Server.shutdown() is not yet implemented"); @@ -246,8 +351,8 @@ public class Server { } private void scheduleWorldTicks(Server server) { - server.getWorld().getChunks().forEach(chunk -> requestEvaluation(chunk.getTickTask())); - requestEvaluation(server.getWorld().getTickEntitiesTask()); + server.getWorld().getChunks().forEach(chunk -> scheduleEvaluation(chunk.getTickTask())); + scheduleEvaluation(server.getWorld().getTickEntitiesTask()); } /** diff --git a/src/main/java/ru/windcorp/progressia/server/ServerState.java b/src/main/java/ru/windcorp/progressia/server/ServerState.java index d252e0c..caa9792 100644 --- a/src/main/java/ru/windcorp/progressia/server/ServerState.java +++ b/src/main/java/ru/windcorp/progressia/server/ServerState.java @@ -18,7 +18,8 @@ package ru.windcorp.progressia.server; -import ru.windcorp.progressia.common.world.WorldData; +import ru.windcorp.progressia.common.world.DefaultWorldData; +import ru.windcorp.progressia.test.gen.TestGenerationConfig; public class ServerState { @@ -33,7 +34,7 @@ public class ServerState { } public static void startServer() { - Server server = new Server(new WorldData()); + Server server = new Server(new DefaultWorldData(), TestGenerationConfig.createGenerator()); setInstance(server); server.start(); } diff --git a/src/main/java/ru/windcorp/progressia/server/comms/ClientManager.java b/src/main/java/ru/windcorp/progressia/server/comms/ClientManager.java index 56bdab3..57ff467 100644 --- a/src/main/java/ru/windcorp/progressia/server/comms/ClientManager.java +++ b/src/main/java/ru/windcorp/progressia/server/comms/ClientManager.java @@ -28,6 +28,7 @@ import gnu.trove.map.TIntObjectMap; import gnu.trove.map.hash.TIntObjectHashMap; import ru.windcorp.progressia.common.comms.CommsChannel.State; import ru.windcorp.progressia.common.comms.packets.Packet; +import ru.windcorp.progressia.common.world.PacketSetGravityModel; import ru.windcorp.progressia.common.world.PacketSetLocalPlayer; import ru.windcorp.progressia.common.world.entity.EntityData; import ru.windcorp.progressia.server.Player; @@ -73,10 +74,14 @@ public class ClientManager { private void addClientPlayer(ClientPlayer client) { String login = client.getLogin(); + + PacketSetGravityModel setGravityModelPacket = new PacketSetGravityModel(); + setGravityModelPacket.set(getServer().getWorld().getData().getGravityModel()); + client.sendPacket(setGravityModelPacket); EntityData entity = getServer().getPlayerManager().conjurePlayerEntity(login); Player player = new Player(entity, getServer(), client); - getServer().getPlayerManager().getPlayers().add(player); + getServer().getPlayerManager().addPlayer(player); PacketSetLocalPlayer packet = new PacketSetLocalPlayer(); packet.set(entity.getEntityId()); diff --git a/src/main/java/ru/windcorp/progressia/server/comms/ClientPlayer.java b/src/main/java/ru/windcorp/progressia/server/comms/ClientPlayer.java index d414524..3a83bb3 100644 --- a/src/main/java/ru/windcorp/progressia/server/comms/ClientPlayer.java +++ b/src/main/java/ru/windcorp/progressia/server/comms/ClientPlayer.java @@ -44,13 +44,13 @@ public abstract class ClientPlayer extends ClientChat { public boolean isChunkVisible(Vec3i chunkPos) { if (player == null) return false; - return player.getServer().getChunkManager().isChunkVisible(chunkPos, player); + return player.getServer().getLoadManager().getVisionManager().isChunkVisible(chunkPos, player); } public ChunkSet getVisibleChunks() { if (player == null) return ChunkSets.empty(); - return player.getServer().getChunkManager().getVisibleChunks(player); + return player.getServer().getLoadManager().getVisionManager().getVisibleChunks(player); } public boolean isChunkVisible(long entityId) { diff --git a/src/main/java/ru/windcorp/progressia/server/world/TickContext.java b/src/main/java/ru/windcorp/progressia/server/events/ClientEvent.java similarity index 58% rename from src/main/java/ru/windcorp/progressia/server/world/TickContext.java rename to src/main/java/ru/windcorp/progressia/server/events/ClientEvent.java index 0997c65..c795fa8 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/TickContext.java +++ b/src/main/java/ru/windcorp/progressia/server/events/ClientEvent.java @@ -15,35 +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.server.events; -package ru.windcorp.progressia.server.world; - -import java.util.Random; - -import ru.windcorp.progressia.common.world.WorldData; import ru.windcorp.progressia.server.Server; -import ru.windcorp.progressia.server.world.tasks.WorldAccessor; +import ru.windcorp.progressia.server.comms.Client; -public interface TickContext { +public interface ClientEvent extends ServerEvent { + + Client getClient(); + + public static abstract class Immutable extends ServerEvent.Default implements ClientEvent { + + private final Client client; - float getTickLength(); - - Server getServer(); - - default WorldLogic getWorld() { - return getServer().getWorld(); - } - - default WorldAccessor getAccessor() { - return getServer().getWorldAccessor(); - } - - default Random getRandom() { - return getServer().getAdHocRandom(); - } - - default WorldData getWorldData() { - return getWorld().getData(); + public Immutable(Server server, Client client) { + super(server); + this.client = client; + } + + /** + * @return the client + */ + @Override + public Client getClient() { + return client; + } + } } diff --git a/src/main/java/ru/windcorp/progressia/server/events/PlayerEvent.java b/src/main/java/ru/windcorp/progressia/server/events/PlayerEvent.java new file mode 100644 index 0000000..544418e --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/events/PlayerEvent.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.server.events; + +import ru.windcorp.progressia.server.Player; +import ru.windcorp.progressia.server.Server; +import ru.windcorp.progressia.server.comms.Client; + +public interface PlayerEvent extends ClientEvent { + + Player getPlayer(); + + public static abstract class Immutable extends ServerEvent.Default implements PlayerEvent { + + private final Player player; + + public Immutable(Server server, Player player) { + super(server); + this.player = player; + } + + @Override + public Player getPlayer() { + return player; + } + + @Override + public Client getClient() { + return player.getClient(); + } + + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/tile/TileLogicStack.java b/src/main/java/ru/windcorp/progressia/server/events/PlayerJoinedEvent.java similarity index 65% rename from src/main/java/ru/windcorp/progressia/server/world/tile/TileLogicStack.java rename to src/main/java/ru/windcorp/progressia/server/events/PlayerJoinedEvent.java index ce94cfc..eed2a90 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/tile/TileLogicStack.java +++ b/src/main/java/ru/windcorp/progressia/server/events/PlayerJoinedEvent.java @@ -15,15 +15,19 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +package ru.windcorp.progressia.server.events; -package ru.windcorp.progressia.server.world.tile; +import ru.windcorp.progressia.server.Player; +import ru.windcorp.progressia.server.Server; -import ru.windcorp.progressia.common.world.generic.GenericTileStack; -import ru.windcorp.progressia.common.world.tile.TileDataStack; -import ru.windcorp.progressia.server.world.ChunkLogic; +public interface PlayerJoinedEvent extends PlayerEvent { + + public static class Immutable extends PlayerEvent.Immutable implements PlayerJoinedEvent { -public abstract class TileLogicStack extends GenericTileStack { - - public abstract TileDataStack getData(); + public Immutable(Server server, Player player) { + super(server, player); + } + + } } diff --git a/src/main/java/ru/windcorp/progressia/server/events/PlayerLeftEvent.java b/src/main/java/ru/windcorp/progressia/server/events/PlayerLeftEvent.java new file mode 100644 index 0000000..d6e4417 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/events/PlayerLeftEvent.java @@ -0,0 +1,33 @@ +/* + * 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.server.events; + +import ru.windcorp.progressia.server.Player; +import ru.windcorp.progressia.server.Server; + +public interface PlayerLeftEvent extends PlayerEvent { + + public static class Immutable extends PlayerEvent.Immutable implements PlayerLeftEvent { + + public Immutable(Server server, Player player) { + super(server, player); + } + + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/events/ServerEvent.java b/src/main/java/ru/windcorp/progressia/server/events/ServerEvent.java new file mode 100644 index 0000000..b63f727 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/events/ServerEvent.java @@ -0,0 +1,68 @@ +/* + * 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.server.events; + +import ru.windcorp.progressia.server.Server; + +/** + * An interface for all events issued by a {@link Server}. + */ +public interface ServerEvent { + + /** + * Returns the server instance that this event happened on. + * + * @return the relevant server + */ + Server getServer(); + + /** + * Sets the server instance that the event is posted on. The value provided + * to this method must be returned by subsequent calls to + * {@link #getServer()}. Do not call this method when handling the event. + * + * @param server the server dispatching the event or {@code null} to unbind + * any previously bound server + */ + void setServer(Server server); + + /** + * A default implementation of {@link ServerEvent}. This is not necessarily + * extended by server events. + */ + public static abstract class Default implements ServerEvent { + + private Server server; + + public Default(Server server) { + this.server = server; + } + + @Override + public Server getServer() { + return server; + } + + @Override + public void setServer(Server server) { + this.server = server; + } + + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/management/load/ChunkManager.java b/src/main/java/ru/windcorp/progressia/server/management/load/ChunkManager.java new file mode 100644 index 0000000..3071530 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/management/load/ChunkManager.java @@ -0,0 +1,192 @@ +/* + * 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.server.management.load; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.DefaultChunkData; +import ru.windcorp.progressia.common.world.PacketRevokeChunk; +import ru.windcorp.progressia.common.world.PacketSendChunk; +import ru.windcorp.progressia.common.world.DefaultWorldData; +import ru.windcorp.progressia.server.Player; +import ru.windcorp.progressia.server.Server; +import ru.windcorp.progressia.test.TestWorldDiskIO; + +/** + * Chunk manager provides facilities to load, unload and generate chunks for a + * {@link Server} on demand. + */ +public class ChunkManager { + + private final LoadManager loadManager; + + public ChunkManager(LoadManager loadManager) { + this.loadManager = loadManager; + } + + /** + * @return the loadManager + */ + public LoadManager getLoadManager() { + return loadManager; + } + + /** + * @return the server + */ + public Server getServer() { + return getLoadManager().getServer(); + } + + /** + * Describes the result of an attempt to load a chunk. + */ + public static enum LoadResult { + /** + * A chunk has successfully been read from disk and is now loaded. + */ + LOADED_FROM_DISK, + + /** + * A chunk has successfully been generated and is now loaded. + */ + GENERATED, + + /** + * A chunk has already been loaded and so no action has been taken. + */ + ALREADY_LOADED, + + /** + * A chunk has not been loaded previously and the operation has failed + * to load it. It is not currently loaded. + */ + NOT_LOADED + } + + /** + * Loads or generates the chunk at the given location unless it is already + * loaded. The chunk is loaded after this method completes normally. + * + * @param chunkPos the position of the chunk + * @return one of {@link LoadResult#LOADED_FROM_DISK LOADED_FROM_DISK}, + * {@link LoadResult#GENERATED GENERATED} or + * {@link LoadResult#ALREADY_LOADED ALREADY_LOADED} + */ + public LoadResult loadOrGenerateChunk(Vec3i chunkPos) { + LoadResult loadResult = loadChunk(chunkPos); + + if (loadResult == LoadResult.NOT_LOADED || !getServer().getWorld().getChunk(chunkPos).isReady()) { + getServer().getWorld().generate(chunkPos); + return LoadResult.GENERATED; + } else { + return loadResult; + } + } + + /** + * Attempts to load the chunk from disk unless it is already loaded. If the + * chunk is not currently loaded and it is not available on the disk this + * method does nothing. + * + * @param chunkPos the position of the chunk + * @return one of {@link LoadResult#LOADED_FROM_DISK LOADED_FROM_DISK}, + * {@link LoadResult#NOT_LOADED NOT_LOADED} or + * {@link LoadResult#ALREADY_LOADED ALREADY_LOADED} + */ + public LoadResult loadChunk(Vec3i chunkPos) { + if (isChunkLoaded(chunkPos)) { + return LoadResult.ALREADY_LOADED; + } + + DefaultWorldData world = getServer().getWorld().getData(); + + DefaultChunkData chunk = TestWorldDiskIO.tryToLoad(chunkPos, world, getServer()); + if (chunk != null) { + world.addChunk(chunk); + return LoadResult.LOADED_FROM_DISK; + } else { + return LoadResult.NOT_LOADED; + } + } + + /** + * Unloads the chunk and saves it to disk if the chunk is loaded, otherwise + * does nothing. + * + * @param chunkPos the position of the chunk + * @return {@code true} iff the chunk had been loaded and was unloaded by + * this method + */ + public boolean unloadChunk(Vec3i chunkPos) { + DefaultWorldData world = getServer().getWorld().getData(); + DefaultChunkData chunk = world.getChunk(chunkPos); + + if (chunk == null) { + return false; + } + + world.removeChunk(chunk); + TestWorldDiskIO.saveChunk(chunk, getServer()); + + return true; + } + + public void sendChunk(Player player, Vec3i chunkPos) { + DefaultChunkData chunk = getServer().getWorld().getData().getChunk(chunkPos); + + if (chunk == null) { + throw new IllegalStateException( + String.format( + "Chunk (%d; %d; %d) is not loaded, cannot send", + chunkPos.x, + chunkPos.y, + chunkPos.z + ) + ); + } + + PacketSendChunk packet = new PacketSendChunk(); + packet.set(chunk); + player.getClient().sendPacket(packet); + + getLoadManager().getVisionManager().getVision(player, true).getVisibleChunks().add(chunkPos); + } + + public void revokeChunk(Player player, Vec3i chunkPos) { + PacketRevokeChunk packet = new PacketRevokeChunk(); + packet.set(chunkPos); + player.getClient().sendPacket(packet); + + PlayerVision vision = getLoadManager().getVisionManager().getVision(player, false); + if (vision != null) { + vision.getVisibleChunks().remove(chunkPos); + } + } + + /** + * Checks whether or not the chunk at the specified location is loaded. A + * loaded chunk is accessible through the server's {@link DefaultWorldData} object. + * + * @param chunkPos the position of the chunk + * @return {@code true} iff the chunk is loaded + */ + public boolean isChunkLoaded(Vec3i chunkPos) { + return getServer().getWorld().isChunkLoaded(chunkPos); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/management/load/ChunkRequestDaemon.java b/src/main/java/ru/windcorp/progressia/server/management/load/ChunkRequestDaemon.java new file mode 100644 index 0000000..f720c7f --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/management/load/ChunkRequestDaemon.java @@ -0,0 +1,221 @@ +/* + * 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.server.management.load; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.Units; +import ru.windcorp.progressia.common.world.generic.ChunkMap; +import ru.windcorp.progressia.common.world.generic.ChunkMaps; +import ru.windcorp.progressia.common.world.generic.ChunkSet; +import ru.windcorp.progressia.common.world.generic.ChunkSets; +import ru.windcorp.progressia.server.Server; + +/** + * Chunk request daemon gathers chunk requests from players (via {@link VisionManager}) and loads or unloads chunks appropriately. + */ +public class ChunkRequestDaemon { + + private static final float CHUNK_UNLOAD_DELAY = Units.get(5, "s"); + + private final ChunkManager chunkManager; + + private final ChunkSet loaded; + private final ChunkSet requested = ChunkSets.newHashSet(); + private final ChunkSet toLoad = ChunkSets.newHashSet(); + private final ChunkSet toGenerate = ChunkSets.newHashSet(); + private final ChunkSet toRequestUnload = ChunkSets.newHashSet(); + + private final Collection buffer = new ArrayList<>(); + + private static class ChunkUnloadRequest { + private final Vec3i chunkPos; + private final long unloadAt; + + public ChunkUnloadRequest(Vec3i chunkPos, long unloadAt) { + this.chunkPos = chunkPos; + this.unloadAt = unloadAt; + } + + /** + * @return the chunk position + */ + public Vec3i getChunkPos() { + return chunkPos; + } + + /** + * @return the moment when the chunks becomes eligible for unloading + */ + public long getUnloadAt() { + return unloadAt; + } + } + + private final ChunkMap unloadSchedule = ChunkMaps.newHashMap(); + + public ChunkRequestDaemon(ChunkManager chunkManager) { + this.chunkManager = chunkManager; + this.loaded = getServer().getWorld().getData().getLoadedChunks(); + } + + public void tick() { + synchronized (getServer().getWorld().getData()) { + synchronized (getServer().getPlayerManager().getMutex()) { + loadAndUnloadChunks(); + sendAndRevokeChunks(); + } + } + } + + private void loadAndUnloadChunks() { + gatherLoadRequests(); + updateLoadQueues(); + processLoadQueues(); + } + + private void gatherLoadRequests() { + requested.clear(); + + getChunkManager().getLoadManager().getVisionManager().forEachVision(vision -> { + vision.getRequestedChunks().clear(); + vision.getPlayer().requestChunksToLoad(vision.getRequestedChunks()::add); + requested.addAll(vision.getRequestedChunks()); + }); + } + + private void updateLoadQueues() { + toLoad.clear(); + toLoad.addAll(requested); + toLoad.removeAll(loaded); + + toRequestUnload.clear(); + toRequestUnload.addAll(loaded); + toRequestUnload.removeAll(requested); + } + + private void processLoadQueues() { + toRequestUnload.forEach(this::scheduleUnload); + toRequestUnload.clear(); + + toLoad.forEach(getChunkManager()::loadOrGenerateChunk); + toLoad.clear(); + + toGenerate.forEach(getChunkManager()::loadOrGenerateChunk); + toGenerate.clear(); + + unloadScheduledChunks(); + } + + private void scheduleUnload(Vec3i chunkPos) { + if (unloadSchedule.containsKey(chunkPos)) { + // Unload already requested, skip + return; + } + + long unloadAt = System.currentTimeMillis() + (long) (getUnloadDelay() * 1000); + Vec3i chunkPosCopy = new Vec3i(chunkPos); + + unloadSchedule.put(chunkPosCopy, new ChunkUnloadRequest(chunkPosCopy, unloadAt)); + } + + private void unloadScheduledChunks() { + long now = System.currentTimeMillis(); + + for (Iterator it = unloadSchedule.values().iterator(); it.hasNext();) { + ChunkUnloadRequest request = it.next(); + + if (request.getUnloadAt() < now) { + + it.remove(); + + if (requested.contains(request.getChunkPos())) { + continue; // do not unload chunks that became requested + } + + getChunkManager().unloadChunk(request.getChunkPos()); + } + } + } + + private void sendAndRevokeChunks() { + getChunkManager().getLoadManager().getVisionManager().forEachVision(vision -> { + revokeChunks(vision); + sendChunks(vision); + }); + } + + private void sendChunks(PlayerVision vision) { + vision.getRequestedChunks().forEachIn(getServer().getWorld(), chunk -> { + if (!chunk.isReady()) { + toGenerate.add(chunk); + return; + } + + if (vision.isChunkVisible(chunk.getPosition())) { + return; + } + + buffer.add(chunk.getPosition()); + }); + + if (buffer.isEmpty()) return; + for (Vec3i chunkPos : buffer) { + getChunkManager().sendChunk(vision.getPlayer(), chunkPos); + } + + buffer.clear(); + } + + private void revokeChunks(PlayerVision vision) { + vision.getVisibleChunks().forEach(chunkPos -> { + if (getChunkManager().isChunkLoaded(chunkPos) && vision.getRequestedChunks().contains(chunkPos)) + return; + buffer.add(new Vec3i(chunkPos)); + }); + + if (buffer.isEmpty()) return; + for (Vec3i chunkPos : buffer) { + getChunkManager().revokeChunk(vision.getPlayer(), chunkPos); + } + + buffer.clear(); + } + + /** + * @return the minimum amount of time a chunk will spend in the unload queue + */ + public float getUnloadDelay() { + return CHUNK_UNLOAD_DELAY; + } + + /** + * @return the manager + */ + public ChunkManager getChunkManager() { + return chunkManager; + } + + public Server getServer() { + return getChunkManager().getServer(); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/management/load/EntityManager.java b/src/main/java/ru/windcorp/progressia/server/management/load/EntityManager.java new file mode 100644 index 0000000..6aa3853 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/management/load/EntityManager.java @@ -0,0 +1,97 @@ +/* + * 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.server.management.load; + +import gnu.trove.set.TLongSet; +import ru.windcorp.progressia.common.world.entity.EntityData; +import ru.windcorp.progressia.common.world.entity.PacketRevokeEntity; +import ru.windcorp.progressia.common.world.entity.PacketSendEntity; +import ru.windcorp.progressia.server.Player; +import ru.windcorp.progressia.server.Server; + +public class EntityManager { + + private final LoadManager loadManager; + + private final TLongSet loaded; + + public EntityManager(LoadManager loadManager) { + this.loadManager = loadManager; + this.loaded = getServer().getWorld().getData().getLoadedEntities(); + } + + public void sendEntity(Player player, long entityId) { + PlayerVision vision = getLoadManager().getVisionManager().getVision(player, true); + if (!vision.getVisibleEntities().add(entityId)) { + return; + } + + EntityData entity = getServer().getWorld().getData().getEntity(entityId); + + if (entity == null) { + throw new IllegalStateException( + "Entity with entity ID " + EntityData.formatEntityId(entityId) + " is not loaded, cannot send" + ); + } + + PacketSendEntity packet = new PacketSendEntity(); + packet.set(entity); + player.getClient().sendPacket(packet); + } + + public void revokeEntity(Player player, long entityId) { + PlayerVision vision = getLoadManager().getVisionManager().getVision(player, false); + if (vision == null) { + return; + } + if (!vision.getVisibleEntities().remove(entityId)) { + return; + } + + PacketRevokeEntity packet = new PacketRevokeEntity(); + packet.set(entityId); + player.getClient().sendPacket(packet); + } + + public boolean isEntityVisible(Player player, long entityId) { + PlayerVision vision = getLoadManager().getVisionManager().getVision(player, false); + + if (vision == null) { + return false; + } + + return vision.isEntityVisible(entityId); + } + + public boolean isEntityLoaded(long entityId) { + return loaded.contains(entityId); + } + + /** + * @return the loadManager + */ + public LoadManager getLoadManager() { + return loadManager; + } + + public Server getServer() { + return getLoadManager().getServer(); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/management/load/EntityRequestDaemon.java b/src/main/java/ru/windcorp/progressia/server/management/load/EntityRequestDaemon.java new file mode 100644 index 0000000..9605ffe --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/management/load/EntityRequestDaemon.java @@ -0,0 +1,121 @@ +/* + * 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.server.management.load; + +import java.util.function.Consumer; + +import glm.vec._3.i.Vec3i; +import gnu.trove.TLongCollection; +import gnu.trove.iterator.TLongIterator; +import gnu.trove.list.array.TLongArrayList; +import gnu.trove.set.TLongSet; +import ru.windcorp.progressia.common.util.Vectors; +import ru.windcorp.progressia.common.world.generic.ChunkSet; +import ru.windcorp.progressia.server.Server; + +public class EntityRequestDaemon { + + private final EntityManager entityManager; + + private final TLongCollection buffer = new TLongArrayList(); + + public EntityRequestDaemon(EntityManager entityManager) { + this.entityManager = entityManager; + } + + public void tick() { + synchronized (getServer().getWorld().getData()) { + synchronized (getServer().getPlayerManager().getMutex()) { + gatherRequests(); + revokeEntities(); + sendEntities(); + } + } + } + + private void gatherRequests() { + Vec3i v = Vectors.grab3i(); + + forEachVision(vision -> { + + TLongSet requestedEntities = vision.getRequestedEntities(); + requestedEntities.clear(); + + ChunkSet visibleChunks = vision.getVisibleChunks(); + getServer().getWorld().forEachEntity(entity -> { + if (visibleChunks.contains(entity.getChunkCoords(v))) { + requestedEntities.add(entity.getEntityId()); + } + }); + }); + + Vectors.release(v); + } + + private void sendEntities() { + forEachVision(vision -> { + for (TLongIterator it = vision.getRequestedEntities().iterator(); it.hasNext();) { + long entityId = it.next(); + if (getEntityManager().isEntityLoaded(entityId) && !vision.getVisibleEntities().contains(entityId)) { + buffer.add(entityId); + } + } + + if (buffer.isEmpty()) return; + for (TLongIterator it = buffer.iterator(); it.hasNext();) { + getEntityManager().sendEntity(vision.getPlayer(), it.next()); + } + + buffer.clear(); + }); + } + + private void revokeEntities() { + forEachVision(vision -> { + for (TLongIterator it = vision.getVisibleEntities().iterator(); it.hasNext();) { + long entityId = it.next(); + if (!getEntityManager().isEntityLoaded(entityId) || !vision.getRequestedEntities().contains(entityId)) { + buffer.add(entityId); + } + } + + if (buffer.isEmpty()) return; + for (TLongIterator it = buffer.iterator(); it.hasNext();) { + getEntityManager().revokeEntity(vision.getPlayer(), it.next()); + } + + buffer.clear(); + }); + } + + /** + * @return the entityManager + */ + public EntityManager getEntityManager() { + return entityManager; + } + + public Server getServer() { + return getEntityManager().getServer(); + } + + private void forEachVision(Consumer action) { + getEntityManager().getLoadManager().getVisionManager().forEachVision(action); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/management/load/LoadManager.java b/src/main/java/ru/windcorp/progressia/server/management/load/LoadManager.java new file mode 100644 index 0000000..3670116 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/management/load/LoadManager.java @@ -0,0 +1,65 @@ +/* + * 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.server.management.load; + +import ru.windcorp.progressia.server.Server; + +public class LoadManager { + + private final Server server; + private final ChunkManager chunkManager; + private final EntityManager entityManager; + private final VisionManager visionManager; + + public LoadManager(Server server) { + this.server = server; + + this.chunkManager = new ChunkManager(this); + this.entityManager = new EntityManager(this); + this.visionManager = new VisionManager(this); + } + + /** + * @return the chunkManager + */ + public ChunkManager getChunkManager() { + return chunkManager; + } + + /** + * @return the entityManager + */ + public EntityManager getEntityManager() { + return entityManager; + } + + /** + * @return the visionManager + */ + public VisionManager getVisionManager() { + return visionManager; + } + + /** + * @return the server + */ + public Server getServer() { + return server; + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/management/load/PlayerVision.java b/src/main/java/ru/windcorp/progressia/server/management/load/PlayerVision.java new file mode 100644 index 0000000..bb8e474 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/management/load/PlayerVision.java @@ -0,0 +1,85 @@ +/* + * 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.server.management.load; + +import glm.vec._3.i.Vec3i; +import gnu.trove.TCollections; +import gnu.trove.set.TLongSet; +import gnu.trove.set.hash.TLongHashSet; +import ru.windcorp.progressia.common.world.generic.ChunkSet; +import ru.windcorp.progressia.common.world.generic.ChunkSets; +import ru.windcorp.progressia.server.Player; + +public class PlayerVision { + + private final Player player; + + private final ChunkSet visibleChunks = ChunkSets.newSyncHashSet(); + private final ChunkSet requestedChunks = ChunkSets.newHashSet(); + + private final TLongSet visibleEntities = TCollections.synchronizedSet(new TLongHashSet()); + private final TLongSet requestedEntities = new TLongHashSet(); + + public PlayerVision(Player player) { + this.player = player; + } + + public boolean isChunkVisible(Vec3i chunkPos) { + return visibleChunks.contains(chunkPos); + } + + public boolean isEntityVisible(long entityId) { + return visibleEntities.contains(entityId); + } + + /** + * @return the requestedChunks + */ + public ChunkSet getRequestedChunks() { + return requestedChunks; + } + + /** + * @return the visibleChunks + */ + public ChunkSet getVisibleChunks() { + return visibleChunks; + } + + /** + * @return the requestedEntities + */ + public TLongSet getRequestedEntities() { + return requestedEntities; + } + + /** + * @return the visibleEntities + */ + public TLongSet getVisibleEntities() { + return visibleEntities; + } + + /** + * @return the player + */ + public Player getPlayer() { + return player; + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/management/load/VisionManager.java b/src/main/java/ru/windcorp/progressia/server/management/load/VisionManager.java new file mode 100644 index 0000000..b4d2a93 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/management/load/VisionManager.java @@ -0,0 +1,106 @@ +/* + * 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.server.management.load; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +import com.google.common.eventbus.Subscribe; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.generic.ChunkSet; +import ru.windcorp.progressia.common.world.generic.ChunkSets; +import ru.windcorp.progressia.server.Player; +import ru.windcorp.progressia.server.Server; +import ru.windcorp.progressia.server.events.PlayerJoinedEvent; +import ru.windcorp.progressia.server.events.PlayerLeftEvent; + +public class VisionManager { + + private final LoadManager loadManager; + + private final Map visions = Collections.synchronizedMap(new HashMap<>()); + + public VisionManager(LoadManager loadManager) { + this.loadManager = loadManager; + getServer().subscribe(this); + } + + @Subscribe + private void onPlayerJoined(PlayerJoinedEvent event) { + getVision(event.getPlayer(), true); + } + + @Subscribe + private void onPlayerLeft(PlayerLeftEvent event) { + visions.remove(event.getPlayer()); + } + + public PlayerVision getVision(Player player, boolean createIfMissing) { + if (createIfMissing) { + return visions.computeIfAbsent(player, PlayerVision::new); + } else { + return visions.get(player); + } + } + + public void forEachVision(Consumer action) { + visions.values().forEach(action); + } + + public boolean isChunkVisible(Vec3i chunkPos, Player player) { + PlayerVision vision = getVision(player, false); + + if (vision == null) { + return false; + } + + return vision.isChunkVisible(chunkPos); + } + + public ChunkSet getVisibleChunks(Player player) { + PlayerVision vision = getVision(player, false); + + if (vision == null) { + return ChunkSets.empty(); + } + + return vision.getVisibleChunks(); + } + + /** + * @return the loadManager + */ + public LoadManager getLoadManager() { + return loadManager; + } + + /** + * @return the chunkManager + */ + public ChunkManager getChunkManager() { + return getLoadManager().getChunkManager(); + } + + public Server getServer() { + return getLoadManager().getServer(); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/ChunkLogic.java b/src/main/java/ru/windcorp/progressia/server/world/ChunkLogic.java index e9b1257..a9c3392 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/ChunkLogic.java +++ b/src/main/java/ru/windcorp/progressia/server/world/ChunkLogic.java @@ -15,177 +15,27 @@ * 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; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Map; -import java.util.WeakHashMap; -import java.util.function.BiConsumer; - import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.common.world.ChunkData; -import ru.windcorp.progressia.common.world.Coordinates; -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.tile.TileReference; -import ru.windcorp.progressia.server.world.block.BlockLogic; -import ru.windcorp.progressia.server.world.block.BlockLogicRegistry; -import ru.windcorp.progressia.server.world.block.TickableBlock; -import ru.windcorp.progressia.server.world.tasks.TickChunk; -import ru.windcorp.progressia.server.world.ticking.TickingPolicy; -import ru.windcorp.progressia.server.world.tile.TickableTile; -import ru.windcorp.progressia.server.world.tile.TileLogic; -import ru.windcorp.progressia.server.world.tile.TileLogicRegistry; -import ru.windcorp.progressia.server.world.tile.TileLogicStack; +import ru.windcorp.progressia.common.world.rels.BlockFace; -public class ChunkLogic implements GenericChunk { +public interface ChunkLogic extends ChunkLogicRO { - private final WorldLogic world; - private final ChunkData data; + /* + * Override return type + */ + + @Override + ChunkData getData(); - private final Collection tickingBlocks = new ArrayList<>(); - private final Collection tickingTiles = new ArrayList<>(); - - private final TickChunk tickTask = new TickChunk(this); - - private final Map tileLogicLists = Collections - .synchronizedMap(new WeakHashMap<>()); - - public ChunkLogic(WorldLogic world, ChunkData data) { - this.world = world; - this.data = data; - - tmp_generateTickLists(); + @Override + default TileLogicStack getTilesOrNull(Vec3i blockInChunk, BlockFace face) { + return (TileLogicStack) ChunkLogicRO.super.getTilesOrNull(blockInChunk, face); } @Override - public Vec3i getPosition() { - return getData().getPosition(); - } - - @Override - public BlockLogic getBlock(Vec3i blockInChunk) { - return BlockLogicRegistry.getInstance().get(getData().getBlock(blockInChunk).getId()); - } - - @Override - public TileLogicStack getTiles(Vec3i blockInChunk, BlockFace face) { - return getTileStackWrapper(getData().getTiles(blockInChunk, face)); - } - - @Override - public boolean hasTiles(Vec3i blockInChunk, BlockFace face) { - return getData().hasTiles(blockInChunk, face); - } - - private TileLogicStack getTileStackWrapper(TileDataStack tileDataList) { - return tileLogicLists.computeIfAbsent(tileDataList, TileLogicStackImpl::new); - } - - public WorldLogic getWorld() { - return world; - } - - public ChunkData getData() { - return data; - } - - public boolean isReady() { - return getWorld().getGenerator().isChunkReady(getData().getGenerationHint()); - } - - public boolean hasTickingBlocks() { - return !tickingBlocks.isEmpty(); - } - - public boolean hasTickingTiles() { - return !tickingTiles.isEmpty(); - } - - public void forEachTickingBlock(BiConsumer action) { - tickingBlocks.forEach(blockInChunk -> { - action.accept(blockInChunk, getBlock(blockInChunk)); - }); - } - - public void forEachTickingTile(BiConsumer action) { - tickingTiles.forEach(ref -> { - action.accept(ref, TileLogicRegistry.getInstance().get(ref.get().getId())); - }); - } - - public TickChunk getTickTask() { - return tickTask; - } - - private class TileLogicStackImpl extends TileLogicStack { - - private final TileDataStack parent; - - public TileLogicStackImpl(TileDataStack parent) { - this.parent = parent; - } - - @Override - public Vec3i getBlockInChunk(Vec3i output) { - return parent.getBlockInChunk(output); - } - - @Override - public ChunkLogic getChunk() { - return ChunkLogic.this; - } - - @Override - public BlockFace getFace() { - return parent.getFace(); - } - - @Override - public TileLogic get(int index) { - return TileLogicRegistry.getInstance().get(parent.get(index).getId()); - } - - @Override - public int size() { - return parent.size(); - } - - @Override - public TileDataStack getData() { - return parent; - } - - } - - private void tmp_generateTickLists() { - ChunkTickContext context = TickContextMutable.start().withChunk(this).build(); - - context.forEachBlock(bctxt -> { - BlockLogic block = bctxt.getBlock(); - - if (!(block instanceof TickableBlock)) - return; - - if (((TickableBlock) block).getTickingPolicy(bctxt) == TickingPolicy.REGULAR) { - tickingBlocks.add(Coordinates.convertInWorldToInChunk(bctxt.getBlockInWorld(), null)); - } - - bctxt.forEachFace(fctxt -> fctxt.forEachTile(tctxt -> { - TileLogic tile = tctxt.getTile(); - - if (!(tile instanceof TickableTile)) - return; - - if (((TickableTile) tile).getTickingPolicy(tctxt) == TickingPolicy.REGULAR) { - tickingTiles.add(tctxt.getReference()); - } - })); - }); - } + TileLogicStack getTiles(Vec3i blockInChunk, BlockFace face); } diff --git a/src/main/java/ru/windcorp/progressia/server/world/ChunkLogicRO.java b/src/main/java/ru/windcorp/progressia/server/world/ChunkLogicRO.java new file mode 100644 index 0000000..5631027 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/ChunkLogicRO.java @@ -0,0 +1,15 @@ +package ru.windcorp.progressia.server.world; + +import ru.windcorp.progressia.common.world.ChunkDataRO; +import ru.windcorp.progressia.common.world.generic.ChunkGenericRO; +import ru.windcorp.progressia.server.world.block.BlockLogic; +import ru.windcorp.progressia.server.world.tile.TileLogic; + +public interface ChunkLogicRO + extends ChunkGenericRO { + + ChunkDataRO getData(); + + boolean isReady(); + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/ChunkTickContext.java b/src/main/java/ru/windcorp/progressia/server/world/ChunkTickContext.java deleted file mode 100644 index c197706..0000000 --- a/src/main/java/ru/windcorp/progressia/server/world/ChunkTickContext.java +++ /dev/null @@ -1,49 +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.server.world; - -import java.util.function.Consumer; - -import glm.vec._3.i.Vec3i; -import ru.windcorp.progressia.common.world.ChunkData; -import ru.windcorp.progressia.server.world.block.BlockTickContext; - -public interface ChunkTickContext extends TickContext { - - Vec3i getChunk(); - - default ChunkLogic getChunkLogic() { - return getWorld().getChunk(getChunk()); - } - - default ChunkData getChunkData() { - ChunkLogic chunkLogic = getChunkLogic(); - return chunkLogic == null ? null : chunkLogic.getData(); - } - - default void forEachBlock(Consumer action) { - TickContextMutable context = TickContextMutable.uninitialized(); - - getChunkData().forEachBlock(blockInChunk -> { - context.rebuild().withServer(getServer()).withChunk(getChunk()).withBlockInChunk(blockInChunk).build(); - action.accept(context); - }); - } - -} diff --git a/src/main/java/ru/windcorp/progressia/server/world/DefaultChunkLogic.java b/src/main/java/ru/windcorp/progressia/server/world/DefaultChunkLogic.java new file mode 100644 index 0000000..d18ff11 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/DefaultChunkLogic.java @@ -0,0 +1,269 @@ +/* + * 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.server.world; + +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.function.BiConsumer; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.DefaultChunkData; +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.TileDataStack; +import ru.windcorp.progressia.common.world.generic.GenericChunks; +import ru.windcorp.progressia.common.world.TileDataReference; +import ru.windcorp.progressia.server.Server; +import ru.windcorp.progressia.server.world.block.BlockLogic; +import ru.windcorp.progressia.server.world.block.BlockLogicRegistry; +import ru.windcorp.progressia.server.world.block.TickableBlock; +import ru.windcorp.progressia.server.world.context.ServerBlockContextRO; +import ru.windcorp.progressia.server.world.context.ServerContexts; +import ru.windcorp.progressia.server.world.context.ServerTileContextRO; +import ru.windcorp.progressia.server.world.context.ServerWorldContextRO; +import ru.windcorp.progressia.server.world.tasks.TickChunk; +import ru.windcorp.progressia.server.world.ticking.TickingPolicy; +import ru.windcorp.progressia.server.world.tile.TickableTile; +import ru.windcorp.progressia.server.world.tile.TileLogic; +import ru.windcorp.progressia.server.world.tile.TileLogicRegistry; + +public class DefaultChunkLogic implements ChunkLogic { + + private final DefaultWorldLogic world; + private final DefaultChunkData data; + + private final Collection tickingBlocks = new ArrayList<>(); + private final Collection tickingTiles = new ArrayList<>(); + + private final TickChunk tickTask = new TickChunk(this); + + private final Map tileLogicLists = Collections + .synchronizedMap(new WeakHashMap<>()); + + public DefaultChunkLogic(DefaultWorldLogic world, DefaultChunkData data) { + this.world = world; + this.data = data; + + tmp_generateTickLists(); + } + + @Override + public Vec3i getPosition() { + return getData().getPosition(); + } + + @Override + public AbsFace getUp() { + return getData().getUp(); + } + + @Override + public BlockLogic getBlock(Vec3i blockInChunk) { + return BlockLogicRegistry.getInstance().get( + getData().getBlock(blockInChunk).getId() + ); + } + + @Override + public TileLogicStack getTiles(Vec3i blockInChunk, BlockFace face) { + return getTileStackWrapper(getData().getTiles(blockInChunk, face)); + } + + @Override + public boolean hasTiles(Vec3i blockInChunk, BlockFace face) { + return getData().hasTiles(blockInChunk, face); + } + + private TileLogicStack getTileStackWrapper(TileDataStack tileDataList) { + return tileLogicLists.computeIfAbsent( + tileDataList, + TileLogicStackImpl::new + ); + } + + public DefaultWorldLogic getWorld() { + return world; + } + + @Override + public DefaultChunkData getData() { + return data; + } + + @Override + public boolean isReady() { + return getWorld().getGenerator().isChunkReady(getData().getGenerationHint()); + } + + public boolean hasTickingBlocks() { + return !tickingBlocks.isEmpty(); + } + + public boolean hasTickingTiles() { + return !tickingTiles.isEmpty(); + } + + public void forEachTickingBlock(BiConsumer action) { + tickingBlocks.forEach(blockInChunk -> { + action.accept(blockInChunk, getBlock(blockInChunk)); + }); + } + + public void forEachTickingTile(BiConsumer action) { + tickingTiles.forEach(ref -> { + action.accept( + ref, + TileLogicRegistry.getInstance().get(ref.get().getId()) + ); + }); + } + + public TickChunk getTickTask() { + return tickTask; + } + + private class TileLogicStackImpl extends AbstractList implements TileLogicStack { + private class TileLogicReferenceImpl implements TileLogicReference { + private final TileDataReference parent; + + public TileLogicReferenceImpl (TileDataReference parent) { + this.parent = parent; + } + + @Override + public TileDataReference getDataReference() { + return parent; + } + + @Override + public TileLogic get() { + return TileLogicRegistry.getInstance().get(parent.get().getId()); + } + + @Override + public int getIndex() { + return parent.getIndex(); + } + + @Override + public TileLogicStack getStack() { + return TileLogicStackImpl.this; + } + } + + private final TileDataStack parent; + + public TileLogicStackImpl(TileDataStack parent) { + this.parent = parent; + } + + @Override + public Vec3i getBlockInChunk(Vec3i output) { + return parent.getBlockInChunk(output); + } + + @Override + public DefaultChunkLogic getChunk() { + return DefaultChunkLogic.this; + } + + @Override + public RelFace getFace() { + return parent.getFace(); + } + + @Override + public TileLogicReference getReference(int index) { + return new TileLogicReferenceImpl(parent.getReference(index)); + } + + @Override + public int getIndexByTag(int tag) { + return parent.getIndexByTag(tag); + } + + @Override + public int getTagByIndex(int index) { + return parent.getTagByIndex(index); + } + + @Override + public TileLogic get(int index) { + return TileLogicRegistry.getInstance().get(parent.get(index).getId()); + } + + @Override + public int size() { + return parent.size(); + } + + @Override + public TileDataStack getData() { + return parent; + } + + } + + private void tmp_generateTickLists() { + ServerWorldContextRO context = Server.getCurrentServer().createContext(getUp()); + + GenericChunks.forEachBiC(blockInChunk -> { + + ServerBlockContextRO blockContext = ServerContexts.pushAbs(context, this, blockInChunk); + BlockLogic block = getBlock(blockInChunk); + + if (!(block instanceof TickableBlock)) { + blockContext.pop(); + return; + } + + if (((TickableBlock) block).getTickingPolicy(blockContext) == TickingPolicy.REGULAR) { + tickingBlocks.add(blockInChunk.add_(0)); + } + + for (RelFace face : RelFace.getFaces()) { + TileLogicStack stack = getTilesOrNull(blockInChunk, face); + if (stack == null || stack.isEmpty()) continue; + + for (int i = 0; i < stack.size(); ++i) { + ServerTileContextRO tileContext = blockContext.push(context.toContext(face.resolve(getUp())), i); + TileLogic tile = stack.get(i); + + if (tile instanceof TickableTile) { + TickingPolicy policy = ((TickableTile) tile).getTickingPolicy(tileContext); + if (policy == TickingPolicy.REGULAR) { + tickingTiles.add(stack.getData().getReference(i)); + } + } + + tileContext.pop(); + } + } + + blockContext.pop(); + + }); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/DefaultWorldLogic.java b/src/main/java/ru/windcorp/progressia/server/world/DefaultWorldLogic.java new file mode 100644 index 0000000..3e654b6 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/DefaultWorldLogic.java @@ -0,0 +1,148 @@ +/* + * 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.server.world; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import glm.Glm; +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.util.crash.CrashReports; +import ru.windcorp.progressia.common.world.DefaultChunkData; +import ru.windcorp.progressia.common.world.ChunkDataListeners; +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.server.Server; +import ru.windcorp.progressia.server.world.generation.WorldGenerator; +import ru.windcorp.progressia.server.world.tasks.TickEntitiesTask; +import ru.windcorp.progressia.server.world.tasks.WorldAccessor; +import ru.windcorp.progressia.server.world.ticking.Evaluation; + +public class DefaultWorldLogic implements WorldLogic { + + private final DefaultWorldData data; + private final Server server; + + private final WorldGenerator generator; + + private final Map chunks = new HashMap<>(); + + private final Evaluation tickEntitiesTask = new TickEntitiesTask(); + + public DefaultWorldLogic(DefaultWorldData data, Server server, WorldGenerator generator, WorldAccessor accessor) { + this.data = data; + this.server = server; + + this.generator = generator; + data.setGravityModel(getGenerator().getGravityModel()); + + data.addListener(new WorldDataListener() { + @Override + public void onChunkLoaded(DefaultWorldData world, DefaultChunkData chunk) { + chunks.put(chunk, new DefaultChunkLogic(DefaultWorldLogic.this, chunk)); + } + + @Override + public void beforeChunkUnloaded(DefaultWorldData world, DefaultChunkData chunk) { + chunks.remove(chunk); + } + }); + + data.addListener(ChunkDataListeners.createAdder(new UpdateTriggerer(accessor))); + } + + @Override + public DefaultChunkLogic getChunk(Vec3i pos) { + return chunks.get(getData().getChunk(pos)); + } + + @Override + public Collection getChunks() { + return chunks.values(); + } + + @Override + public Collection getEntities() { + return getData().getEntities(); + } + + @Override + public EntityData getEntity(long entityId) { + return getData().getEntity(entityId); + } + + public Evaluation getTickEntitiesTask() { + return tickEntitiesTask; + } + + public Server getServer() { + return server; + } + + @Override + public DefaultWorldData getData() { + return data; + } + + public WorldGenerator getGenerator() { + return generator; + } + + public DefaultChunkData generate(Vec3i chunkPos) { + DefaultChunkData chunk = getGenerator().generate(chunkPos); + + if (!Glm.equals(chunkPos, chunk.getPosition())) { + throw CrashReports.report(null, "Generator %s has generated a chunk at (%d; %d; %d) when requested to generate a chunk at (%d; %d; %d)", + getGenerator(), + chunk.getX(), chunk.getY(), chunk.getZ(), + chunkPos.x, chunkPos.y, chunkPos.z + ); + } + + if (getData().getChunk(chunk.getPosition()) != chunk) { + if (isChunkLoaded(chunkPos)) { + throw CrashReports.report(null, "Generator %s has returned a chunk different to the chunk that is located at (%d; %d; %d)", + getGenerator(), + chunkPos.x, chunkPos.y, chunkPos.z + ); + } else { + throw CrashReports.report(null, "Generator %s has returned a chunk that is not loaded when requested to generate a chunk at (%d; %d; %d)", + getGenerator(), + chunkPos.x, chunkPos.y, chunkPos.z + ); + } + } + + if (!getChunk(chunk).isReady()) { + throw CrashReports.report(null, "Generator %s has returned a chunk that is not ready when requested to generate a chunk at (%d; %d; %d)", + getGenerator(), + chunkPos.x, chunkPos.y, chunkPos.z + ); + } + + return chunk; + } + + public DefaultChunkLogic getChunk(DefaultChunkData chunkData) { + return chunks.get(chunkData); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/TickAndUpdateUtil.java b/src/main/java/ru/windcorp/progressia/server/world/TickAndUpdateUtil.java index bb7fb2e..9e21c63 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/TickAndUpdateUtil.java +++ b/src/main/java/ru/windcorp/progressia/server/world/TickAndUpdateUtil.java @@ -20,23 +20,31 @@ package ru.windcorp.progressia.server.world; import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.common.util.crash.CrashReports; -import ru.windcorp.progressia.common.world.block.BlockFace; import ru.windcorp.progressia.common.world.entity.EntityData; +import ru.windcorp.progressia.common.world.rels.AbsFace; import ru.windcorp.progressia.server.Server; import ru.windcorp.progressia.server.world.block.BlockLogic; -import ru.windcorp.progressia.server.world.block.BlockTickContext; import ru.windcorp.progressia.server.world.block.TickableBlock; import ru.windcorp.progressia.server.world.block.UpdateableBlock; +import ru.windcorp.progressia.server.world.context.ServerBlockContext; +import ru.windcorp.progressia.server.world.context.ServerContexts; +import ru.windcorp.progressia.server.world.context.ServerTileContext; +import ru.windcorp.progressia.server.world.context.ServerTileStackContext; +import ru.windcorp.progressia.server.world.context.ServerWorldContext; import ru.windcorp.progressia.server.world.entity.EntityLogic; import ru.windcorp.progressia.server.world.entity.EntityLogicRegistry; import ru.windcorp.progressia.server.world.tile.TickableTile; import ru.windcorp.progressia.server.world.tile.TileLogic; -import ru.windcorp.progressia.server.world.tile.TileTickContext; import ru.windcorp.progressia.server.world.tile.UpdateableTile; public class TickAndUpdateUtil { - public static void tickBlock(TickableBlock block, BlockTickContext context) { + public static void tickBlock(ServerBlockContext context) { + BlockLogic uncheckedBlock = context.logic().getBlock(); + if (!(uncheckedBlock instanceof BlockLogic)) { + return; + } + TickableBlock block = (TickableBlock) uncheckedBlock; try { block.tick(context); } catch (Exception e) { @@ -44,16 +52,21 @@ public class TickAndUpdateUtil { } } - public static void tickBlock(WorldLogic world, Vec3i blockInWorld) { - BlockLogic block = world.getBlock(blockInWorld); - if (!(block instanceof TickableBlock)) - return; // also checks nulls - - BlockTickContext tickContext = TickContextMutable.start().withWorld(world).withBlock(blockInWorld).build(); - tickBlock((TickableBlock) block, tickContext); + public static void tickBlock(Server server, Vec3i blockInWorld) { + BlockLogic block = server.getWorld().getBlock(blockInWorld); + if (!(block instanceof TickableBlock)) { + return; + } + ServerBlockContext context = server.createContext(blockInWorld); + tickBlock(context); } - public static void tickTile(TickableTile tile, TileTickContext context) { + public static void tickTile(ServerTileContext context) { + TileLogic uncheckedTile = context.logic().getTile(); + if (!(uncheckedTile instanceof TickableTile)) { + return; + } + TickableTile tile = (TickableTile) uncheckedTile; try { tile.tick(context); } catch (Exception e) { @@ -61,72 +74,86 @@ public class TickAndUpdateUtil { } } - public static void tickTile(WorldLogic world, Vec3i blockInWorld, BlockFace face, int layer) { - TileLogic tile = world.getTile(blockInWorld, face, layer); - if (!(tile instanceof TickableTile)) + public static void tickTile(Server server, Vec3i blockInWorld, AbsFace face, int layer) { + TileLogic tile = server.getWorld().getTile(blockInWorld, face, layer); + if (!(tile instanceof TickableTile)) { return; - - TileTickContext tickContext = TickContextMutable.start().withWorld(world).withBlock(blockInWorld).withFace(face) - .withLayer(layer); - tickTile((TickableTile) tile, tickContext); + } + ServerTileContext context = ServerContexts.pushAbs(server.createContext(blockInWorld), blockInWorld, face) + .push(layer); + tickTile(context); } - public static void tickTiles(WorldLogic world, Vec3i blockInWorld, BlockFace face) { - TickContextMutable.start().withWorld(world).withBlock(blockInWorld).withFace(face).build() - .forEachTile(context -> { - TileLogic tile = context.getTile(); - if (tile instanceof TickableTile) { - tickTile((TickableTile) tile, context); - } - }); + public static void tickTiles(Server server, Vec3i blockInWorld, AbsFace face) { + if (!server.getWorld().hasTiles(blockInWorld, face)) { + return; + } + + ServerTileStackContext context = ServerContexts.pushAbs(server.createContext(blockInWorld), blockInWorld, face); + for (int i = 0; i < context.getTileCount(); ++i) { + tickTile(context.push(i)); + context.pop(); + } } - public static void updateBlock(UpdateableBlock block, BlockTickContext context) { + public static void updateBlock(ServerBlockContext context) { + BlockLogic uncheckedBlock = context.logic().getBlock(); + if (!(uncheckedBlock instanceof BlockLogic)) { + return; + } + UpdateableBlock block = (UpdateableBlock) uncheckedBlock; try { block.update(context); } catch (Exception e) { - throw CrashReports.report(e, "Could not update block {}", block); + throw CrashReports.report(e, "Could not update block %s", block); } } - public static void updateBlock(WorldLogic world, Vec3i blockInWorld) { - BlockLogic block = world.getBlock(blockInWorld); - if (!(block instanceof UpdateableBlock)) - return; // also checks nulls - - BlockTickContext tickContext = TickContextMutable.start().withWorld(world).withBlock(blockInWorld).build(); - updateBlock((UpdateableBlock) block, tickContext); + public static void updateBlock(Server server, Vec3i blockInWorld) { + BlockLogic block = server.getWorld().getBlock(blockInWorld); + if (!(block instanceof UpdateableBlock)) { + return; + } + ServerBlockContext context = server.createContext(blockInWorld); + updateBlock(context); } - public static void updateTile(UpdateableTile tile, TileTickContext context) { + public static void updateTile(ServerTileContext context) { + TileLogic uncheckedTile = context.logic().getTile(); + if (!(uncheckedTile instanceof UpdateableTile)) { + return; + } + UpdateableTile tile = (UpdateableTile) uncheckedTile; try { tile.update(context); } catch (Exception e) { - throw CrashReports.report(e, "Could not update tile {}", tile); + throw CrashReports.report(e, "Could not tick tile %s", tile); } } - public static void updateTile(WorldLogic world, Vec3i blockInWorld, BlockFace face, int layer) { - TileLogic tile = world.getTile(blockInWorld, face, layer); - if (!(tile instanceof UpdateableTile)) + public static void updateTile(Server server, Vec3i blockInWorld, AbsFace face, int layer) { + TileLogic tile = server.getWorld().getTile(blockInWorld, face, layer); + if (!(tile instanceof UpdateableTile)) { return; - - TileTickContext tickContext = TickContextMutable.start().withWorld(world).withBlock(blockInWorld).withFace(face) - .withLayer(layer); - updateTile((UpdateableTile) tile, tickContext); + } + ServerTileContext context = ServerContexts.pushAbs(server.createContext(blockInWorld), blockInWorld, face) + .push(layer); + updateTile(context); } - public static void updateTiles(WorldLogic world, Vec3i blockInWorld, BlockFace face) { - TickContextMutable.start().withWorld(world).withBlock(blockInWorld).withFace(face).build() - .forEachTile(context -> { - TileLogic tile = context.getTile(); - if (tile instanceof UpdateableTile) { - updateTile((UpdateableTile) tile, context); - } - }); + public static void updateTiles(Server server, Vec3i blockInWorld, AbsFace face) { + if (!server.getWorld().hasTiles(blockInWorld, face)) { + return; + } + + ServerTileStackContext context = ServerContexts.pushAbs(server.createContext(blockInWorld), blockInWorld, face); + for (int i = 0; i < context.getTileCount(); ++i) { + updateTile(context.push(i)); + context.pop(); + } } - public static void tickEntity(EntityLogic logic, EntityData data, TickContext context) { + public static void tickEntity(EntityLogic logic, EntityData data, ServerWorldContext context) { try { logic.tick(data, context); } catch (Exception e) { @@ -135,8 +162,11 @@ public class TickAndUpdateUtil { } public static void tickEntity(EntityData data, Server server) { - tickEntity(EntityLogicRegistry.getInstance().get(data.getId()), data, - TickContextMutable.start().withServer(server).build()); + tickEntity( + EntityLogicRegistry.getInstance().get(data.getId()), + data, + server.createContext(data.getUpFace()) + ); } private TickAndUpdateUtil() { diff --git a/src/main/java/ru/windcorp/progressia/server/world/TickContextMutable.java b/src/main/java/ru/windcorp/progressia/server/world/TickContextMutable.java deleted file mode 100644 index 2c92857..0000000 --- a/src/main/java/ru/windcorp/progressia/server/world/TickContextMutable.java +++ /dev/null @@ -1,478 +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.server.world; - -import java.util.Objects; -import java.util.function.Consumer; -import java.util.function.Function; - -import glm.vec._3.i.Vec3i; -import ru.windcorp.progressia.common.world.ChunkData; -import ru.windcorp.progressia.common.world.Coordinates; -import ru.windcorp.progressia.common.world.block.BlockFace; -import ru.windcorp.progressia.common.world.generic.GenericTileStack; -import ru.windcorp.progressia.common.world.tile.TileDataStack; -import ru.windcorp.progressia.common.world.tile.TileReference; -import ru.windcorp.progressia.server.Server; -import ru.windcorp.progressia.server.world.block.BlockTickContext; -import ru.windcorp.progressia.server.world.tile.TSTickContext; -import ru.windcorp.progressia.server.world.tile.TileTickContext; - -public abstract class TickContextMutable implements BlockTickContext, TSTickContext, TileTickContext { - - private static enum Role { - NONE, WORLD, CHUNK, BLOCK, TILE_STACK, TILE; - } - - /* - * TickContextMutable interface - */ - - // Only TickContextMutable.Impl can extend; extend Impl if need be - private TickContextMutable() { - } - - public abstract Builder.Empty rebuild(); - - /* - * Static methods - */ - - public static TickContextMutable uninitialized() { - return new Impl(); - } - - public static Builder.Empty start() { - return uninitialized().rebuild(); - } - - public static Builder.World copyWorld(TickContext context) { - return start().withServer(context.getServer()); - } - - public static Builder.Chunk copyChunk(ChunkTickContext context) { - return start().withChunk(context.getChunkLogic()); - } - - public static Builder.Block copyBlock(BlockTickContext context) { - return copyWorld(context).withBlock(context.getBlockInWorld()); - } - - public static Builder.TileStack copyTS(TSTickContext context) { - return copyBlock(context).withFace(context.getFace()); - } - - public static TileTickContext copyTile(TileTickContext context) { - return copyTS(context).withLayer(context.getLayer()); - } - - /* - * Builder interfaces - */ - - public static interface Builder { - TickContextMutable build(); - - public static interface Empty /* does not extend Builder */ { - World withServer(Server server); - - default Builder.World withWorld(WorldLogic world) { - Objects.requireNonNull(world, "world"); - return withServer(world.getServer()); - } - - default Builder.Chunk withChunk(ChunkLogic chunk) { - Objects.requireNonNull(chunk, "chunk"); - return withWorld(chunk.getWorld()).withChunk(chunk.getPosition()); - } - } - - public static interface World extends Builder { - Chunk withChunk(Vec3i chunk); - - Block withBlock(Vec3i blockInWorld); - - TileStack withTS(GenericTileStack tileStack); - - default Builder.Chunk withChunk(ChunkData chunk) { - Objects.requireNonNull(chunk, "chunk"); - return withChunk(chunk.getPosition()); - } - - default TileTickContext withTile(TileReference ref) { - Objects.requireNonNull(ref, "ref"); - return withTS(ref.getStack()).withLayer(ref.getIndex()); - } - } - - public static interface Chunk extends Builder { - Builder.Block withBlockInChunk(Vec3i blockInChunk); - } - - public static interface Block extends Builder { - Builder.TileStack withFace(BlockFace face); - } - - public static interface TileStack extends Builder { - TickContextMutable withLayer(int layer); - } - } - - /* - * Impl - */ - - public static class Impl extends TickContextMutable - implements Builder.Empty, Builder.World, Builder.Chunk, Builder.Block, Builder.TileStack { - - protected Impl() { - } - - protected Server server; - protected final Vec3i chunk = new Vec3i(); - protected final Vec3i blockInWorld = new Vec3i(); - protected BlockFace face; - protected int layer; - - protected Role role = Role.NONE; - protected boolean isBeingBuilt = false; - - /** - * Updated lazily - */ - protected final Vec3i blockInChunk = new Vec3i(); - - /* - * TickContextMutable - */ - - @Override - public Server getServer() { - checkContextState(Role.WORLD); - return this.server; - } - - @Override - public float getTickLength() { - checkContextState(Role.WORLD); - return (float) this.server.getTickLength(); - } - - @Override - public Vec3i getChunk() { - checkContextState(Role.CHUNK); - return this.chunk; - } - - @Override - public Vec3i getBlockInWorld() { - checkContextState(Role.BLOCK); - return this.blockInWorld; - } - - @Override - public BlockFace getFace() { - checkContextState(Role.TILE_STACK); - return this.face; - } - - @Override - public int getLayer() { - checkContextState(Role.TILE); - return this.layer; - } - - @Override - public Builder.Empty rebuild() { - this.role = Role.NONE; - this.isBeingBuilt = true; - - this.server = null; - this.chunk.set(0); - this.blockInWorld.set(0); - this.face = null; - this.layer = -1; - - return this; - } - - /* - * Builder memo: do NOT use Context getters, they throw ISEs - */ - - @Override - public TickContextMutable build() { - checkBuilderState(null); - this.isBeingBuilt = false; - return this; - } - - @Override - public World withServer(Server server) { - Objects.requireNonNull(server, "server"); - checkBuilderState(Role.NONE); - this.server = server; - this.role = Role.WORLD; - return this; - } - - @Override - public Chunk withChunk(Vec3i chunk) { - Objects.requireNonNull(chunk, "chunk"); - checkBuilderState(Role.WORLD); - - this.chunk.set(chunk.x, chunk.y, chunk.z); - - this.role = Role.CHUNK; - return this; - } - - @Override - public Block withBlock(Vec3i blockInWorld) { - Objects.requireNonNull(blockInWorld, "blockInWorld"); - checkBuilderState(Role.WORLD); - - this.blockInWorld.set(blockInWorld.x, blockInWorld.y, blockInWorld.z); - Coordinates.convertInWorldToChunk(blockInWorld, this.chunk); - - this.role = Role.BLOCK; - return this; - } - - @Override - public TileStack withTS(GenericTileStack tileStack) { - Objects.requireNonNull(tileStack, "tileStack"); - - return withBlock(tileStack.getBlockInWorld(this.blockInWorld)).withFace(tileStack.getFace()); - // ^^^^^^^^^^^^^^^^^ This is safe - } - - @Override - public Block withBlockInChunk(Vec3i blockInChunk) { - Objects.requireNonNull(blockInChunk, "blockInChunk"); - checkBuilderState(Role.CHUNK); - - Coordinates.getInWorld(this.chunk, blockInChunk, this.blockInWorld); - - this.role = Role.BLOCK; - return this; - } - - @Override - public TileStack withFace(BlockFace face) { - Objects.requireNonNull(face, "face"); - checkBuilderState(Role.BLOCK); - - this.face = face; - - this.role = Role.TILE_STACK; - return this; - } - - @Override - public TickContextMutable withLayer(int layer) { - checkBuilderState(Role.TILE); - - this.layer = layer; - - this.role = Role.TILE; - return build(); - } - - /* - * Optimization - */ - - @Override - public Vec3i getBlockInChunk() { - return Coordinates.convertInWorldToInChunk(getBlockInWorld(), this.blockInChunk); - } - - @Override - public void forEachBlock(Consumer action) { - checkContextState(Role.CHUNK); - - Vec3i v = this.blockInWorld; - - int previousX = v.x; - int previousY = v.y; - int previousZ = v.z; - Role previousRole = this.role; - - this.role = Role.BLOCK; - - final int minX = Coordinates.getInWorld(chunk.x, 0); - final int minY = Coordinates.getInWorld(chunk.y, 0); - final int minZ = Coordinates.getInWorld(chunk.z, 0); - final int size = ChunkData.BLOCKS_PER_CHUNK; - - for (v.x = minX; v.x < minX + size; ++v.x) { - for (v.y = minY; v.y < minY + size; ++v.y) { - for (v.z = minZ; v.z < minZ + size; ++v.z) { - action.accept(this); - } - } - } - - this.role = previousRole; - blockInWorld.set(previousX, previousY, previousZ); - } - - @Override - public void forEachFace(Consumer action) { - checkContextState(Role.BLOCK); - BlockFace previousFace = this.face; - Role previousRole = this.role; - - this.role = Role.TILE_STACK; - for (int i = 0; i < BlockFace.BLOCK_FACE_COUNT; ++i) { - this.face = BlockFace.getFaces().get(i); - action.accept(this); - } - - this.role = previousRole; - this.face = previousFace; - } - - @Override - public R evalNeighbor(Vec3i direction, Function action) { - this.blockInWorld.add(direction); - R result = action.apply(this); - this.blockInWorld.sub(direction); - - return result; - } - - @Override - public void forNeighbor(Vec3i direction, Consumer action) { - this.blockInWorld.add(direction); - action.accept(this); - this.blockInWorld.sub(direction); - } - - @Override - public boolean forEachTile(Consumer action) { - checkContextState(Role.TILE_STACK); - int previousLayer = this.layer; - Role previousRole = this.role; - - this.role = Role.TILE; - TileDataStack stack = getTDSOrNull(); - if (stack == null || stack.isEmpty()) - return false; - - for (this.layer = 0; this.layer < stack.size(); ++this.layer) { - action.accept(this); - } - - this.role = previousRole; - this.layer = previousLayer; - return true; - } - - @Override - public R evalComplementary(Function action) { - Objects.requireNonNull(action, "action"); - checkContextState(Role.TILE_STACK); - - this.blockInWorld.add(this.face.getVector()); - this.face = this.face.getCounter(); - R result = action.apply(this); - this.face = this.face.getCounter(); - this.blockInWorld.sub(this.face.getVector()); - - return result; - } - - @Override - public void forComplementary(Consumer action) { - Objects.requireNonNull(action, "action"); - checkContextState(Role.TILE_STACK); - - this.blockInWorld.add(this.face.getVector()); - this.face = this.face.getCounter(); - action.accept(this); - this.face = this.face.getCounter(); - this.blockInWorld.sub(this.face.getVector()); - } - - /* - * Misc - */ - - protected void checkContextState(Role requiredRole) { - if (isBeingBuilt) { - throw new IllegalStateException("This context is still being built"); - } - - if ((role == null) || (requiredRole.compareTo(role) > 0)) { - throw new IllegalStateException( - "This context is currently initialized as " + role + "; requested " + requiredRole); - } - } - - protected void checkBuilderState(Role requiredRole) { - if (!isBeingBuilt) { - throw new IllegalStateException("This context is already built"); - } - - if (requiredRole == null) { - if (role == Role.NONE) { - throw new IllegalStateException("This context is currently not initialized"); - } - } else { - if (role != requiredRole) { - throw new IllegalStateException( - "This context is currently initialized as " + role + "; requested " + requiredRole); - } - } - } - - @Override - public String toString() { - final String format; - - switch (this.role) { - case WORLD: - format = "TickContext"; - break; - case CHUNK: - format = "(%2$d; %3$d; %4$d)"; - break; - case BLOCK: - format = "(%5$d; %6$d; %7$d)"; - break; - case TILE_STACK: - format = "((%5$d; %6$d; %7$d); %8$6s)"; - break; - case TILE: - format = "((%5$d; %6$d; %7$d); %8$6s; %9$d)"; - break; - case NONE: - default: - format = "Uninitialized TickContextMutable"; - break; - } - - return String.format(format, this.chunk.x, this.chunk.y, this.chunk.z, this.blockInWorld.x, - this.blockInWorld.y, this.blockInWorld.z, this.face, this.layer); - } - } - -} diff --git a/src/main/java/ru/windcorp/progressia/server/world/TileLogicReference.java b/src/main/java/ru/windcorp/progressia/server/world/TileLogicReference.java new file mode 100644 index 0000000..e56cffb --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/TileLogicReference.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.server.world; + +import ru.windcorp.progressia.common.world.TileDataReference; +import ru.windcorp.progressia.common.world.generic.TileGenericReferenceRO; +import ru.windcorp.progressia.common.world.tile.TileData; +import ru.windcorp.progressia.server.world.tile.TileLogic; + +/** + * A read-only {@link TileGenericReferenceRO TileReference} for a + * {@link TileData} that provides convenient read-write access to the matching + * {@link TileLogic} instance. + *

    + * For all methods other than {@link #get()}, {@link #getStack()} and + * {@link #getDataReference()}, + * logicRef.method() == logicRef.getDataReference().method(). + */ +public interface TileLogicReference + extends TileLogicReferenceRO { + + /* + * Override return type + */ + + @Override + TileDataReference getDataReference(); + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/TileLogicReferenceRO.java b/src/main/java/ru/windcorp/progressia/server/world/TileLogicReferenceRO.java new file mode 100644 index 0000000..2a92764 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/TileLogicReferenceRO.java @@ -0,0 +1,82 @@ +/* + * 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.server.world; + +import ru.windcorp.progressia.common.world.TileDataReference; +import ru.windcorp.progressia.common.world.TileDataReferenceRO; +import ru.windcorp.progressia.common.world.generic.TileGenericReferenceRO; +import ru.windcorp.progressia.common.world.tile.TileData; +import ru.windcorp.progressia.server.world.block.BlockLogic; +import ru.windcorp.progressia.server.world.tile.TileLogic; + +/** + * A {@link TileGenericReferenceRO TileReference} for a {@link TileData} that + * provides convenient access to the matching {@link TileLogic} instance. + *

    + * For all methods other than {@link #get()}, {@link #getStack()} and + * {@link #getDataReference()}, + * logicRef.method() == logicRef.getDataReference().method(). + */ +public interface TileLogicReferenceRO + extends TileGenericReferenceRO { + + /** + * Returns a reference to the {@link TileData} that this object references. + * + * @return a {@link TileDataReference} equivalent to this object + */ + TileDataReferenceRO getDataReference(); + + /** + * Returns the {@link TileData} that is referenced by this object, or + * {@code null} if the tile does not exist. + * + * @return the relevant {@link TileData} + */ + default TileData getData() { + return getDataReference().get(); + } + + /** + * {@inheritDoc} + * + * @see #getData() + */ + @Override + TileLogic get(); + + /* + * Refer to #getDataReference() by default + */ + + @Override + default int getIndex() { + return getDataReference().getIndex(); + } + + @Override + default int getTag() { + return getDataReference().getTag(); + } + + @Override + default boolean isValid() { + return getDataReference().isValid(); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/TileLogicStack.java b/src/main/java/ru/windcorp/progressia/server/world/TileLogicStack.java new file mode 100644 index 0000000..3b8ef2f --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/TileLogicStack.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.server.world; + +import ru.windcorp.progressia.common.world.TileDataStack; + +public interface TileLogicStack extends TileLogicStackRO { + + /* + * Override return types + */ + + @Override + TileDataStack getData(); + + @Override + ChunkLogic getChunk(); + + @Override + TileLogicReference getReference(int index); + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/TileLogicStackRO.java b/src/main/java/ru/windcorp/progressia/server/world/TileLogicStackRO.java new file mode 100644 index 0000000..3561663 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/TileLogicStackRO.java @@ -0,0 +1,31 @@ +/* + * 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.server.world; + +import ru.windcorp.progressia.common.world.TileDataStackRO; +import ru.windcorp.progressia.common.world.generic.TileGenericStackRO; +import ru.windcorp.progressia.server.world.block.BlockLogic; +import ru.windcorp.progressia.server.world.tile.TileLogic; + +public interface TileLogicStackRO + extends TileGenericStackRO { + + public abstract TileDataStackRO getData(); + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/UpdateTriggerer.java b/src/main/java/ru/windcorp/progressia/server/world/UpdateTriggerer.java index a7c122a..8175937 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/UpdateTriggerer.java +++ b/src/main/java/ru/windcorp/progressia/server/world/UpdateTriggerer.java @@ -19,31 +19,41 @@ package ru.windcorp.progressia.server.world; import glm.vec._3.i.Vec3i; -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.Coordinates; 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; -import ru.windcorp.progressia.server.Server; +import ru.windcorp.progressia.server.world.tasks.WorldAccessor; public class UpdateTriggerer implements ChunkDataListener { - private final Server server; + private final WorldAccessor worldAccessor; - public UpdateTriggerer(Server server) { - this.server = server; + public UpdateTriggerer(WorldAccessor worldAccessor) { + this.worldAccessor = worldAccessor; } @Override - public void onChunkBlockChanged(ChunkData chunk, Vec3i blockInChunk, BlockData previous, BlockData current) { - server.getWorldAccessor().triggerUpdates(Coordinates.getInWorld(chunk.getPosition(), blockInChunk, null)); + public void onChunkBlockChanged( + DefaultChunkData chunk, + Vec3i blockInChunk, + BlockData previous, + BlockData current + ) { + worldAccessor.triggerUpdates(Coordinates.getInWorld(chunk.getPosition(), blockInChunk, null)); } @Override - public void onChunkTilesChanged(ChunkData chunk, Vec3i blockInChunk, BlockFace face, TileData tile, - boolean wasAdded) { - server.getWorldAccessor().triggerUpdates(Coordinates.getInWorld(chunk.getPosition(), blockInChunk, null), face); + public void onChunkTilesChanged( + DefaultChunkData chunk, + Vec3i blockInChunk, + RelFace face, + TileData tile, + boolean wasAdded + ) { + worldAccessor.triggerUpdates(Coordinates.getInWorld(chunk.getPosition(), blockInChunk, null), face); } } diff --git a/src/main/java/ru/windcorp/progressia/server/world/WorldLogic.java b/src/main/java/ru/windcorp/progressia/server/world/WorldLogic.java index 2f3a32b..f0cdb0a 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/WorldLogic.java +++ b/src/main/java/ru/windcorp/progressia/server/world/WorldLogic.java @@ -15,104 +15,42 @@ * 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; import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Function; import glm.vec._3.i.Vec3i; -import ru.windcorp.progressia.common.world.ChunkData; -import ru.windcorp.progressia.common.world.ChunkDataListeners; import ru.windcorp.progressia.common.world.WorldData; -import ru.windcorp.progressia.common.world.WorldDataListener; -import ru.windcorp.progressia.common.world.entity.EntityData; -import ru.windcorp.progressia.common.world.generic.GenericWorld; -import ru.windcorp.progressia.server.Server; -import ru.windcorp.progressia.server.world.block.BlockLogic; -import ru.windcorp.progressia.server.world.generation.WorldGenerator; -import ru.windcorp.progressia.server.world.tasks.TickEntitiesTask; -import ru.windcorp.progressia.server.world.ticking.Evaluation; -import ru.windcorp.progressia.server.world.tile.TileLogic; -import ru.windcorp.progressia.server.world.tile.TileLogicStack; +import ru.windcorp.progressia.common.world.rels.BlockFace; -public class WorldLogic implements GenericWorld { +public interface WorldLogic extends WorldLogicRO { - private final WorldData data; - private final Server server; + /* + * Override return types + */ + + @Override + WorldData getData(); - private final WorldGenerator generator; + @Override + ChunkLogic getChunk(Vec3i pos); - private final Map chunks = new HashMap<>(); + @Override + Collection getChunks(); - private final Evaluation tickEntitiesTask = new TickEntitiesTask(); - - public WorldLogic(WorldData data, Server server, Function worldGeneratorConstructor) { - this.data = data; - this.server = server; - this.generator = worldGeneratorConstructor.apply(this); - - data.addListener(new WorldDataListener() { - @Override - public void onChunkLoaded(WorldData world, ChunkData chunk) { - chunks.put(chunk, new ChunkLogic(WorldLogic.this, chunk)); - } - - @Override - public void beforeChunkUnloaded(WorldData world, ChunkData chunk) { - chunks.remove(chunk); - } - }); - - data.addListener(ChunkDataListeners.createAdder(new UpdateTriggerer(server))); + @Override + default ChunkLogic getChunkByBlock(Vec3i blockInWorld) { + return (ChunkLogic) WorldLogicRO.super.getChunkByBlock(blockInWorld); } @Override - public ChunkLogic getChunk(Vec3i pos) { - return chunks.get(getData().getChunk(pos)); + default TileLogicStack getTiles(Vec3i blockInWorld, BlockFace face) { + return (TileLogicStack) WorldLogicRO.super.getTiles(blockInWorld, face); } @Override - public Collection getChunks() { - return chunks.values(); - } - - @Override - public Collection getEntities() { - return getData().getEntities(); - } - - public Evaluation getTickEntitiesTask() { - return tickEntitiesTask; - } - - public Server getServer() { - return server; - } - - public WorldData getData() { - return data; - } - - public WorldGenerator getGenerator() { - return generator; - } - - public ChunkData generate(Vec3i chunkPos) { - return getGenerator().generate(chunkPos, getData()); - } - - public ChunkLogic getChunk(ChunkData chunkData) { - return chunks.get(chunkData); + default TileLogicStack getTilesOrNull(Vec3i blockInWorld, BlockFace face) { + return (TileLogicStack) WorldLogicRO.super.getTilesOrNull(blockInWorld, face); } } diff --git a/src/main/java/ru/windcorp/progressia/server/world/WorldLogicRO.java b/src/main/java/ru/windcorp/progressia/server/world/WorldLogicRO.java new file mode 100644 index 0000000..7f98ef9 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/WorldLogicRO.java @@ -0,0 +1,31 @@ +/* + * 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.server.world; + +import ru.windcorp.progressia.common.world.WorldDataRO; +import ru.windcorp.progressia.common.world.entity.EntityData; +import ru.windcorp.progressia.common.world.generic.WorldGenericRO; +import ru.windcorp.progressia.server.world.block.BlockLogic; +import ru.windcorp.progressia.server.world.tile.TileLogic; + +public interface WorldLogicRO + extends WorldGenericRO { + + WorldDataRO getData(); + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/block/BlockLogic.java b/src/main/java/ru/windcorp/progressia/server/world/block/BlockLogic.java index fcf141d..f873093 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/block/BlockLogic.java +++ b/src/main/java/ru/windcorp/progressia/server/world/block/BlockLogic.java @@ -19,24 +19,25 @@ package ru.windcorp.progressia.server.world.block; import ru.windcorp.progressia.common.util.namespaces.Namespaced; -import ru.windcorp.progressia.common.world.block.BlockFace; -import ru.windcorp.progressia.common.world.generic.GenericBlock; +import ru.windcorp.progressia.common.world.generic.BlockGeneric; +import ru.windcorp.progressia.common.world.rels.RelFace; +import ru.windcorp.progressia.server.world.context.ServerBlockContextRO; -public class BlockLogic extends Namespaced implements GenericBlock { +public class BlockLogic extends Namespaced implements BlockGeneric { public BlockLogic(String id) { super(id); } - public boolean isSolid(BlockTickContext context, BlockFace face) { + public boolean isSolid(ServerBlockContextRO context, RelFace face) { return isSolid(face); } - public boolean isSolid(BlockFace face) { + public boolean isSolid(RelFace face) { return true; } - public boolean isTransparent(BlockTickContext context) { + public boolean isTransparent(ServerBlockContextRO context) { return isTransparent(); } diff --git a/src/main/java/ru/windcorp/progressia/server/world/block/BlockTickContext.java b/src/main/java/ru/windcorp/progressia/server/world/block/BlockTickContext.java deleted file mode 100644 index d10f10c..0000000 --- a/src/main/java/ru/windcorp/progressia/server/world/block/BlockTickContext.java +++ /dev/null @@ -1,114 +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.server.world.block; - -import java.util.Objects; -import java.util.function.Consumer; -import java.util.function.Function; - -import glm.vec._3.i.Vec3i; -import ru.windcorp.progressia.common.world.Coordinates; -import ru.windcorp.progressia.common.world.block.BlockData; -import ru.windcorp.progressia.common.world.block.BlockFace; -import ru.windcorp.progressia.common.world.block.BlockRelation; -import ru.windcorp.progressia.server.world.ChunkTickContext; -import ru.windcorp.progressia.server.world.TickContextMutable; -import ru.windcorp.progressia.server.world.tile.TSTickContext; - -public interface BlockTickContext extends ChunkTickContext { - - /** - * Returns the current world coordinates. - * - * @return the world coordinates of the block being ticked - */ - Vec3i getBlockInWorld(); - - default Vec3i getBlockInChunk() { - return Coordinates.convertInWorldToInChunk(getBlockInWorld(), null); - } - - default BlockLogic getBlock() { - return getWorld().getBlock(getBlockInWorld()); - } - - default BlockData getBlockData() { - return getWorldData().getBlock(getBlockInWorld()); - } - - default void forEachFace(Consumer action) { - Objects.requireNonNull(action, "action"); - TickContextMutable context = TickContextMutable.uninitialized(); - - for (BlockFace face : BlockFace.getFaces()) { - context.rebuild().withServer(getServer()).withBlock(getBlockInWorld()).withFace(face).build(); - action.accept(context); - } - } - - default BlockTickContext getNeighbor(Vec3i direction) { - Objects.requireNonNull(direction, "direction"); - return TickContextMutable.copyWorld(this).withBlock(getBlockInWorld().add_(direction)).build(); - } - - default BlockTickContext getNeighbor(BlockRelation relation) { - Objects.requireNonNull(relation, "relation"); - return getNeighbor(relation.getVector()); - } - - default R evalNeighbor(Vec3i direction, Function action) { - Objects.requireNonNull(action, "action"); - Objects.requireNonNull(direction, "direction"); - return action.apply(getNeighbor(direction)); - } - - default R evalNeighbor(BlockRelation relation, Function action) { - Objects.requireNonNull(action, "action"); - Objects.requireNonNull(relation, "relation"); - return evalNeighbor(relation.getVector(), action); - } - - default void forNeighbor(Vec3i direction, Consumer action) { - Objects.requireNonNull(action, "action"); - Objects.requireNonNull(direction, "direction"); - evalNeighbor(direction, (Function) ctxt -> { - action.accept(ctxt); - return null; - }); - } - - default void forNeighbor(BlockRelation relation, Consumer action) { - Objects.requireNonNull(action, "action"); - Objects.requireNonNull(relation, "relation"); - forNeighbor(relation.getVector(), action); - } - - /* - * Convenience methods - changes - */ - - default void setThisBlock(BlockData block) { - getAccessor().setBlock(getBlockInWorld(), block); - } - - default void setThisBlock(String id) { - getAccessor().setBlock(getBlockInWorld(), id); - } - -} diff --git a/src/main/java/ru/windcorp/progressia/server/world/block/TickableBlock.java b/src/main/java/ru/windcorp/progressia/server/world/block/TickableBlock.java index 62b830f..666cfd5 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/block/TickableBlock.java +++ b/src/main/java/ru/windcorp/progressia/server/world/block/TickableBlock.java @@ -18,12 +18,14 @@ package ru.windcorp.progressia.server.world.block; +import ru.windcorp.progressia.server.world.context.ServerBlockContext; +import ru.windcorp.progressia.server.world.context.ServerBlockContextRO; import ru.windcorp.progressia.server.world.ticking.TickingPolicy; public interface TickableBlock { - void tick(BlockTickContext context); + void tick(ServerBlockContext context); - TickingPolicy getTickingPolicy(BlockTickContext context); + TickingPolicy getTickingPolicy(ServerBlockContextRO context); } diff --git a/src/main/java/ru/windcorp/progressia/server/world/block/UpdateableBlock.java b/src/main/java/ru/windcorp/progressia/server/world/block/UpdateableBlock.java index 12ae51c..757e972 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/block/UpdateableBlock.java +++ b/src/main/java/ru/windcorp/progressia/server/world/block/UpdateableBlock.java @@ -15,16 +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.server.world.block; -import org.apache.logging.log4j.LogManager; +import ru.windcorp.progressia.server.world.context.ServerBlockContext; public interface UpdateableBlock { - default void update(BlockTickContext context) { - LogManager.getLogger().info("Updating block {} @ ({}; {}; {})", context.getBlock(), context.getBlockInWorld().x, - context.getBlockInWorld().y, context.getBlockInWorld().z); - } + void update(ServerBlockContext context); } diff --git a/src/main/java/ru/windcorp/progressia/server/world/context/ServerBlockContext.java b/src/main/java/ru/windcorp/progressia/server/world/context/ServerBlockContext.java new file mode 100644 index 0000000..e0b5c70 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/context/ServerBlockContext.java @@ -0,0 +1,87 @@ +/* + * 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.server.world.context; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.context.BlockDataContext; +import ru.windcorp.progressia.common.world.rels.RelFace; +import ru.windcorp.progressia.common.world.rels.RelRelation; + +public interface ServerBlockContext extends BlockDataContext, ServerWorldContext, ServerBlockContextRO { + + public interface Logic extends ServerBlockContextRO.Logic, ServerWorldContext.Logic { + + @Override + ServerBlockContext data(); + + @Override + default ServerBlockContext.Logic pushRelative(int dx, int dy, int dz) { + return push(getLocation().add_(dx, dy, dz)); + } + + @Override + default ServerBlockContext.Logic pushRelative(Vec3i direction) { + return push(getLocation().add_(direction)); + } + + @Override + default ServerBlockContext.Logic pushRelative(RelRelation direction) { + return push(getLocation().add_(direction.getRelVector())); + } + + @Override + default ServerTileStackContext.Logic push(RelFace face) { + return push(getLocation(), face); + } + + @Override + default ServerTileContext.Logic push(RelFace face, int layer) { + return push(getLocation(), face, layer); + } + + } + + @Override + ServerBlockContext.Logic logic(); + + @Override + default ServerBlockContext pushRelative(int dx, int dy, int dz) { + return push(getLocation().add_(dx, dy, dz)); + } + + @Override + default ServerBlockContext pushRelative(Vec3i direction) { + return push(getLocation().add_(direction)); + } + + @Override + default ServerBlockContext pushRelative(RelRelation direction) { + return push(getLocation().add_(direction.getRelVector())); + } + + @Override + default ServerTileStackContext push(RelFace face) { + return push(getLocation(), face); + } + + @Override + default ServerTileContext push(RelFace face, int layer) { + return push(getLocation(), face, layer); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/context/ServerBlockContextRO.java b/src/main/java/ru/windcorp/progressia/server/world/context/ServerBlockContextRO.java new file mode 100644 index 0000000..1fdb90e --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/context/ServerBlockContextRO.java @@ -0,0 +1,92 @@ +/* + * 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.server.world.context; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.context.BlockDataContextRO; +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.server.world.block.BlockLogic; +import ru.windcorp.progressia.server.world.tile.TileLogic; + +public interface ServerBlockContextRO extends ServerWorldContextRO, BlockDataContextRO { + + public interface Logic + extends ServerWorldContextRO.Logic, BlockGenericContextRO { + + @Override + ServerBlockContextRO data(); + + @Override + default ServerBlockContextRO.Logic pushRelative(int dx, int dy, int dz) { + return push(getLocation().add_(dx, dy, dz)); + } + + @Override + default ServerBlockContextRO.Logic pushRelative(Vec3i direction) { + return push(getLocation().add_(direction)); + } + + @Override + default ServerBlockContextRO.Logic pushRelative(RelRelation direction) { + return push(getLocation().add_(direction.getRelVector())); + } + + @Override + default ServerTileStackContextRO.Logic push(RelFace face) { + return push(getLocation(), face); + } + + @Override + default ServerTileContextRO.Logic push(RelFace face, int layer) { + return push(getLocation(), face, layer); + } + + } + + @Override + ServerBlockContextRO.Logic logic(); + + @Override + default ServerBlockContextRO pushRelative(int dx, int dy, int dz) { + return push(getLocation().add_(dx, dy, dz)); + } + + @Override + default ServerBlockContextRO pushRelative(Vec3i direction) { + return push(getLocation().add_(direction)); + } + + @Override + default ServerBlockContextRO pushRelative(RelRelation direction) { + return push(getLocation().add_(direction.getRelVector())); + } + + @Override + default ServerTileStackContextRO push(RelFace face) { + return push(getLocation(), face); + } + + @Override + default ServerTileContextRO push(RelFace face, int layer) { + return push(getLocation(), face, layer); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/context/ServerContext.java b/src/main/java/ru/windcorp/progressia/server/world/context/ServerContext.java new file mode 100644 index 0000000..052718f --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/context/ServerContext.java @@ -0,0 +1,82 @@ +/* + * 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.server.world.context; + +import java.util.Random; + +import ru.windcorp.progressia.common.world.context.Context; +import ru.windcorp.progressia.server.Server; +import ru.windcorp.progressia.server.ServerState; + +/** + * A server-side {@link Context}. This context has a {@link Server} instance. + */ +public interface ServerContext extends Context { + + /** + * Gets the {@link Server} instance relevant to this context. This method + * should always be preferred to {@link ServerState#getInstance()} when + * possible. + * + * @return the server instance + */ + Server getServer(); + + /** + * Retrieves a context-appropriate source of randomness. This source should + * always be preferred to any other when possible. + * + * @return an intended {@link Random} instance + */ + Random getRandom(); + + /** + * Returns the duration of the last server tick. Server logic should assume + * that this much in-world time has passed. + * + * @return the length of the last server tick + */ + double getTickLength(); + + /** + * Adjusts the provided value according to tick length assuming the value + * scales linearly. The call {@code ctxt.adjustValue(x)} is equivalent to + * {@code ((float) ctxt.getTickLength()) * x}. + * + * @param valueForOneSecond the value to adjust, normalized to one second + * @return the value adjust to account for the actual tick length + * @see #getTickLength() + */ + default float adjustTime(float valueForOneSecond) { + return ((float) getTickLength()) * valueForOneSecond; + } + + /** + * Adjusts the provided value according to tick length assuming the value + * scales linearly. The call {@code ctxt.adjustValue(x)} is equivalent to + * {@code ctxt.getTickLength() * x}. + * + * @param valueForOneSecond the value to adjust, normalized to one second + * @return the value adjust to account for the actual tick length + * @see #getTickLength() + */ + default double adjustTime(double valueForOneSecond) { + return getTickLength() * valueForOneSecond; + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/context/ServerContexts.java b/src/main/java/ru/windcorp/progressia/server/world/context/ServerContexts.java new file mode 100644 index 0000000..b0cf996 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/context/ServerContexts.java @@ -0,0 +1,141 @@ +/* + * 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.server.world.context; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.util.Vectors; +import ru.windcorp.progressia.common.world.Coordinates; +import ru.windcorp.progressia.common.world.generic.ChunkGenericRO; +import ru.windcorp.progressia.common.world.generic.TileGenericReferenceRO; +import ru.windcorp.progressia.common.world.rels.AbsFace; +import ru.windcorp.progressia.common.world.rels.RelFace; + +public class ServerContexts { + + /* + * RW + */ + + public static ServerBlockContext pushAbs(ServerWorldContext context, Vec3i blockInWorld) { + Vec3i contextLocation = Vectors.grab3i(); + context.toContext(blockInWorld, contextLocation); + ServerBlockContext blockContext = context.push(contextLocation); + Vectors.release(contextLocation); + return blockContext; + } + + public static ServerBlockContext pushAbs(ServerWorldContext context, ChunkGenericRO chunk, Vec3i blockInChunk) { + Vec3i contextLocation = Vectors.grab3i(); + Coordinates.getInWorld(chunk.getPosition(), blockInChunk, contextLocation); + context.toContext(contextLocation, contextLocation); + ServerBlockContext blockContext = context.push(contextLocation); + Vectors.release(contextLocation); + return blockContext; + } + + public static ServerTileStackContext pushAbs(ServerWorldContext context, Vec3i blockInWorld, AbsFace face) { + Vec3i contextLocation = Vectors.grab3i(); + context.toContext(blockInWorld, contextLocation); + RelFace contextFace = context.toContext(face); + ServerTileStackContext tileStackContext = context.push(contextLocation, contextFace); + Vectors.release(contextLocation); + return tileStackContext; + } + + public static ServerTileStackContext pushAbs(ServerWorldContext context, ChunkGenericRO chunk, Vec3i blockInChunk, AbsFace face) { + Vec3i contextLocation = Vectors.grab3i(); + Coordinates.getInWorld(chunk.getPosition(), blockInChunk, contextLocation); + context.toContext(contextLocation, contextLocation); + RelFace contextFace = context.toContext(face); + ServerTileStackContext tileStackContext = context.push(contextLocation, contextFace); + Vectors.release(contextLocation); + return tileStackContext; + } + + public static ServerTileStackContext pushAbs(ServerBlockContext context, AbsFace face) { + return context.push(context.toContext(face)); + } + + public static ServerTileContext pushAbs(ServerWorldContext context, AbsFace up, TileGenericReferenceRO ref) { + Vec3i contextLocation = Vectors.grab3i(); + ref.getStack().getBlockInWorld(contextLocation); + context.toContext(contextLocation, contextLocation); + RelFace contextFace = context.toContext(ref.getStack().getFace().resolve(up)); + ServerTileContext tileContext = context.push(contextLocation, contextFace, ref.getIndex()); + Vectors.release(contextLocation); + return tileContext; + } + + /* + * RO + */ + + public static ServerBlockContextRO pushAbs(ServerWorldContextRO context, Vec3i blockInWorld) { + Vec3i contextLocation = Vectors.grab3i(); + context.toContext(blockInWorld, contextLocation); + ServerBlockContextRO blockContextRO = context.push(contextLocation); + Vectors.release(contextLocation); + return blockContextRO; + } + + public static ServerBlockContextRO pushAbs(ServerWorldContextRO context, ChunkGenericRO chunk, Vec3i blockInChunk) { + Vec3i contextLocation = Vectors.grab3i(); + Coordinates.getInWorld(chunk.getPosition(), blockInChunk, contextLocation); + context.toContext(contextLocation, contextLocation); + ServerBlockContextRO blockContextRO = context.push(contextLocation); + Vectors.release(contextLocation); + return blockContextRO; + } + + public static ServerTileStackContextRO pushAbs(ServerWorldContextRO context, Vec3i blockInWorld, AbsFace face) { + Vec3i contextLocation = Vectors.grab3i(); + context.toContext(blockInWorld, contextLocation); + RelFace contextFace = context.toContext(face); + ServerTileStackContextRO tileStackContextRO = context.push(contextLocation, contextFace); + Vectors.release(contextLocation); + return tileStackContextRO; + } + + public static ServerTileStackContextRO pushAbs(ServerWorldContextRO context, ChunkGenericRO chunk, Vec3i blockInChunk, AbsFace face) { + Vec3i contextLocation = Vectors.grab3i(); + Coordinates.getInWorld(chunk.getPosition(), blockInChunk, contextLocation); + context.toContext(contextLocation, contextLocation); + RelFace contextFace = context.toContext(face); + ServerTileStackContextRO tileStackContextRO = context.push(contextLocation, contextFace); + Vectors.release(contextLocation); + return tileStackContextRO; + } + + public static ServerTileStackContextRO pushAbs(ServerBlockContextRO context, AbsFace face) { + return context.push(context.toContext(face)); + } + + public static ServerTileContextRO pushAbs(ServerWorldContextRO context, AbsFace up, TileGenericReferenceRO ref) { + Vec3i contextLocation = Vectors.grab3i(); + ref.getStack().getBlockInWorld(contextLocation); + context.toContext(contextLocation, contextLocation); + RelFace contextFace = context.toContext(ref.getStack().getFace().resolve(up)); + ServerTileContextRO tileContextRO = context.push(contextLocation, contextFace, ref.getIndex()); + Vectors.release(contextLocation); + return tileContextRO; + } + + private ServerContexts() { + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/context/ServerTileContext.java b/src/main/java/ru/windcorp/progressia/server/world/context/ServerTileContext.java new file mode 100644 index 0000000..6850f43 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/context/ServerTileContext.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.server.world.context; + +import ru.windcorp.progressia.common.world.context.TileDataContext; + +public interface ServerTileContext extends TileDataContext, ServerTileStackContext, ServerTileContextRO { + + public interface Logic extends ServerTileContextRO.Logic, ServerTileStackContext.Logic { + + @Override + ServerTileContext data(); + + @Override + default ServerTileContext.Logic pushCloser() { + return push(getLocation(), getFace(), getLayer() - 1); + } + + @Override + default ServerTileContext.Logic pushFarther() { + return push(getLocation(), getFace(), getLayer() + 1); + } + + } + + @Override + ServerTileContext.Logic logic(); + + @Override + default ServerTileContext pushCloser() { + return push(getLocation(), getFace(), getLayer() - 1); + } + + @Override + default ServerTileContext pushFarther() { + return push(getLocation(), getFace(), getLayer() + 1); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/context/ServerTileContextRO.java b/src/main/java/ru/windcorp/progressia/server/world/context/ServerTileContextRO.java new file mode 100644 index 0000000..18382bd --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/context/ServerTileContextRO.java @@ -0,0 +1,59 @@ +/* + * 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.server.world.context; + +import ru.windcorp.progressia.common.world.context.TileDataContextRO; +import ru.windcorp.progressia.common.world.entity.EntityData; +import ru.windcorp.progressia.common.world.generic.context.TileGenericContextRO; +import ru.windcorp.progressia.server.world.block.BlockLogic; +import ru.windcorp.progressia.server.world.tile.TileLogic; + +public interface ServerTileContextRO extends ServerTileStackContextRO, TileDataContextRO { + + public interface Logic + extends ServerTileStackContextRO.Logic, TileGenericContextRO { + + @Override + ServerTileContextRO data(); + + @Override + default ServerTileContextRO.Logic pushCloser() { + return push(getLocation(), getFace(), getLayer() - 1); + } + + @Override + default ServerTileContextRO.Logic pushFarther() { + return push(getLocation(), getFace(), getLayer() + 1); + } + + } + + @Override + ServerTileContextRO.Logic logic(); + + @Override + default ServerTileContextRO pushCloser() { + return push(getLocation(), getFace(), getLayer() - 1); + } + + @Override + default ServerTileContextRO pushFarther() { + return push(getLocation(), getFace(), getLayer() + 1); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/context/ServerTileStackContext.java b/src/main/java/ru/windcorp/progressia/server/world/context/ServerTileStackContext.java new file mode 100644 index 0000000..7454f26 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/context/ServerTileStackContext.java @@ -0,0 +1,64 @@ +/* + * 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.server.world.context; + +import ru.windcorp.progressia.common.world.context.TileStackDataContext; + +public interface ServerTileStackContext extends TileStackDataContext, ServerBlockContext, ServerTileStackContextRO { + + public interface Logic extends ServerTileStackContextRO.Logic, ServerBlockContext.Logic { + + @Override + ServerTileStackContext data(); + + @Override + default ServerTileContext.Logic push(int layer) { + return push(getLocation(), getFace(), layer); + } + + @Override + default ServerTileStackContext.Logic pushCounter() { + return push(getFace().getCounter()); + } + + @Override + default ServerTileStackContext.Logic pushOpposite() { + return push(getLocation().add_(getFace().getRelVector()), getFace().getCounter()); + } + + } + + @Override + ServerTileStackContext.Logic logic(); + + @Override + default ServerTileContext push(int layer) { + return push(getLocation(), getFace(), layer); + } + + @Override + default ServerTileStackContext pushCounter() { + return push(getFace().getCounter()); + } + + @Override + default ServerTileStackContext pushOpposite() { + return push(getLocation().add_(getFace().getRelVector()), getFace().getCounter()); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/context/ServerTileStackContextRO.java b/src/main/java/ru/windcorp/progressia/server/world/context/ServerTileStackContextRO.java new file mode 100644 index 0000000..ef2d476 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/context/ServerTileStackContextRO.java @@ -0,0 +1,69 @@ +/* + * 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.server.world.context; + +import ru.windcorp.progressia.common.world.context.TileStackDataContextRO; +import ru.windcorp.progressia.common.world.entity.EntityData; +import ru.windcorp.progressia.common.world.generic.context.TileStackGenericContextRO; +import ru.windcorp.progressia.server.world.block.BlockLogic; +import ru.windcorp.progressia.server.world.tile.TileLogic; + +public interface ServerTileStackContextRO extends ServerBlockContextRO, TileStackDataContextRO { + + public interface Logic + extends ServerBlockContextRO.Logic, TileStackGenericContextRO { + + @Override + ServerTileStackContextRO data(); + + @Override + default ServerTileContextRO.Logic push(int layer) { + return push(getLocation(), getFace(), layer); + } + + @Override + default ServerTileStackContextRO.Logic pushCounter() { + return push(getFace().getCounter()); + } + + @Override + default ServerTileStackContextRO.Logic pushOpposite() { + return push(getLocation().add_(getFace().getRelVector()), getFace().getCounter()); + } + + } + + @Override + ServerTileStackContextRO.Logic logic(); + + @Override + default ServerTileContextRO push(int layer) { + return push(getLocation(), getFace(), layer); + } + + @Override + default ServerTileStackContextRO pushCounter() { + return push(getFace().getCounter()); + } + + @Override + default ServerTileStackContextRO pushOpposite() { + return push(getLocation().add_(getFace().getRelVector()), getFace().getCounter()); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/context/ServerWorldContext.java b/src/main/java/ru/windcorp/progressia/server/world/context/ServerWorldContext.java new file mode 100644 index 0000000..3de8038 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/context/ServerWorldContext.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.server.world.context; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.context.WorldDataContext; +import ru.windcorp.progressia.common.world.rels.RelFace; + +public interface ServerWorldContext extends WorldDataContext, ServerWorldContextRO { + + public interface Logic extends ServerWorldContextRO.Logic { + + @Override + ServerWorldContext data(); + + @Override + ServerBlockContext.Logic push(Vec3i location); + + @Override + ServerTileStackContext.Logic push(Vec3i location, RelFace face); + + @Override + ServerTileContext.Logic push(Vec3i location, RelFace face, int layer); + + } + + @Override + ServerWorldContext.Logic logic(); + + @Override + ServerBlockContext push(Vec3i location); + + @Override + ServerTileStackContext push(Vec3i location, RelFace face); + + @Override + ServerTileContext push(Vec3i location, RelFace face, int layer); + +} \ No newline at end of file diff --git a/src/main/java/ru/windcorp/progressia/server/world/context/ServerWorldContextRO.java b/src/main/java/ru/windcorp/progressia/server/world/context/ServerWorldContextRO.java new file mode 100644 index 0000000..ff849c1 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/context/ServerWorldContextRO.java @@ -0,0 +1,53 @@ +package ru.windcorp.progressia.server.world.context; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.context.WorldDataContextRO; +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.server.world.block.BlockLogic; +import ru.windcorp.progressia.server.world.tile.TileLogic; + +public interface ServerWorldContextRO extends WorldDataContextRO, ServerContext { + + public interface Logic extends WorldGenericContextRO, ServerContext { + + /** + * Acquires the data view of this context. Use this method to + * conveniently access data content. The returned object is linked to + * this context and operates on the same data. + * + * @return a view of this context that returns data objects + */ + ServerWorldContextRO data(); + + @Override + ServerBlockContextRO.Logic push(Vec3i location); + + @Override + ServerTileStackContextRO.Logic push(Vec3i location, RelFace face); + + @Override + ServerTileContextRO.Logic push(Vec3i location, RelFace face, int layer); + + } + + /** + * Acquires the logic view of this context. Use this method to conveniently + * access logic content. The returned object is linked to this context and + * operates on the same data. + * + * @return a view of this context that returns appropriate logic objects + */ + ServerWorldContextRO.Logic logic(); + + @Override + ServerBlockContextRO push(Vec3i location); + + @Override + ServerTileStackContextRO push(Vec3i location, RelFace face); + + @Override + ServerTileContextRO push(Vec3i location, RelFace face, int layer); + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/context/impl/DefaultServerContext.java b/src/main/java/ru/windcorp/progressia/server/world/context/impl/DefaultServerContext.java new file mode 100644 index 0000000..afeadea --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/context/impl/DefaultServerContext.java @@ -0,0 +1,142 @@ +/* + * 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.server.world.context.impl; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.block.BlockData; +import ru.windcorp.progressia.common.world.context.Context; +import ru.windcorp.progressia.common.world.entity.EntityData; +import ru.windcorp.progressia.common.world.generic.context.AbstractContextRO; +import ru.windcorp.progressia.common.world.rels.RelFace; +import ru.windcorp.progressia.common.world.tile.TileData; +import ru.windcorp.progressia.server.world.WorldLogic; +import ru.windcorp.progressia.server.world.WorldLogicRO; +import ru.windcorp.progressia.server.world.context.*; + +/** + * An implementation of the entire {@link ServerContext} tree. The objects of + * this class are designed to be highly reusable, including reusability in + * {@linkplain Context#subcontexting subcontexting}. Use this implementation + * when a {@link WorldLogic} (or a {@link WorldLogicRO}) instance requires a + * context wrapper. + *

    + * Use other unutilized instances of {@code ReusableServerContext} or + * {@link #empty()} static method to acquire a usable instance. + *

    + * {@code ReusableServerContext} asserts that is it {@linkplain #isReal() real} + * and {@linkplain #isImmediate() immediate}. It creates and provides an + * independent randomness source. The tick length is consulted with the server. + * Use wrappers to alter these properties. + *

    + * This class defines the outward-facing safe interface of the actual + * implementation located in {@link DefaultServerContextImpl}. The reasoning + * for creating a subclass is to allow a single instance to implement both + * {@linkplain DefaultServerContextBuilders builder interfaces} and the context + * interface without causing confusion around object states. + * + * @author javapony + */ +public abstract class DefaultServerContext extends AbstractContextRO + implements ServerTileContext { + + /** + * An RSC can conform to a variety of different {@link Context} interfaces. + * Each compliance mode is identified by a Role. + */ + public enum Role { + /** + * This object has not been configured yet. + */ + NONE, + /** + * This object conforms to {@link ServerWorldContext} or + * {@link ServerWorldContextRO}. + */ + WORLD, + /** + * This object conforms to {@link ServerBlockContext} or + * {@link ServerBlockContextRO}. + */ + LOCATION, + /** + * This object conforms to {@link ServerTileStackContext} or + * {@link ServerTileStackContextRO}. + */ + TILE_STACK, + /** + * This object conforms to {@link ServerTileContext} or + * {@link ServerTileContextRO}. + */ + TILE + } + + /** + * Do not extend ReusableServerContext directly. Use + * {@link DefaultServerContextImpl} if this is truly necessary. + */ + DefaultServerContext() { + // do nothing + } + + /** + * Resets this object to its uninitialized state and returns a builder to + * reinitialize it. + * + * @return a {@link DefaultServerContextBuilders.Empty} instance that may + * be used to reinitialize this object + * @throws IllegalStateException if active subcontexting is detected + */ + public abstract DefaultServerContextBuilders.Empty reuse() throws IllegalStateException; + + /** + * Returns the {@link Role} currently assumed by this object. + * + * @return the role + */ + public abstract Role getRole(); + + /** + * Instantiates a new {@link DefaultServerContext} using an appropriate + * implementation. + * + * @return a {@link DefaultServerContextBuilders.Empty} instance that can + * be used to initialize this object + */ + public static DefaultServerContextBuilders.Empty empty() { + return new DefaultServerContextImpl(); + } + + @Override + public DefaultServerContext push(Vec3i location) { + super.push(location); + return this; + } + + @Override + public DefaultServerContext push(Vec3i location, RelFace face) { + super.push(location, face); + return this; + } + + @Override + public DefaultServerContext push(Vec3i location, RelFace face, int layer) { + super.push(location, face, layer); + return this; + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/context/impl/DefaultServerContextBuilders.java b/src/main/java/ru/windcorp/progressia/server/world/context/impl/DefaultServerContextBuilders.java new file mode 100644 index 0000000..cb182ce --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/context/impl/DefaultServerContextBuilders.java @@ -0,0 +1,87 @@ +/* + * 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.server.world.context.impl; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.WorldData; +import ru.windcorp.progressia.common.world.generic.TileGenericReferenceRO; +import ru.windcorp.progressia.common.world.generic.TileGenericStackRO; +import ru.windcorp.progressia.common.world.rels.BlockFace; +import ru.windcorp.progressia.common.world.rels.RelFace; +import ru.windcorp.progressia.server.Server; + +public interface DefaultServerContextBuilders { + + DefaultServerContext build(); + + public interface Empty /* does not extend DSCB */ { + + WithWorld in(Server server, WorldData world); + + default WithWorld inRealWorldOf(Server server) { + return in(server, server.getWorld().getData()); + } + + } + + public interface WithWorld extends DefaultServerContextBuilders { + + WithLocation at(Vec3i location); + + default DefaultServerContext at(TileGenericReferenceRO reference) { + if (!reference.isValid()) { + throw new IllegalArgumentException("Reference " + reference + " is invalid"); + } + + TileGenericStackRO stack = reference.getStack(); + return at(stack.getBlockInWorld(null)).on(stack.getFace()).index(reference.getIndex()); + } + + } + + public interface WithLocation extends DefaultServerContextBuilders { + + WithTileStack on(RelFace side); + WithTileStack on(BlockFace side); + + default DefaultServerContext on(TileGenericReferenceRO reference) { + if (!reference.isValid()) { + throw new IllegalArgumentException("Reference " + reference + " is invalid"); + } + + TileGenericStackRO stack = reference.getStack(); + return on(stack.getFace()).index(reference.getIndex()); + } + + } + + public interface WithTileStack extends DefaultServerContextBuilders { + + DefaultServerContext index(int index); + + default DefaultServerContext index(TileGenericReferenceRO reference) { + if (!reference.isValid()) { + throw new IllegalArgumentException("Reference " + reference + " is invalid"); + } + + return index(reference.getIndex()); + } + + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/context/impl/DefaultServerContextImpl.java b/src/main/java/ru/windcorp/progressia/server/world/context/impl/DefaultServerContextImpl.java new file mode 100644 index 0000000..b4dfedc --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/context/impl/DefaultServerContextImpl.java @@ -0,0 +1,507 @@ +/* + * 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.server.world.context.impl; + +import java.util.Collection; +import java.util.Random; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.state.StateChange; +import ru.windcorp.progressia.common.state.StatefulObject; +import ru.windcorp.progressia.common.world.GravityModel; +import ru.windcorp.progressia.common.world.TileDataStack; +import ru.windcorp.progressia.common.world.WorldData; +import ru.windcorp.progressia.common.world.block.BlockData; +import ru.windcorp.progressia.common.world.entity.EntityData; +import ru.windcorp.progressia.common.world.generic.EntityGeneric; +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.server.Server; + +class DefaultServerContextImpl extends DefaultServerContext + implements DefaultServerContextBuilders.Empty, DefaultServerContextBuilders.WithWorld, + DefaultServerContextBuilders.WithLocation, DefaultServerContextBuilders.WithTileStack { + + /* + * STATE MANAGEMENT & UTIL + */ + + public DefaultServerContextImpl() { + reuse(); + } + + /** + * The relevant {@link Server} instance. If this is {@code null}, the role + * is {@link Role#NONE}. + */ + protected Server server; + + /** + * The relevant {@link WorldData} instance. If this is {@code null}, the + * role is {@link Role#NONE}. + */ + protected WorldData world; + + /** + * The {@link Random} instance exposed with {@link #getRandom()}. + */ + protected final Random random = new Random(); + + /** + * Determines whether this object currently acts as a builder or a context. + */ + protected boolean isBuilder; + + /** + * Counts the instances of subcontexting that are currently active. This + * value increases by 1 when subcontexting begins and decreases by 1 when it + * ends. This is always 0 when the object is a builder. + */ + protected int subcontextDepth = 0; + + /** + * The Logic view returned by {@link #logic()}. + */ + protected final DefaultServerContextImpl.Logic logic = new DefaultServerContextLogic(this); + + /** + * Returns the Role currently assumed by this object. + * + * @return the role + */ + @Override + public Role getRole() { + if (server == null) + return Role.NONE; + if (frame == null) + return Role.WORLD; + if (frame.face == null) + return Role.LOCATION; + if (frame.layer == -1) + return Role.TILE_STACK; + return Role.TILE; + } + + /** + * Throws an {@link IllegalStateException} iff this object does not conform + * to the specified role. The object must not be a builder to pass the + * check. + * + * @param role the required role + * @return {@code true} (for convenience with {@code assert}) + * @throws IllegalStateException when the check fails + */ + public boolean requireContextRole(Role role) throws IllegalStateException { + + boolean ok = !isBuilder && getRole().compareTo(role) >= 0; + if (!ok) { + complainAboutIllegalState(role, false); + } + return true; + + } + + /** + * Throws an {@link IllegalStateException} iff this object does not conform + * to the specified role. The object must be a builder to pass the check. If + * {@code role} is {@code null}, any role except {@link Role#NONE} passes. + * + * @param role the required role or {@code null}, see above + * @return {@code true} (for convenience with {@code assert}) + * @throws IllegalStateException when the check fails + */ + public boolean requireBuilderRole(Role role) { + + boolean ok = isBuilder && role == null ? (getRole() != Role.NONE) : (getRole() == role); + if (!ok) { + complainAboutIllegalState(role, true); + } + return true; + + } + + private void complainAboutIllegalState(Role role, boolean builder) { + throw new IllegalStateException( + "Required " + (builder ? "builder for" : "context") + " " + role + + ", but I am currently " + this + ); + } + + @Override + public String toString() { + String result; + + switch (getRole()) { + case TILE: + result = String.format( + "ServerTileContext [x=%+4d, y=%+4d, z=%+4d, %s, index=%d]", + frame.location.x, + frame.location.y, + frame.location.z, + frame.face, + frame.layer + ); + break; + case TILE_STACK: + result = String + .format("ServerBlockFaceContext [x=%+4d, y=%+4d, z=%+4d, %s]", frame.location.x, frame.location.y, frame.location.z, frame.face); + break; + case LOCATION: + result = String.format("ServerBlockContext [x=%+4d, y=%+4d, z=%+4d]", frame.location.x, frame.location.y, frame.location.z); + break; + case WORLD: + result = "ServerWorldContext"; + break; + default: + result = "Uninitialized DefaultServerContext"; + break; + } + + if (isBuilder) { + result = "Builder for " + result; + } + + return result; + } + + /* + * RSC INTERFACE + */ + + @Override + public Empty reuse() { + + server = null; + world = null; + + while (isSubcontexting()) { + pop(); + } + + isBuilder = true; + + return this; + + } + + /* + * BUILDER INTERFACE + */ + + @Override + public DefaultServerContext build() { + assert requireBuilderRole(null); + isBuilder = false; + return this; + } + + /* + * Empty + */ + + @Override + public WithWorld in(Server server, WorldData world) { + requireBuilderRole(Role.NONE); + this.server = server; + this.world = world; + return this; + } + + /* + * WithWorld + */ + + @Override + public WithLocation at(Vec3i location) { + requireBuilderRole(Role.WORLD); + push(location); + return this; + } + + /* + * WithLocation + */ + + @Override + public WithTileStack on(RelFace side) { + requireBuilderRole(Role.LOCATION); + frame.face = side; + return this; + } + + @Override + public WithTileStack on(BlockFace side) { + requireBuilderRole(Role.LOCATION); + frame.face = side.relativize(world.getUp(frame.location)); + return this; + } + + /* + * WithTileStack + */ + + @Override + public DefaultServerContext index(int index) { + requireBuilderRole(Role.TILE_STACK); + frame.layer = index; + return build(); + } + + /* + * LOCATION GETTERS + */ + + @Override + public Server getServer() { + assert requireContextRole(Role.WORLD); + return server; + } + + @Override + public Vec3i getLocation() { + assert requireContextRole(Role.LOCATION); + return frame.location; + } + + @Override + public RelFace getFace() { + assert requireContextRole(Role.TILE_STACK); + return frame.face; + } + + @Override + public int getLayer() { + assert requireContextRole(Role.TILE); + return frame.layer; + } + + /* + * ABSOLUTE COORDINATE CONVERSIONS + * (or lack thereof) + */ + + @Override + public Vec3i toAbsolute(Vec3i contextLocation, Vec3i output) { + if (output == null) { + output = new Vec3i(); + } + + output.set(contextLocation.x, contextLocation.y, contextLocation.z); + return output; + } + + @Override + public Vec3i toContext(Vec3i absoluteLocation, Vec3i output) { + if (output == null) { + output = new Vec3i(); + } + + output.set(absoluteLocation.x, absoluteLocation.y, absoluteLocation.z); + return output; + } + + @Override + public AbsFace toAbsolute(RelFace contextFace) { + return contextFace.resolve(AbsFace.POS_Z); + } + + @Override + public RelFace toContext(AbsFace absoluteFace) { + return absoluteFace.relativize(AbsFace.POS_Z); + } + + /* + * RO CONTEXT INTERFACE + */ + + @Override + public boolean isReal() { + assert requireContextRole(Role.WORLD); + return true; + } + + @Override + public Random getRandom() { + assert requireContextRole(Role.WORLD); + return random; + } + + @Override + public double getTickLength() { + assert requireContextRole(Role.WORLD); + return server.getTickLength(); + } + + @Override + public BlockData getBlock(Vec3i location) { + assert requireContextRole(Role.WORLD); + return world.getBlock(location); + } + + @Override + public boolean isLocationLoaded(Vec3i location) { + assert requireContextRole(Role.WORLD); + return world.isLocationLoaded(location); + } + + @Override + public TileData getTile(Vec3i location, RelFace face, int layer) { + assert requireContextRole(Role.WORLD); + return world.getTile(location, face.resolve(AbsFace.POS_Z), layer); + } + + @Override + public boolean hasTile(Vec3i location, RelFace face, int layer) { + assert requireContextRole(Role.WORLD); + return world.hasTile(location, face.resolve(AbsFace.POS_Z), layer); + } + + @Override + public TileData getTileByTag(Vec3i location, RelFace face, int tag) { + assert requireContextRole(Role.WORLD); + TileDataStack stack = world.getTilesOrNull(location, face.resolve(AbsFace.POS_Z)); + if (stack == null) + return null; + int layer = stack.getIndexByTag(tag); + if (layer == -1) + return null; + return stack.get(layer); + } + + @Override + public boolean isTagValid(Vec3i location, RelFace face, int tag) { + assert requireContextRole(Role.WORLD); + TileDataStack stack = world.getTilesOrNull(location, face.resolve(AbsFace.POS_Z)); + if (stack == null) + return false; + return stack.getIndexByTag(tag) != -1; + } + + @Override + public int getTag() { + assert requireContextRole(Role.TILE); + TileDataStack stack = world.getTilesOrNull(frame.location, frame.face.resolve(AbsFace.POS_Z)); + if (stack == null) + return -1; + return stack.getTagByIndex(frame.layer); + } + + @Override + public int getTileCount(Vec3i location, RelFace face) { + assert requireContextRole(Role.TILE_STACK); + TileDataStack stack = world.getTilesOrNull(location, face.resolve(AbsFace.POS_Z)); + if (stack == null) + return 0; + return stack.size(); + } + + @Override + public Collection getEntities() { + assert requireContextRole(Role.WORLD); + return world.getEntities(); + } + + @Override + public EntityData getEntity(long entityId) { + assert requireContextRole(Role.WORLD); + return world.getEntity(entityId); + } + + @Override + public GravityModel getGravityModel() { + assert requireContextRole(Role.WORLD); + return world.getGravityModel(); + } + + @Override + public float getTime() { + assert requireContextRole(Role.WORLD); + return world.getTime(); + } + + /* + * RW CONTEXT INTERFACE + */ + + @Override + public boolean isImmediate() { + assert requireContextRole(Role.WORLD); + return true; + } + + @Override + public void setBlock(Vec3i blockInWorld, BlockData block) { + assert requireContextRole(Role.WORLD); + world.setBlock(blockInWorld, block, true); + } + + @Override + public void addTile(Vec3i location, RelFace face, TileData tile) { + assert requireContextRole(Role.WORLD); + world.getTiles(location, face.resolve(AbsFace.POS_Z)).addFarthest(tile); + } + + @Override + public void removeTile(Vec3i location, RelFace face, int tag) { + assert requireContextRole(Role.WORLD); + TileDataStack stack = world.getTilesOrNull(location, face.resolve(AbsFace.POS_Z)); + if (stack == null) + return; + int layer = stack.getIndexByTag(tag); + if (layer == -1) + return; + stack.remove(layer); + } + + @Override + public void addEntity(EntityData entity) { + assert requireContextRole(Role.WORLD); + world.addEntity(entity); + } + + @Override + public void removeEntity(long entityId) { + assert requireContextRole(Role.WORLD); + world.removeEntity(entityId); + } + + @Override + public void changeEntity(SE entity, StateChange change) { + assert requireContextRole(Role.WORLD); + world.changeEntity(entity, change); + } + + @Override + public void advanceTime(float change) { + assert requireContextRole(Role.WORLD); + world.advanceTime(change); + } + + /* + * ServerWorldContext.Logic STUFF + */ + + @Override + public Logic logic() { + assert requireContextRole(Role.WORLD); + return logic; + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/context/impl/DefaultServerContextLogic.java b/src/main/java/ru/windcorp/progressia/server/world/context/impl/DefaultServerContextLogic.java new file mode 100644 index 0000000..d6bb661 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/context/impl/DefaultServerContextLogic.java @@ -0,0 +1,185 @@ +/* + * 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.server.world.context.impl; + +import java.util.Collection; +import java.util.Random; + +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.rels.AbsFace; +import ru.windcorp.progressia.common.world.rels.RelFace; +import ru.windcorp.progressia.common.world.tile.TileData; +import ru.windcorp.progressia.server.Server; +import ru.windcorp.progressia.server.world.block.BlockLogic; +import ru.windcorp.progressia.server.world.block.BlockLogicRegistry; +import ru.windcorp.progressia.server.world.context.ServerTileContext; +import ru.windcorp.progressia.server.world.tile.TileLogic; +import ru.windcorp.progressia.server.world.tile.TileLogicRegistry; + +public class DefaultServerContextLogic implements ServerTileContext.Logic { + + private final ServerTileContext parent; + + public DefaultServerContextLogic(ServerTileContext parent) { + this.parent = parent; + } + + @Override + public ServerTileContext data() { + return parent; + } + + @Override + public Server getServer() { + return parent.getServer(); + } + + @Override + public Random getRandom() { + return parent.getRandom(); + } + + @Override + public BlockLogic getBlock(Vec3i location) { + BlockData data = parent.getBlock(location); + return data == null ? null : BlockLogicRegistry.getInstance().get(data.getId()); + } + + @Override + public boolean isLocationLoaded(Vec3i location) { + return parent.isLocationLoaded(location); + } + + @Override + public ServerTileContext.Logic push(Vec3i location) { + parent.push(location); + return this; + } + + @Override + public ServerTileContext.Logic push(Vec3i location, RelFace face) { + parent.push(location, face); + return this; + } + + @Override + public ServerTileContext.Logic push(Vec3i location, RelFace face, int layer) { + parent.push(location, face, layer); + return this; + } + + @Override + public Vec3i toAbsolute(Vec3i contextLocation, Vec3i output) { + return parent.toAbsolute(contextLocation, output); + } + + @Override + public Vec3i toContext(Vec3i absoluteLocation, Vec3i output) { + return parent.toContext(absoluteLocation, output); + } + + @Override + public AbsFace toAbsolute(RelFace contextFace) { + return parent.toAbsolute(contextFace); + } + + @Override + public RelFace toContext(AbsFace absoluteFace) { + return parent.toContext(absoluteFace); + } + + @Override + public double getTickLength() { + return parent.getTickLength(); + } + + @Override + public TileLogic getTile(Vec3i location, RelFace face, int layer) { + TileData data = parent.getTile(location, face, layer); + return data == null ? null : TileLogicRegistry.getInstance().get(data.getId()); + } + + @Override + public TileLogic getTileByTag(Vec3i location, RelFace face, int tag) { + TileData data = parent.getTileByTag(location, face, tag); + return data == null ? null : TileLogicRegistry.getInstance().get(data.getId()); + } + + @Override + public Vec3i getLocation() { + return parent.getLocation(); + } + + @Override + public boolean hasTile(Vec3i location, RelFace face, int layer) { + return parent.hasTile(location, face, layer); + } + + @Override + public boolean isTagValid(Vec3i location, RelFace face, int tag) { + return parent.isTagValid(location, face, tag); + } + + @Override + public boolean isReal() { + return parent.isReal(); + } + + @Override + public int getTileCount(Vec3i location, RelFace face) { + return parent.getTileCount(location, face); + } + + @Override + public Collection getEntities() { + return parent.getEntities(); + } + + @Override + public EntityData getEntity(long entityId) { + return parent.getEntity(entityId); + } + + @Override + public void pop() { + parent.pop(); + } + + @Override + public RelFace getFace() { + return parent.getFace(); + } + + @Override + public int getLayer() { + return parent.getLayer(); + } + + @Override + public int getTag() { + return parent.getTag(); + } + + @Override + public String toString() { + return parent + ".Logic"; + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/context/impl/FilterServerContext.java b/src/main/java/ru/windcorp/progressia/server/world/context/impl/FilterServerContext.java new file mode 100644 index 0000000..80c5073 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/context/impl/FilterServerContext.java @@ -0,0 +1,243 @@ +/* + * 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.server.world.context.impl; + +import java.util.Collection; +import java.util.Random; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.state.StateChange; +import ru.windcorp.progressia.common.state.StatefulObject; +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.EntityGeneric; +import ru.windcorp.progressia.common.world.rels.AbsFace; +import ru.windcorp.progressia.common.world.rels.RelFace; +import ru.windcorp.progressia.common.world.tile.TileData; +import ru.windcorp.progressia.server.Server; +import ru.windcorp.progressia.server.world.context.ServerBlockContext; +import ru.windcorp.progressia.server.world.context.ServerTileContext; +import ru.windcorp.progressia.server.world.context.ServerTileStackContext; + +/** + * This is an implementation of the server context tree that delegates all calls + * to a provided instance of {@link ServerTileContext}. + */ +public abstract class FilterServerContext implements ServerTileContext { + + protected final ServerTileContext parent; + protected final DefaultServerContextLogic logic = new DefaultServerContextLogic(this); + + public FilterServerContext(ServerTileContext parent) { + this.parent = parent; + } + + public ServerTileContext getParent() { + return parent; + } + + @Override + public String toString() { + return getClass().getSimpleName() + " [" + parent + "]"; + } + + @Override + public int getLayer() { + return parent.getLayer(); + } + + @Override + public int getTag() { + return parent.getTag(); + } + + @Override + public RelFace getFace() { + return parent.getFace(); + } + + @Override + public Vec3i getLocation() { + return parent.getLocation(); + } + + @Override + public boolean isReal() { + return parent.isReal(); + } + + @Override + public void pop() { + parent.pop(); + } + + @Override + public boolean isImmediate() { + return parent.isImmediate(); + } + + @Override + public void setBlock(Vec3i location, BlockData block) { + parent.setBlock(location, block); + } + + @Override + public void addTile(Vec3i location, RelFace face, TileData tile) { + parent.addTile(location, face, tile); + } + + @Override + public void removeTile(Vec3i location, RelFace face, int tag) { + parent.removeTile(location, face, tag); + } + + @Override + public void addEntity(EntityData entity) { + parent.addEntity(entity); + } + + @Override + public void removeEntity(long entityId) { + parent.removeEntity(entityId); + } + + @Override + public void changeEntity(SE entity, StateChange change) { + parent.changeEntity(entity, change); + } + + @Override + public void advanceTime(float change) { + parent.advanceTime(change); + } + + @Override + public float getTime() { + return parent.getTime(); + } + + @Override + public GravityModel getGravityModel() { + return parent.getGravityModel(); + } + + @Override + public BlockData getBlock(Vec3i location) { + return parent.getBlock(location); + } + + @Override + public boolean isLocationLoaded(Vec3i location) { + return parent.isLocationLoaded(location); + } + + @Override + public TileData getTile(Vec3i location, RelFace face, int layer) { + return parent.getTile(location, face, layer); + } + + @Override + public TileData getTileByTag(Vec3i location, RelFace face, int tag) { + return parent.getTileByTag(location, face, tag); + } + + @Override + public boolean hasTile(Vec3i location, RelFace face, int layer) { + return parent.hasTile(location, face, layer); + } + + @Override + public boolean isTagValid(Vec3i location, RelFace face, int tag) { + return parent.isTagValid(location, face, tag); + } + + @Override + public int getTileCount(Vec3i location, RelFace face) { + return parent.getTileCount(location, face); + } + + @Override + public Collection getEntities() { + return parent.getEntities(); + } + + @Override + public EntityData getEntity(long entityId) { + return parent.getEntity(entityId); + } + + @Override + public ServerBlockContext push(Vec3i location) { + parent.push(location); + return this; + } + + @Override + public ServerTileStackContext push(Vec3i location, RelFace face) { + parent.push(location, face); + return this; + } + + @Override + public ServerTileContext push(Vec3i location, RelFace face, int layer) { + parent.push(location, face, layer); + return this; + } + + @Override + public Vec3i toAbsolute(Vec3i contextLocation, Vec3i output) { + return parent.toAbsolute(contextLocation, output); + } + + @Override + public Vec3i toContext(Vec3i absoluteLocation, Vec3i output) { + return parent.toContext(absoluteLocation, output); + } + + @Override + public AbsFace toAbsolute(RelFace contextFace) { + return parent.toAbsolute(contextFace); + } + + @Override + public RelFace toContext(AbsFace absoluteFace) { + return parent.toContext(absoluteFace); + } + + @Override + public Server getServer() { + return parent.getServer(); + } + + @Override + public Random getRandom() { + return parent.getRandom(); + } + + @Override + public double getTickLength() { + return parent.getTickLength(); + } + + @Override + public ServerTileContext.Logic logic() { + return logic; + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/context/impl/ReportingServerContext.java b/src/main/java/ru/windcorp/progressia/server/world/context/impl/ReportingServerContext.java new file mode 100644 index 0000000..57ff66c --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/context/impl/ReportingServerContext.java @@ -0,0 +1,145 @@ +/* + * 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.server.world.context.impl; + +import glm.vec._3.i.Vec3i; +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.EntityGeneric; +import ru.windcorp.progressia.common.world.rels.RelFace; +import ru.windcorp.progressia.common.world.tile.TileData; +import ru.windcorp.progressia.server.world.context.ServerTileContext; + +public class ReportingServerContext extends FilterServerContext { + + public static interface ChangeListener { + + void onBlockSet(Vec3i location, BlockData block); + + void onTileAdded(Vec3i location, RelFace face, TileData tile); + + void onTileRemoved(Vec3i location, RelFace face, int tag); + + void onEntityAdded(EntityData entity); + + void onEntityRemoved(long entityId); + + void onEntityChanged(SE entity, StateChange change); + + void onTimeChanged(float change); + + } + + private ChangeListener listener = null; + private boolean passToParent = true; + + /** + * Creates a new {@link ReportingServerContext} instance that delegates + * method calls to the specified parent context. Write methods are always + * passed, disable with {@link #setPassToParent(boolean)}. No listener is + * set, set a listener with {@link #withListener(ChangeListener)}. + * + * @param parent the parent context + */ + public ReportingServerContext(ServerTileContext parent) { + super(parent); + } + + public ReportingServerContext withListener(ChangeListener listener) { + this.listener = listener; + return this; + } + + public ReportingServerContext setPassToParent(boolean pass) { + this.passToParent = pass; + return this; + } + + @Override + public void setBlock(Vec3i location, BlockData block) { + if (passToParent) { + super.setBlock(location, block); + } + if (listener != null) { + listener.onBlockSet(location, block); + } + } + + @Override + public void addTile(Vec3i location, RelFace face, TileData tile) { + if (passToParent) { + super.addTile(location, face, tile); + } + if (listener != null) { + listener.onTileAdded(location, face, tile); + } + } + + @Override + public void removeTile(Vec3i location, RelFace face, int tag) { + if (passToParent) { + super.removeTile(location, face, tag); + } + if (listener != null) { + listener.onTileRemoved(location, face, tag); + } + } + + @Override + public void addEntity(EntityData entity) { + if (passToParent) { + super.addEntity(entity); + } + if (listener != null) { + listener.onEntityAdded(entity); + } + } + + @Override + public void removeEntity(long entityId) { + if (passToParent) { + super.removeEntity(entityId); + } + if (listener != null) { + listener.onEntityRemoved(entityId); + } + } + + @Override + public void changeEntity(SE entity, StateChange change) { + if (passToParent) { + super.changeEntity(entity, change); + } + if (listener != null) { + listener.onEntityChanged(entity, change); + } + } + + @Override + public void advanceTime(float change) { + if (passToParent) { + super.advanceTime(change); + } + if (listener != null) { + listener.onTimeChanged(change); + } + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/context/impl/RotatingServerContext.java b/src/main/java/ru/windcorp/progressia/server/world/context/impl/RotatingServerContext.java new file mode 100644 index 0000000..a73d2b0 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/context/impl/RotatingServerContext.java @@ -0,0 +1,59 @@ +/* + * 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.server.world.context.impl; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.generic.GenericChunks; +import ru.windcorp.progressia.common.world.rels.AbsFace; +import ru.windcorp.progressia.common.world.rels.RelFace; +import ru.windcorp.progressia.server.world.context.ServerTileContext; + +public class RotatingServerContext extends TransformingServerContext { + + private final AbsFace up; + + public RotatingServerContext(ServerTileContext parent, AbsFace up) { + super(parent); + this.up = up; + } + + public AbsFace getUp() { + return up; + } + + @Override + protected void transform(Vec3i userLocation, Vec3i output) { + GenericChunks.resolve(userLocation, up, output); + } + + @Override + protected void untransform(Vec3i parentLocation, Vec3i output) { + GenericChunks.relativize(parentLocation, up, output); + } + + @Override + protected RelFace transform(RelFace userFace) { + return userFace.resolve(up).relativize(AbsFace.POS_Z); + } + + @Override + protected RelFace untransform(RelFace parentFace) { + return parentFace.resolve(AbsFace.POS_Z).relativize(up); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/context/impl/TransformingServerContext.java b/src/main/java/ru/windcorp/progressia/server/world/context/impl/TransformingServerContext.java new file mode 100644 index 0000000..9c0be6c --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/context/impl/TransformingServerContext.java @@ -0,0 +1,284 @@ +/* + * 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.server.world.context.impl; + +import java.util.ArrayList; +import java.util.List; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.block.BlockData; +import ru.windcorp.progressia.common.world.rels.AbsFace; +import ru.windcorp.progressia.common.world.rels.RelFace; +import ru.windcorp.progressia.common.world.tile.TileData; +import ru.windcorp.progressia.server.world.context.ServerBlockContext; +import ru.windcorp.progressia.server.world.context.ServerTileContext; +import ru.windcorp.progressia.server.world.context.ServerTileStackContext; + +public abstract class TransformingServerContext extends FilterServerContext { + + private final Vec3i location = new Vec3i(); + private boolean isLocationValid = false; + + private RelFace face = null; + + private final List vectorCache = new ArrayList<>(1); + + public TransformingServerContext(ServerTileContext parent) { + super(parent); + } + + protected abstract void transform(Vec3i userLocation, Vec3i output); + + protected abstract void untransform(Vec3i parentLocation, Vec3i output); + + protected abstract RelFace transform(RelFace userFace); + + protected abstract RelFace untransform(RelFace parentFace); + + protected void invalidateCache() { + isLocationValid = false; + face = null; + } + + private Vec3i grabVector(Vec3i userVector) { + Vec3i parentVector; + + if (vectorCache.isEmpty()) { + parentVector = new Vec3i(); + } else { + parentVector = vectorCache.remove(vectorCache.size() - 1); + } + + transform(userVector, parentVector); + + return parentVector; + } + + private void releaseVector(Vec3i parentVector) { + vectorCache.add(parentVector); + } + + @Override + public Vec3i getLocation() { + // Always invoke parent method to allow parent to determine validity + Vec3i parentLocation = super.getLocation(); + + if (!isLocationValid) { + untransform(parentLocation, location); + isLocationValid = true; + } + + return location; + } + + @Override + public RelFace getFace() { + // Always invoke parent method to allow parent to determine validity + RelFace parentFace = super.getFace(); + + if (face == null) { + face = untransform(parentFace); + } + + return face; + } + + @Override + public void pop() { + super.pop(); + invalidateCache(); + } + + @Override + public ServerBlockContext push(Vec3i userLocation) { + Vec3i parentLocation = grabVector(userLocation); + super.push(parentLocation); + releaseVector(parentLocation); + + location.set(userLocation.x, userLocation.y, userLocation.z); + isLocationValid = true; + face = null; + + return this; + } + + @Override + public ServerTileStackContext push(Vec3i userLocation, RelFace userFace) { + Vec3i parentLocation = grabVector(userLocation); + super.push(parentLocation, transform(userFace)); + releaseVector(parentLocation); + + location.set(userLocation.x, userLocation.y, userLocation.z); + isLocationValid = true; + face = userFace; + + return this; + } + + @Override + public ServerTileContext push(Vec3i userLocation, RelFace userFace, int layer) { + Vec3i parentLocation = grabVector(userLocation); + super.push(parentLocation, transform(userFace), layer); + releaseVector(parentLocation); + + location.set(userLocation.x, userLocation.y, userLocation.z); + isLocationValid = true; + face = userFace; + + return this; + } + + @Override + public Vec3i toAbsolute(Vec3i contextLocation, Vec3i output) { + if (output == null) { + output = new Vec3i(); + } + + transform(contextLocation, output); + + return super.toAbsolute(output, output); + } + + @Override + public Vec3i toContext(Vec3i absoluteLocation, Vec3i output) { + output = super.toContext(absoluteLocation, output); + untransform(output, output); + return output; + } + + @Override + public AbsFace toAbsolute(RelFace contextFace) { + return super.toAbsolute(transform(contextFace)); + } + + @Override + public RelFace toContext(AbsFace absoluteFace) { + return untransform(super.toContext(absoluteFace)); + } + + @Override + public boolean isLocationLoaded(Vec3i userLocation) { + Vec3i parentLocation = grabVector(userLocation); + + try { + return super.isLocationLoaded(parentLocation); + } finally { + releaseVector(parentLocation); + } + } + + @Override + public BlockData getBlock(Vec3i userLocation) { + Vec3i parentLocation = grabVector(userLocation); + + try { + return super.getBlock(parentLocation); + } finally { + releaseVector(parentLocation); + } + } + + @Override + public void setBlock(Vec3i userLocation, BlockData block) { + Vec3i parentLocation = grabVector(userLocation); + + try { + super.setBlock(parentLocation, block); + } finally { + releaseVector(parentLocation); + } + } + + @Override + public boolean hasTile(Vec3i userLocation, RelFace userFace, int layer) { + Vec3i parentLocation = grabVector(userLocation); + + try { + return super.hasTile(parentLocation, transform(userFace), layer); + } finally { + releaseVector(parentLocation); + } + } + + @Override + public TileData getTile(Vec3i userLocation, RelFace userFace, int layer) { + Vec3i parentLocation = grabVector(userLocation); + + try { + return super.getTile(parentLocation, transform(userFace), layer); + } finally { + releaseVector(parentLocation); + } + } + + @Override + public boolean isTagValid(Vec3i userLocation, RelFace userFace, int tag) { + Vec3i parentLocation = grabVector(userLocation); + + try { + return super.isTagValid(parentLocation, transform(userFace), tag); + } finally { + releaseVector(parentLocation); + } + } + + @Override + public TileData getTileByTag(Vec3i userLocation, RelFace userFace, int tag) { + Vec3i parentLocation = grabVector(userLocation); + + try { + return super.getTileByTag(parentLocation, transform(userFace), tag); + } finally { + releaseVector(parentLocation); + } + } + + @Override + public int getTileCount(Vec3i userLocation, RelFace userFace) { + Vec3i parentLocation = grabVector(userLocation); + + try { + return super.getTileCount(parentLocation, transform(userFace)); + } finally { + releaseVector(parentLocation); + } + } + + @Override + public void addTile(Vec3i userLocation, RelFace userFace, TileData tile) { + Vec3i parentLocation = grabVector(userLocation); + + try { + super.addTile(parentLocation, transform(userFace), tile); + } finally { + releaseVector(parentLocation); + } + } + + @Override + public void removeTile(Vec3i userLocation, RelFace userFace, int tag) { + Vec3i parentLocation = grabVector(userLocation); + + try { + super.removeTile(parentLocation, transform(userFace), tag); + } finally { + releaseVector(parentLocation); + } + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/entity/EntityLogic.java b/src/main/java/ru/windcorp/progressia/server/world/entity/EntityLogic.java index 0f33517..3f87caf 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/entity/EntityLogic.java +++ b/src/main/java/ru/windcorp/progressia/server/world/entity/EntityLogic.java @@ -20,7 +20,7 @@ package ru.windcorp.progressia.server.world.entity; import ru.windcorp.progressia.common.util.namespaces.Namespaced; import ru.windcorp.progressia.common.world.entity.EntityData; -import ru.windcorp.progressia.server.world.TickContext; +import ru.windcorp.progressia.server.world.context.ServerWorldContext; public class EntityLogic extends Namespaced { @@ -28,7 +28,7 @@ public class EntityLogic extends Namespaced { super(id); } - public void tick(EntityData entity, TickContext context) { + public void tick(EntityData entity, ServerWorldContext context) { entity.incrementAge(context.getTickLength()); } diff --git a/src/main/java/ru/windcorp/progressia/server/world/generation/AbstractWorldGenerator.java b/src/main/java/ru/windcorp/progressia/server/world/generation/AbstractWorldGenerator.java index 2753a8f..c91594e 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/generation/AbstractWorldGenerator.java +++ b/src/main/java/ru/windcorp/progressia/server/world/generation/AbstractWorldGenerator.java @@ -23,16 +23,31 @@ import java.io.DataOutputStream; import java.io.IOException; import java.util.Objects; -import ru.windcorp.progressia.common.world.ChunkData; +import ru.windcorp.progressia.common.world.DefaultChunkData; import ru.windcorp.progressia.common.world.DecodingException; +import ru.windcorp.progressia.common.world.GravityModel; +import ru.windcorp.progressia.common.world.GravityModelRegistry; +import ru.windcorp.progressia.common.world.DefaultWorldData; +import ru.windcorp.progressia.server.Server; +import ru.windcorp.progressia.server.world.DefaultWorldLogic; public abstract class AbstractWorldGenerator extends WorldGenerator { private final Class hintClass; + + private final GravityModel gravityModel; + + private final Server server; - public AbstractWorldGenerator(String id, Class hintClass) { + public AbstractWorldGenerator(String id, Server server, Class hintClass, String gravityModelId) { super(id); this.hintClass = Objects.requireNonNull(hintClass, "hintClass"); + this.gravityModel = GravityModelRegistry.getInstance().create(Objects.requireNonNull(gravityModelId, "gravityModelId")); + this.server = server; + + if (this.gravityModel == null) { + throw new IllegalArgumentException("Gravity model with ID \"" + gravityModelId + "\" not found"); + } } @Override @@ -56,12 +71,32 @@ public abstract class AbstractWorldGenerator extends WorldGenerator { protected abstract boolean checkIsChunkReady(H hint); - protected H getHint(ChunkData chunk) { + protected H getHint(DefaultChunkData chunk) { return hintClass.cast(chunk.getGenerationHint()); } - protected void setHint(ChunkData chunk, H hint) { + protected void setHint(DefaultChunkData chunk, H hint) { chunk.setGenerationHint(hint); } + + @Override + public GravityModel getGravityModel() { + return gravityModel; + } + + @Override + public Server getServer() { + return server; + } + + @Override + public DefaultWorldLogic getWorldLogic() { + return server.getWorld(); + } + + @Override + public DefaultWorldData getWorldData() { + return server.getWorld().getData(); + } } diff --git a/src/main/java/ru/windcorp/progressia/server/world/generation/WorldGenerator.java b/src/main/java/ru/windcorp/progressia/server/world/generation/WorldGenerator.java index 41a7885..200fdfb 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/generation/WorldGenerator.java +++ b/src/main/java/ru/windcorp/progressia/server/world/generation/WorldGenerator.java @@ -22,11 +22,15 @@ import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; +import glm.vec._3.Vec3; import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.common.util.namespaces.Namespaced; -import ru.windcorp.progressia.common.world.ChunkData; +import ru.windcorp.progressia.common.world.DefaultChunkData; import ru.windcorp.progressia.common.world.DecodingException; -import ru.windcorp.progressia.common.world.WorldData; +import ru.windcorp.progressia.common.world.GravityModel; +import ru.windcorp.progressia.common.world.DefaultWorldData; +import ru.windcorp.progressia.server.Server; +import ru.windcorp.progressia.server.world.DefaultWorldLogic; public abstract class WorldGenerator extends Namespaced { @@ -35,12 +39,20 @@ public abstract class WorldGenerator extends Namespaced { // package-private constructor; extend AbstractWorldGeneration } - public abstract ChunkData generate(Vec3i chunkPos, WorldData world); + public abstract DefaultChunkData generate(Vec3i chunkPos); public abstract Object readGenerationHint(DataInputStream input) throws IOException, DecodingException; public abstract void writeGenerationHint(DataOutputStream output, Object hint) throws IOException; public abstract boolean isChunkReady(Object hint); + + public abstract GravityModel getGravityModel(); + + public abstract Vec3 suggestSpawnLocation(); + + public abstract Server getServer(); + public abstract DefaultWorldLogic getWorldLogic(); + public abstract DefaultWorldData getWorldData(); } diff --git a/src/main/java/ru/windcorp/progressia/server/world/generation/planet/Planet.java b/src/main/java/ru/windcorp/progressia/server/world/generation/planet/Planet.java new file mode 100644 index 0000000..161e2c0 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/generation/planet/Planet.java @@ -0,0 +1,89 @@ +/* + * 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.server.world.generation.planet; + +import ru.windcorp.progressia.common.world.DefaultChunkData; + +public class Planet { + + private final int radiusInChunks; + + private final PlanetGravityModel.Settings gravityModelSettings; + + public Planet( + int radiusInChunks, + float surfaceGravitationalAcceleration, + float curvature, + float innerGravityRadius + ) { + this.radiusInChunks = radiusInChunks; + this.gravityModelSettings = new PlanetGravityModel.Settings( + surfaceGravitationalAcceleration, + curvature, + innerGravityRadius + ); + } + + /** + * @return the radiusInChunks + */ + public int getRadiusInChunks() { + return radiusInChunks; + } + + public float getRadius() { + return radiusInChunks * DefaultChunkData.BLOCKS_PER_CHUNK + DefaultChunkData.CHUNK_RADIUS; + } + + public int getDiameterInChunks() { + return radiusInChunks * 2 + 1; + } + + public float getDiameter() { + return getDiameterInChunks() * DefaultChunkData.BLOCKS_PER_CHUNK; + } + + /** + * @return the curvature + */ + public float getCurvature() { + return gravityModelSettings.curvature; + } + + /** + * @return the innerGravityRadius + */ + public float getInnerGravityRadius() { + return gravityModelSettings.innerRadius; + } + + /** + * @return the surfaceGravitationalAcceleration + */ + public float getSurfaceGravitationalAcceleration() { + return gravityModelSettings.surfaceGravitationalAcceleration; + } + + /** + * @return the gravityModelSettings + */ + public PlanetGravityModel.Settings getGravityModelSettings() { + return gravityModelSettings; + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/generation/planet/PlanetFeatureGenerator.java b/src/main/java/ru/windcorp/progressia/server/world/generation/planet/PlanetFeatureGenerator.java new file mode 100644 index 0000000..97e2424 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/generation/planet/PlanetFeatureGenerator.java @@ -0,0 +1,75 @@ +/* + * 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.server.world.generation.planet; + +import java.util.List; +import java.util.Map; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.util.VectorUtil; +import ru.windcorp.progressia.common.world.DefaultChunkData; +import ru.windcorp.progressia.common.world.rels.AbsFace; +import ru.windcorp.progressia.server.Server; +import ru.windcorp.progressia.server.world.generation.surface.Surface; +import ru.windcorp.progressia.server.world.generation.surface.SurfaceFeature; +import ru.windcorp.progressia.server.world.generation.surface.SurfaceFeatureGenerator; + +public class PlanetFeatureGenerator { + + private final PlanetGenerator parent; + + private final Map surfaceGenerators; + + public PlanetFeatureGenerator(PlanetGenerator generator, List features) { + this.parent = generator; + + int seaLevel = (int) parent.getPlanet().getRadius(); + this.surfaceGenerators = AbsFace.mapToFaces(face -> new SurfaceFeatureGenerator( + new Surface(face, seaLevel), + features + )); + } + + public PlanetGenerator getGenerator() { + return parent; + } + + public void generateFeatures(Server server, DefaultChunkData chunk) { + if (isOrdinaryChunk(chunk.getPosition())) { + generateOrdinaryFeatures(server, chunk); + } else { + generateBorderFeatures(server, chunk); + } + + chunk.setGenerationHint(true); + } + + private boolean isOrdinaryChunk(Vec3i chunkPos) { + Vec3i sorted = VectorUtil.sortAfterAbs(chunkPos, null); + return sorted.x != sorted.y; + } + + private void generateOrdinaryFeatures(Server server, DefaultChunkData chunk) { + surfaceGenerators.get(chunk.getUp()).generateFeatures(server, chunk); + } + + private void generateBorderFeatures(Server server, DefaultChunkData chunk) { + // Do nothing + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/generation/planet/PlanetGenerator.java b/src/main/java/ru/windcorp/progressia/server/world/generation/planet/PlanetGenerator.java new file mode 100644 index 0000000..b2c5c8c --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/generation/planet/PlanetGenerator.java @@ -0,0 +1,112 @@ +/* + * 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.server.world.generation.planet; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.List; + +import glm.vec._3.Vec3; +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.util.FloatRangeMap; +import ru.windcorp.progressia.common.util.VectorUtil; +import ru.windcorp.progressia.common.world.DefaultChunkData; +import ru.windcorp.progressia.common.world.DecodingException; +import ru.windcorp.progressia.server.Server; +import ru.windcorp.progressia.server.world.generation.AbstractWorldGenerator; +import ru.windcorp.progressia.server.world.generation.surface.SurfaceFeature; +import ru.windcorp.progressia.server.world.generation.surface.SurfaceFloatField; +import ru.windcorp.progressia.server.world.generation.surface.TerrainLayer; + +public class PlanetGenerator extends AbstractWorldGenerator { + + private final Planet planet; + + private final PlanetTerrainGenerator terrainGenerator; + private final PlanetFeatureGenerator featureGenerator; + + public PlanetGenerator( + String id, + Server server, + Planet planet, + SurfaceFloatField heightMap, + FloatRangeMap layers, + List features + ) { + super(id, server, Boolean.class, "Test:PlanetGravityModel"); + + this.planet = planet; + + PlanetGravityModel model = (PlanetGravityModel) this.getGravityModel(); + model.configure(planet.getGravityModelSettings()); + + this.terrainGenerator = new PlanetTerrainGenerator(this, heightMap, layers); + this.featureGenerator = new PlanetFeatureGenerator(this, features); + } + + /** + * @return the planet + */ + public Planet getPlanet() { + return planet; + } + + @Override + public Vec3 suggestSpawnLocation() { + return new Vec3(7f, 1f, getPlanet().getRadius() + 10); + } + + @Override + protected Boolean doReadGenerationHint(DataInputStream input) throws IOException, DecodingException { + return input.readBoolean(); + } + + @Override + protected void doWriteGenerationHint(DataOutputStream output, Boolean hint) throws IOException { + output.writeBoolean(hint); + } + + @Override + protected boolean checkIsChunkReady(Boolean hint) { + return Boolean.TRUE.equals(hint); // Avoid NPE + } + + @Override + public DefaultChunkData generate(Vec3i chunkPos) { + VectorUtil.iterateCuboidAround(chunkPos, 3, r -> conjureTerrain(r)); + DefaultChunkData chunk = getWorldData().getChunk(chunkPos); + + if (!isChunkReady(chunk.getGenerationHint())) { + featureGenerator.generateFeatures(getServer(), chunk); + } + + return chunk; + } + + private void conjureTerrain(Vec3i chunkPos) { + getServer().getLoadManager().getChunkManager().loadChunk(chunkPos); + DefaultChunkData chunk = getWorldData().getChunk(chunkPos); + + if (chunk == null) { + chunk = terrainGenerator.generateTerrain(getServer(), chunkPos); + getWorldData().addChunk(chunk); + } + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/generation/planet/PlanetGravityModel.java b/src/main/java/ru/windcorp/progressia/server/world/generation/planet/PlanetGravityModel.java new file mode 100644 index 0000000..e2545b5 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/generation/planet/PlanetGravityModel.java @@ -0,0 +1,193 @@ +/* + * 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.server.world.generation.planet; + +import glm.vec._3.Vec3; +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.DefaultChunkData; +import ru.windcorp.progressia.common.world.DecodingException; +import ru.windcorp.progressia.common.world.GravityModel; +import ru.windcorp.progressia.common.world.rels.AbsFace; + +import static java.lang.Math.*; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +public class PlanetGravityModel extends GravityModel { + + public static class Settings { + public float surfaceGravitationalAcceleration; + public float curvature; + public float innerRadius; + + public Settings() {} + + public Settings(float surfaceGravitationalAcceleration, float curvature, float innerRadius) { + this.surfaceGravitationalAcceleration = surfaceGravitationalAcceleration; + this.curvature = curvature; + this.innerRadius = innerRadius; + } + + public void copyFrom(Settings copyFrom) { + this.surfaceGravitationalAcceleration = copyFrom.surfaceGravitationalAcceleration; + this.curvature = copyFrom.curvature; + this.innerRadius = copyFrom.innerRadius; + } + + public void read(DataInput input) throws IOException, DecodingException { + surfaceGravitationalAcceleration = input.readFloat(); + curvature = input.readFloat(); + innerRadius = input.readFloat(); + } + + public void write(DataOutput output) throws IOException { + output.writeFloat(surfaceGravitationalAcceleration); + output.writeFloat(curvature); + output.writeFloat(innerRadius); + } + } + + private Settings settings = new Settings(); + + public PlanetGravityModel(String id) { + super(id); + } + + public float getSurfaceGravitationalAcceleration() { + return settings.surfaceGravitationalAcceleration; + } + + public float getCurvature() { + return settings.curvature; + } + + public float getInnerRadius() { + return settings.innerRadius; + } + + public void configure(Settings settings) { + this.settings = settings; + } + + @Override + protected void doGetGravity(Vec3 pos, Vec3 output) { + float r = getInnerRadius(); + float c = getCurvature(); + + // Change to a CS where (0;0;0) is the center of the center chunk + float px = pos.x - DefaultChunkData.CHUNK_RADIUS + 0.5f; + float py = pos.y - DefaultChunkData.CHUNK_RADIUS + 0.5f; + float pz = pos.z - DefaultChunkData.CHUNK_RADIUS + 0.5f; + + // Assume weightlessness when too close to center + if ((px*px + py*py + pz*pz) < r*r) { + output.set(0, 0, 0); + return; + } + + // Cache absolute coordinates + float ax = abs(px); + float ay = abs(py); + float az = abs(pz); + + // Determine maximum and middle coordinates by absolute value + final float maxAbs; + final float midAbs; + + // herptyderp + if (ax > ay) { + if (ax > az) { + maxAbs = ax; + midAbs = ay > az ? ay : az; + } else { + maxAbs = az; + midAbs = ax; + } + } else { + if (ay > az) { + maxAbs = ay; + midAbs = ax > az ? ax : az; + } else { + maxAbs = az; + midAbs = ay; + } + } + + output.x = maxAbs - ax < c ? (px > 0 ? +1 : -1) : 0; + output.y = maxAbs - ay < c ? (py > 0 ? +1 : -1) : 0; + output.z = maxAbs - az < c ? (pz > 0 ? +1 : -1) : 0; + + if (maxAbs - midAbs < c) { + output.normalize(); + computeEdgeGravity(output.x, output.y, output.z, px, py, pz, output); + } else { + assert output.dot(output) == 1 : "maxAbs - midAbs = " + maxAbs + " - " + midAbs + " > " + c + " yet l*l != 1"; + } + + output.mul(-getSurfaceGravitationalAcceleration()); + } + + private void computeEdgeGravity(float lx, float ly, float lz, float rx, float ry, float rz, Vec3 output) { + // da math is gud, no worry + // - Javapony + + float c = getCurvature(); + + if (lx == 0) rx = 0; + if (ly == 0) ry = 0; + if (lz == 0) rz = 0; + + float scalarProduct = rx*lx + ry*ly + rz*lz; + float rSquared = rx*rx + ry*ry + rz*rz; + + float distanceAlongEdge = scalarProduct - (float) sqrt( + scalarProduct*scalarProduct - rSquared + c*c + ); + + output.set(lx, ly, lz).mul(-distanceAlongEdge).add(rx, ry, rz).div(c); + + final float f = (float) sqrt(3.0/2); + + if (signum(lx) != signum(output.x)) { + computeEdgeGravity(0, ly*f, lz*f, rx, ry, rz, output); + } else if (signum(ly) != signum(output.y)) { + computeEdgeGravity(lx*f, 0, lz*f, rx, ry, rz, output); + } else if (signum(lz) != signum(output.z)) { + computeEdgeGravity(lx*f, ly*f, 0, rx, ry, rz, output); + } + } + + @Override + protected AbsFace doGetDiscreteUp(Vec3i chunkPos) { + AbsFace rounded = AbsFace.roundToFace(chunkPos.x, chunkPos.y, chunkPos.z); + return rounded == null ? AbsFace.POS_Z : rounded; + } + + @Override + protected void doReadSettings(DataInput input) throws IOException, DecodingException { + this.settings.read(input); + } + + @Override + protected void doWriteSettings(DataOutput output) throws IOException { + this.settings.write(output); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/generation/planet/PlanetTerrainGenerator.java b/src/main/java/ru/windcorp/progressia/server/world/generation/planet/PlanetTerrainGenerator.java new file mode 100644 index 0000000..dba3b8a --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/generation/planet/PlanetTerrainGenerator.java @@ -0,0 +1,113 @@ +/* + * 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.server.world.generation.planet; + +import java.util.Map; + +import glm.vec._3.Vec3; +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.util.FloatRangeMap; +import ru.windcorp.progressia.common.util.VectorUtil; +import ru.windcorp.progressia.common.world.DefaultChunkData; +import ru.windcorp.progressia.common.world.Coordinates; +import ru.windcorp.progressia.common.world.block.BlockData; +import ru.windcorp.progressia.common.world.block.BlockDataRegistry; +import ru.windcorp.progressia.common.world.generic.GenericChunks; +import ru.windcorp.progressia.common.world.rels.AbsFace; +import ru.windcorp.progressia.server.Server; +import ru.windcorp.progressia.server.world.generation.surface.Surface; +import ru.windcorp.progressia.server.world.generation.surface.SurfaceFloatField; +import ru.windcorp.progressia.server.world.generation.surface.SurfaceTerrainGenerator; +import ru.windcorp.progressia.server.world.generation.surface.TerrainLayer; + +class PlanetTerrainGenerator { + + private final PlanetGenerator parent; + private final Map surfaceGenerators; + + public PlanetTerrainGenerator( + PlanetGenerator generator, + SurfaceFloatField heightMap, + FloatRangeMap layers + ) { + this.parent = generator; + + int seaLevel = (int) parent.getPlanet().getRadius(); + SurfaceFloatField adjustedHeightMap = (f, n, w) -> heightMap.get(f, n, w) + generator.getPlanet().getRadius(); + + this.surfaceGenerators = AbsFace.mapToFaces( + face -> new SurfaceTerrainGenerator( + new Surface(face, seaLevel), + adjustedHeightMap, + layers + ) + ); + } + + public PlanetGenerator getGenerator() { + return parent; + } + + public DefaultChunkData generateTerrain(Server server, Vec3i chunkPos) { + DefaultChunkData chunk = new DefaultChunkData(chunkPos, getGenerator().getWorldData()); + + if (isOrdinaryChunk(chunkPos)) { + generateOrdinaryTerrain(server, chunk); + } else { + generateBorderTerrain(server, chunk); + } + + chunk.setGenerationHint(false); + + return chunk; + } + + private boolean isOrdinaryChunk(Vec3i chunkPos) { + Vec3i sorted = VectorUtil.sortAfterAbs(chunkPos, null); + return sorted.x != sorted.y; + } + + private void generateOrdinaryTerrain(Server server, DefaultChunkData chunk) { + surfaceGenerators.get(chunk.getUp()).generateTerrain(server, chunk); + } + + private void generateBorderTerrain(Server server, DefaultChunkData chunk) { + BlockData stone = BlockDataRegistry.getInstance().get("Test:Stone"); + BlockData air = BlockDataRegistry.getInstance().get("Test:Air"); + + float radius = parent.getPlanet().getRadius(); + + Vec3 biw = new Vec3(); + + GenericChunks.forEachBiC(bic -> { + + biw.set( + Coordinates.getInWorld(chunk.getX(), bic.x), + Coordinates.getInWorld(chunk.getY(), bic.y), + Coordinates.getInWorld(chunk.getZ(), bic.z) + ); + + biw.sub(DefaultChunkData.CHUNK_RADIUS - 0.5f); + VectorUtil.sortAfterAbs(biw, biw); + + chunk.setBlock(bic, biw.x <= radius ? stone : air, false); + + }); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/generation/surface/Surface.java b/src/main/java/ru/windcorp/progressia/server/world/generation/surface/Surface.java new file mode 100644 index 0000000..08c9308 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/generation/surface/Surface.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.server.world.generation.surface; + +import java.util.Random; + +import glm.Glm; +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.util.CoordinatePacker; +import ru.windcorp.progressia.common.world.generic.ChunkGenericRO; +import ru.windcorp.progressia.common.world.rels.AbsFace; +import ru.windcorp.progressia.server.Server; +import ru.windcorp.progressia.server.world.context.ServerTileContext; +import ru.windcorp.progressia.server.world.generation.surface.context.SurfaceContextImpl; +import ru.windcorp.progressia.server.world.generation.surface.context.SurfaceWorldContext; + +public class Surface { + + private final AbsFace up; + private final int seaLevel; + + public Surface(AbsFace up, int seaLevel) { + this.up = up; + this.seaLevel = seaLevel; + } + + /** + * @return the up + */ + public AbsFace getUp() { + return up; + } + + /** + * @return the seaLevel + */ + public int getSeaLevel() { + return seaLevel; + } + + public SurfaceWorldContext createContext(Server server, ChunkGenericRO chunk, long seed) { + + Random random = new Random(CoordinatePacker.pack3IntsIntoLong(chunk.getPosition()) ^ seed); + + SurfaceContextImpl context = new SurfaceContextImpl((ServerTileContext) server.createAbsoluteContext(), this); + context.setRandom(random); + + Vec3i tmpA = new Vec3i(); + Vec3i tmpB = new Vec3i(); + + chunk.getMinBIW(tmpA); + chunk.getMaxBIW(tmpB); + + context.toContext(tmpA, tmpA); + context.toContext(tmpB, tmpB); + + Glm.min(tmpA, tmpB, context.getMin()); + Glm.max(tmpA, tmpB, context.getMax()); + + return context; + + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/generation/surface/SurfaceFeature.java b/src/main/java/ru/windcorp/progressia/server/world/generation/surface/SurfaceFeature.java new file mode 100644 index 0000000..8430168 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/generation/surface/SurfaceFeature.java @@ -0,0 +1,46 @@ +/* + * 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.server.world.generation.surface; + +import java.util.List; + +import ru.windcorp.progressia.common.util.namespaces.Namespaced; +import ru.windcorp.progressia.server.world.context.ServerContext; +import ru.windcorp.progressia.server.world.generation.surface.context.SurfaceWorldContext; + +public abstract class SurfaceFeature extends Namespaced { + + public SurfaceFeature(String id) { + super(id); + } + + public abstract void process(SurfaceWorldContext context); + + protected static double randomDouble(ServerContext context, double from, double to) { + return from + (to - from) * context.getRandom().nextDouble(); + } + + protected static double stretch(double t, double from, double to) { + return from + (to - from) * t; + } + + protected static T pickRandom(ServerContext context, List from) { + return from.get(context.getRandom().nextInt(from.size())); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/generation/surface/SurfaceFeatureGenerator.java b/src/main/java/ru/windcorp/progressia/server/world/generation/surface/SurfaceFeatureGenerator.java new file mode 100644 index 0000000..3548184 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/generation/surface/SurfaceFeatureGenerator.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.server.world.generation.surface; + +import java.util.ArrayList; +import java.util.List; +import ru.windcorp.progressia.common.world.DefaultChunkData; +import ru.windcorp.progressia.server.Server; +import ru.windcorp.progressia.server.world.generation.surface.context.SurfaceWorldContext; + +public class SurfaceFeatureGenerator { + + private final Surface surface; + + private final List features; + + public SurfaceFeatureGenerator(Surface surface, List features) { + this.surface = surface; + this.features = new ArrayList<>(features); + } + + /** + * @return the surface + */ + public Surface getSurface() { + return surface; + } + + public void generateFeatures(Server server, DefaultChunkData chunk) { + SurfaceWorldContext context = surface.createContext(server, chunk, 0); + + for (SurfaceFeature feature : features) { + feature.process(context); + } + + chunk.setGenerationHint(true); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/generation/surface/SurfaceFloatField.java b/src/main/java/ru/windcorp/progressia/server/world/generation/surface/SurfaceFloatField.java new file mode 100644 index 0000000..81506f1 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/generation/surface/SurfaceFloatField.java @@ -0,0 +1,34 @@ +/* + * 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.server.world.generation.surface; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.rels.AbsFace; +import ru.windcorp.progressia.server.world.generation.surface.context.SurfaceBlockContext; + +@FunctionalInterface +public interface SurfaceFloatField { + + float get(AbsFace face, float x, float y); + + default float get(SurfaceBlockContext context) { + Vec3i location = context.getLocation(); + return get(context.getSurface().getUp(), location.x, location.y); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/generation/surface/SurfaceTerrainGenerator.java b/src/main/java/ru/windcorp/progressia/server/world/generation/surface/SurfaceTerrainGenerator.java new file mode 100644 index 0000000..35b4a58 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/generation/surface/SurfaceTerrainGenerator.java @@ -0,0 +1,89 @@ +/* + * 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.server.world.generation.surface; + +import glm.vec._3.Vec3; +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.util.FloatRangeMap; +import ru.windcorp.progressia.common.util.Vectors; +import ru.windcorp.progressia.common.world.DefaultChunkData; +import ru.windcorp.progressia.common.world.block.BlockData; +import ru.windcorp.progressia.common.world.rels.AxisRotations; +import ru.windcorp.progressia.server.Server; +import ru.windcorp.progressia.server.world.generation.surface.context.SurfaceBlockContext; +import ru.windcorp.progressia.server.world.generation.surface.context.SurfaceWorldContext; + +public class SurfaceTerrainGenerator { + + private final Surface surface; + + private final SurfaceFloatField heightMap; + private final FloatRangeMap layers; + + public SurfaceTerrainGenerator(Surface surface, SurfaceFloatField heightMap, FloatRangeMap layers) { + this.surface = surface; + this.heightMap = heightMap; + this.layers = layers; + } + + public void generateTerrain(Server server, DefaultChunkData chunk) { + + Vec3i relBIC = new Vec3i(); + + Vec3 offset = new Vec3(chunk.getMinX(), chunk.getMinY(), chunk.getMinZ()); + AxisRotations.relativize(offset, chunk.getUp(), offset); + + SurfaceWorldContext context = surface.createContext(server, chunk, 0); + + for (relBIC.x = 0; relBIC.x < DefaultChunkData.BLOCKS_PER_CHUNK; ++relBIC.x) { + for (relBIC.y = 0; relBIC.y < DefaultChunkData.BLOCKS_PER_CHUNK; ++relBIC.y) { + generateColumn(chunk, relBIC, offset, context); + } + } + + } + + public void generateColumn(DefaultChunkData chunk, Vec3i relBIC, Vec3 offset, SurfaceWorldContext context) { + + int north = (int) (relBIC.x + offset.x); + int west = (int) (relBIC.y + offset.y); + + float relSurface = heightMap.get(chunk.getUp(), north, west) - offset.z + DefaultChunkData.CHUNK_RADIUS - 0.5f; + Vec3i location = Vectors.grab3i(); + + for (relBIC.z = 0; relBIC.z < DefaultChunkData.BLOCKS_PER_CHUNK; ++relBIC.z) { + float depth = relSurface - relBIC.z; + int altitude = (int) (relBIC.z + offset.z); + + location.set(north, west, altitude); + SurfaceBlockContext blockContext = context.push(location); + + BlockData block = layers.get(depth).get(blockContext, depth); + + blockContext.pop(); + + chunk.resolve(relBIC, relBIC); + chunk.setBlock(relBIC, block, false); + chunk.relativize(relBIC, relBIC); + } + + Vectors.release(location); + + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/generation/surface/SurfaceTopLayerFeature.java b/src/main/java/ru/windcorp/progressia/server/world/generation/surface/SurfaceTopLayerFeature.java new file mode 100644 index 0000000..d96d68b --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/generation/surface/SurfaceTopLayerFeature.java @@ -0,0 +1,64 @@ +/* + * 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.server.world.generation.surface; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.server.world.generation.surface.context.SurfaceBlockContext; +import ru.windcorp.progressia.server.world.generation.surface.context.SurfaceWorldContext; + +public abstract class SurfaceTopLayerFeature extends SurfaceFeature { + + public SurfaceTopLayerFeature(String id) { + super(id); + } + + protected abstract void processTopBlock(SurfaceBlockContext context); + + protected abstract boolean isSolid(SurfaceBlockContext context); + + @Override + public void process(SurfaceWorldContext context) { + Vec3i cursor = new Vec3i(); + + context.forEachOnFloor(pos -> { + + cursor.set(pos.x, pos.y, pos.z); + + if (!isSolid(context.push(cursor))) { + context.pop(); + return; + } + context.pop(); + + for (cursor.z += 1; cursor.z <= context.getMaxZ() + 1; ++cursor.z) { + SurfaceBlockContext blockContext = context.push(cursor); + + if (!isSolid(blockContext)) { + processTopBlock(blockContext.pushRelative(0, 0, -1)); + context.pop(); + context.pop(); + return; + } + + context.pop(); + } + + }); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/generation/surface/TerrainLayer.java b/src/main/java/ru/windcorp/progressia/server/world/generation/surface/TerrainLayer.java new file mode 100644 index 0000000..5ec131c --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/generation/surface/TerrainLayer.java @@ -0,0 +1,28 @@ +/* + * 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.server.world.generation.surface; + +import ru.windcorp.progressia.common.world.block.BlockData; +import ru.windcorp.progressia.server.world.generation.surface.context.SurfaceBlockContext; + +@FunctionalInterface +public interface TerrainLayer { + + BlockData get(SurfaceBlockContext context, float depth); + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/generation/surface/context/SurfaceBlockContext.java b/src/main/java/ru/windcorp/progressia/server/world/generation/surface/context/SurfaceBlockContext.java new file mode 100644 index 0000000..64531fb --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/generation/surface/context/SurfaceBlockContext.java @@ -0,0 +1,87 @@ +/* + * 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.server.world.generation.surface.context; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.rels.RelFace; +import ru.windcorp.progressia.common.world.rels.RelRelation; +import ru.windcorp.progressia.server.world.context.ServerBlockContext; + +public interface SurfaceBlockContext extends ServerBlockContext, SurfaceWorldContext { + + public interface Logic extends ServerBlockContext.Logic, SurfaceWorldContext.Logic { + + @Override + SurfaceBlockContext data(); + + @Override + default SurfaceBlockContext.Logic pushRelative(int dx, int dy, int dz) { + return push(getLocation().add_(dx, dy, dz)); + } + + @Override + default SurfaceBlockContext.Logic pushRelative(Vec3i direction) { + return push(getLocation().add_(direction)); + } + + @Override + default SurfaceBlockContext.Logic pushRelative(RelRelation direction) { + return push(getLocation().add_(direction.getRelVector())); + } + + @Override + default SurfaceTileStackContext.Logic push(RelFace face) { + return push(getLocation(), face); + } + + @Override + default SurfaceTileContext.Logic push(RelFace face, int layer) { + return push(getLocation(), face, layer); + } + + } + + @Override + SurfaceBlockContext.Logic logic(); + + @Override + default SurfaceBlockContext pushRelative(int dx, int dy, int dz) { + return push(getLocation().add_(dx, dy, dz)); + } + + @Override + default SurfaceBlockContext pushRelative(Vec3i direction) { + return push(getLocation().add_(direction)); + } + + @Override + default SurfaceBlockContext pushRelative(RelRelation direction) { + return push(getLocation().add_(direction.getRelVector())); + } + + @Override + default SurfaceTileStackContext push(RelFace face) { + return push(getLocation(), face); + } + + @Override + default SurfaceTileContext push(RelFace face, int layer) { + return push(getLocation(), face, layer); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/generation/surface/context/SurfaceContext.java b/src/main/java/ru/windcorp/progressia/server/world/generation/surface/context/SurfaceContext.java new file mode 100644 index 0000000..a69d60b --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/generation/surface/context/SurfaceContext.java @@ -0,0 +1,136 @@ +/* + * 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.server.world.generation.surface.context; + +import java.util.function.Consumer; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.util.VectorUtil; +import ru.windcorp.progressia.server.world.generation.surface.Surface; + +public interface SurfaceContext { + + /** + * Returns the {@link Surface} object relevant to this context. + * + * @return the surface details + */ + Surface getSurface(); + + /** + * Returns lower bounds (inclusive) on the coordinates of the requested + * region. + * + * @return the coordinates of the minimum corner of the region that + * should be processed + */ + Vec3i getMin(); + + /** + * Returns upper bounds (inclusive) on the coordinates of the requested + * region. + * + * @return the coordinates of the maximum corner of the region that + * should be processed + */ + Vec3i getMax(); + + /* + * Convenience methods + */ + + default int getMinX() { + return getMin().x; + } + + default int getMinY() { + return getMin().y; + } + + default int getMinZ() { + return getMin().z; + } + + default int getMaxX() { + return getMax().x; + } + + default int getMaxY() { + return getMax().y; + } + + default int getMaxZ() { + return getMax().z; + } + + default boolean isRequested(int x, int y, int z) { + Vec3i min = getMin(); + Vec3i max = getMax(); + return (min.x <= x && x <= max.x) && (min.y <= y && y <= max.y) && (min.z <= z && z <= max.z); + } + + default boolean isRequested(Vec3i location) { + return isRequested(location.x, location.y, location.z); + } + + default void forEachRequested(Consumer action) { + Vec3i min = getMin(); + Vec3i max = getMax(); + VectorUtil.iterateCuboid( + min.x, + min.y, + min.z, + max.x + 1, + max.y + 1, + max.z + 1, + action + ); + } + + /** + * Provided vectors have z set to {@link #getMinZ()}. + */ + default void forEachOnFloor(Consumer action) { + forEachOnLayer(action, getMinZ()); + } + + /** + * Provided vectors have z set to {@link #getMaxZ()}. + */ + default void forEachOnCeiling(Consumer action) { + forEachOnLayer(action, getMaxZ()); + } + + /** + * Provided vectors have z set to layer. + */ + default void forEachOnLayer(Consumer action, int layer) { + Vec3i min = getMin(); + Vec3i max = getMax(); + VectorUtil.iterateCuboid( + min.x, + min.y, + layer, + max.x + 1, + max.y + 1, + layer + 1, + action + ); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/generation/surface/context/SurfaceContextImpl.java b/src/main/java/ru/windcorp/progressia/server/world/generation/surface/context/SurfaceContextImpl.java new file mode 100644 index 0000000..6050c49 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/generation/surface/context/SurfaceContextImpl.java @@ -0,0 +1,114 @@ +/* + * 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.server.world.generation.surface.context; + +import java.util.Random; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.rels.RelFace; +import ru.windcorp.progressia.server.world.context.ServerTileContext; +import ru.windcorp.progressia.server.world.context.impl.RotatingServerContext; +import ru.windcorp.progressia.server.world.generation.surface.Surface; + +public class SurfaceContextImpl extends RotatingServerContext implements SurfaceTileContext { + + final Surface surface; + final Vec3i min = new Vec3i(); + final Vec3i max = new Vec3i(); + private Random random; + + private final SurfaceContextImplLogic logic; + + public SurfaceContextImpl(ServerTileContext parent, Surface surface) { + super(parent, surface.getUp()); + this.logic = new SurfaceContextImplLogic(this); + + this.surface = surface; + } + + @Override + protected void transform(Vec3i userLocation, Vec3i output) { + output.set(userLocation.x, userLocation.y, userLocation.z); + output.z += surface.getSeaLevel(); + super.transform(output, output); + } + + @Override + protected void untransform(Vec3i parentLocation, Vec3i output) { + super.untransform(parentLocation, output); + output.z -= surface.getSeaLevel(); + } + + @Override + public Surface getSurface() { + return surface; + } + + /** + * {@inheritDoc} + * + * @implNote can rw + */ + @Override + public Vec3i getMin() { + return min; + } + + /** + * {@inheritDoc} + * + * @implNote can rw + */ + @Override + public Vec3i getMax() { + return max; + } + + @Override + public Random getRandom() { + return random; + } + + public void setRandom(Random random) { + this.random = random; + } + + @Override + public SurfaceContextImplLogic logic() { + return logic; + } + + @Override + public SurfaceTileContext push(Vec3i location) { + super.push(location); + return this; + } + + @Override + public SurfaceTileContext push(Vec3i location, RelFace face) { + super.push(location, face); + return this; + } + + @Override + public SurfaceTileContext push(Vec3i location, RelFace face, int layer) { + super.push(location, face, layer); + return this; + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/generation/surface/context/SurfaceContextImplLogic.java b/src/main/java/ru/windcorp/progressia/server/world/generation/surface/context/SurfaceContextImplLogic.java new file mode 100644 index 0000000..cbf9b88 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/generation/surface/context/SurfaceContextImplLogic.java @@ -0,0 +1,72 @@ +/* + * 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.server.world.generation.surface.context; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.rels.RelFace; +import ru.windcorp.progressia.server.world.context.impl.DefaultServerContextLogic; +import ru.windcorp.progressia.server.world.generation.surface.Surface; + +public class SurfaceContextImplLogic extends DefaultServerContextLogic implements SurfaceTileContext.Logic { + + private final SurfaceContextImpl surfaceParent; + + public SurfaceContextImplLogic(SurfaceContextImpl surfaceParent) { + super(surfaceParent); + this.surfaceParent = surfaceParent; + } + + @Override + public Surface getSurface() { + return this.surfaceParent.surface; + } + + @Override + public Vec3i getMin() { + return this.surfaceParent.min; + } + + @Override + public Vec3i getMax() { + return this.surfaceParent.max; + } + + @Override + public SurfaceContextImpl data() { + return this.surfaceParent; + } + + @Override + public SurfaceTileContext.Logic push(Vec3i location) { + super.push(location); + return this; + } + + @Override + public SurfaceTileContext.Logic push(Vec3i location, RelFace face) { + super.push(location, face); + return this; + } + + @Override + public SurfaceTileContext.Logic push(Vec3i location, RelFace face, int layer) { + super.push(location, face, layer); + return this; + } + +} \ No newline at end of file diff --git a/src/main/java/ru/windcorp/progressia/server/world/generation/surface/context/SurfaceTileContext.java b/src/main/java/ru/windcorp/progressia/server/world/generation/surface/context/SurfaceTileContext.java new file mode 100644 index 0000000..ce28c6e --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/generation/surface/context/SurfaceTileContext.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.server.world.generation.surface.context; + +import ru.windcorp.progressia.server.world.context.ServerTileContext; + +public interface SurfaceTileContext extends ServerTileContext, SurfaceTileStackContext { + + public interface Logic extends ServerTileContext.Logic, SurfaceTileStackContext.Logic { + + @Override + SurfaceTileContext data(); + + @Override + default SurfaceTileContext.Logic pushCloser() { + return push(getLocation(), getFace(), getLayer() - 1); + } + + @Override + default SurfaceTileContext.Logic pushFarther() { + return push(getLocation(), getFace(), getLayer() + 1); + } + + } + + @Override + SurfaceTileContext.Logic logic(); + + @Override + default SurfaceTileContext pushCloser() { + return push(getLocation(), getFace(), getLayer() - 1); + } + + @Override + default SurfaceTileContext pushFarther() { + return push(getLocation(), getFace(), getLayer() + 1); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/generation/surface/context/SurfaceTileStackContext.java b/src/main/java/ru/windcorp/progressia/server/world/generation/surface/context/SurfaceTileStackContext.java new file mode 100644 index 0000000..d090e94 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/generation/surface/context/SurfaceTileStackContext.java @@ -0,0 +1,64 @@ +/* + * 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.server.world.generation.surface.context; + +import ru.windcorp.progressia.server.world.context.ServerTileStackContext; + +public interface SurfaceTileStackContext extends ServerTileStackContext, SurfaceBlockContext { + + public interface Logic extends ServerTileStackContext.Logic, SurfaceBlockContext.Logic { + + @Override + SurfaceTileStackContext data(); + + @Override + default SurfaceTileContext.Logic push(int layer) { + return push(getLocation(), getFace(), layer); + } + + @Override + default SurfaceTileStackContext.Logic pushCounter() { + return push(getFace().getCounter()); + } + + @Override + default SurfaceTileStackContext.Logic pushOpposite() { + return push(getLocation().add_(getFace().getRelVector()), getFace().getCounter()); + } + + } + + @Override + SurfaceTileStackContext.Logic logic(); + + @Override + default SurfaceTileContext push(int layer) { + return push(getLocation(), getFace(), layer); + } + + @Override + default SurfaceTileStackContext pushCounter() { + return push(getFace().getCounter()); + } + + @Override + default SurfaceTileStackContext pushOpposite() { + return push(getLocation().add_(getFace().getRelVector()), getFace().getCounter()); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/generation/surface/context/SurfaceWorldContext.java b/src/main/java/ru/windcorp/progressia/server/world/generation/surface/context/SurfaceWorldContext.java new file mode 100644 index 0000000..96a0922 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/generation/surface/context/SurfaceWorldContext.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.server.world.generation.surface.context; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.rels.RelFace; +import ru.windcorp.progressia.server.world.context.ServerWorldContext; + +public interface SurfaceWorldContext extends ServerWorldContext, SurfaceContext { + + public interface Logic extends ServerWorldContext.Logic, SurfaceContext { + + @Override + SurfaceWorldContext data(); + + @Override + SurfaceBlockContext.Logic push(Vec3i location); + + @Override + SurfaceTileStackContext.Logic push(Vec3i location, RelFace face); + + @Override + SurfaceTileContext.Logic push(Vec3i location, RelFace face, int layer); + + } + + @Override + SurfaceWorldContext.Logic logic(); + + @Override + SurfaceBlockContext push(Vec3i location); + + @Override + SurfaceTileStackContext push(Vec3i location, RelFace face); + + @Override + SurfaceTileContext push(Vec3i location, RelFace face, int layer); + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/tasks/BlockTriggeredUpdate.java b/src/main/java/ru/windcorp/progressia/server/world/tasks/BlockTriggeredUpdate.java index 6d8d408..f7c385b 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/tasks/BlockTriggeredUpdate.java +++ b/src/main/java/ru/windcorp/progressia/server/world/tasks/BlockTriggeredUpdate.java @@ -22,10 +22,9 @@ import java.util.function.Consumer; import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.common.world.Coordinates; -import ru.windcorp.progressia.common.world.block.BlockFace; +import ru.windcorp.progressia.common.world.rels.AbsFace; import ru.windcorp.progressia.server.Server; import ru.windcorp.progressia.server.world.TickAndUpdateUtil; -import ru.windcorp.progressia.server.world.WorldLogic; class BlockTriggeredUpdate extends CachedEvaluation { @@ -39,13 +38,11 @@ class BlockTriggeredUpdate extends CachedEvaluation { public void evaluate(Server server) { Vec3i cursor = new Vec3i(blockInWorld.x, blockInWorld.y, blockInWorld.z); - WorldLogic world = server.getWorld(); - - for (BlockFace face : BlockFace.getFaces()) { - TickAndUpdateUtil.updateTiles(world, cursor, face); + for (AbsFace face : AbsFace.getFaces()) { + TickAndUpdateUtil.updateTiles(server, cursor, face); cursor.add(face.getVector()); - TickAndUpdateUtil.updateBlock(world, cursor); - TickAndUpdateUtil.updateTiles(world, cursor, face.getCounter()); + TickAndUpdateUtil.updateBlock(server, cursor); + TickAndUpdateUtil.updateTiles(server, cursor, face.getCounter()); cursor.sub(face.getVector()); } } diff --git a/src/main/java/ru/windcorp/progressia/server/world/tasks/ChangeEntity.java b/src/main/java/ru/windcorp/progressia/server/world/tasks/ChangeEntity.java index a7a7072..f8c77ce 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/tasks/ChangeEntity.java +++ b/src/main/java/ru/windcorp/progressia/server/world/tasks/ChangeEntity.java @@ -21,6 +21,7 @@ package ru.windcorp.progressia.server.world.tasks; import java.util.function.Consumer; import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.state.StateChange; import ru.windcorp.progressia.common.world.entity.EntityData; import ru.windcorp.progressia.common.world.entity.PacketChangeEntity; import ru.windcorp.progressia.server.Server; @@ -36,7 +37,7 @@ class ChangeEntity extends CachedChange { super(disposer); } - public void set(T entity, StateChange change) { + public void set(EntityData entity, StateChange change) { if (this.entity != null) throw new IllegalStateException("Entity is not null. Current: " + this.entity + "; requested: " + entity); diff --git a/src/main/java/ru/windcorp/progressia/server/world/tasks/TickChunk.java b/src/main/java/ru/windcorp/progressia/server/world/tasks/TickChunk.java index b9dd457..a2a59f9 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/tasks/TickChunk.java +++ b/src/main/java/ru/windcorp/progressia/server/world/tasks/TickChunk.java @@ -27,110 +27,120 @@ import com.google.common.collect.ImmutableList; import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.common.util.FloatMathUtil; -import ru.windcorp.progressia.common.world.ChunkData; -import ru.windcorp.progressia.common.world.block.BlockFace; -import ru.windcorp.progressia.common.world.tile.TileDataStack; +import ru.windcorp.progressia.common.world.DefaultChunkData; +import ru.windcorp.progressia.common.world.rels.AbsFace; +import ru.windcorp.progressia.common.world.TileDataStack; import ru.windcorp.progressia.server.Server; -import ru.windcorp.progressia.server.world.ChunkLogic; -import ru.windcorp.progressia.server.world.TickContextMutable; +import ru.windcorp.progressia.server.world.DefaultChunkLogic; import ru.windcorp.progressia.server.world.block.BlockLogic; import ru.windcorp.progressia.server.world.block.TickableBlock; +import ru.windcorp.progressia.server.world.context.ServerBlockContext; +import ru.windcorp.progressia.server.world.context.ServerContexts; +import ru.windcorp.progressia.server.world.context.ServerTileContext; +import ru.windcorp.progressia.server.world.context.ServerTileStackContext; +import ru.windcorp.progressia.server.world.context.ServerWorldContext; import ru.windcorp.progressia.server.world.ticking.Evaluation; import ru.windcorp.progressia.server.world.ticking.TickingPolicy; -import ru.windcorp.progressia.server.world.tile.TSTickContext; import ru.windcorp.progressia.server.world.tile.TickableTile; import ru.windcorp.progressia.server.world.tile.TileLogic; -import static ru.windcorp.progressia.common.world.ChunkData.BLOCKS_PER_CHUNK; +import static ru.windcorp.progressia.common.world.DefaultChunkData.BLOCKS_PER_CHUNK; public class TickChunk extends Evaluation { - private static final int CHUNK_VOLUME = ChunkData.BLOCKS_PER_CHUNK * ChunkData.BLOCKS_PER_CHUNK - * ChunkData.BLOCKS_PER_CHUNK; + private static final int CHUNK_VOLUME = DefaultChunkData.BLOCKS_PER_CHUNK * + DefaultChunkData.BLOCKS_PER_CHUNK * + DefaultChunkData.BLOCKS_PER_CHUNK; - private final List> randomTickMethods; + private final List> randomTickMethods; { - List> randomTickMethods = new ArrayList<>(); + List> randomTickMethods = new ArrayList<>(); randomTickMethods.add(this::tickRandomBlock); - for (BlockFace face : BlockFace.getFaces()) { - randomTickMethods.add(s -> this.tickRandomTile(s, face)); + for (AbsFace face : AbsFace.getFaces()) { + randomTickMethods.add(context -> this.tickRandomTile(face, context)); } this.randomTickMethods = ImmutableList.copyOf(randomTickMethods); } - private final ChunkLogic chunk; + private final DefaultChunkLogic chunk; + private ServerWorldContext context; - public TickChunk(ChunkLogic chunk) { + public TickChunk(DefaultChunkLogic chunk) { this.chunk = chunk; } @Override public void evaluate(Server server) { - tickRegulars(server); - tickRandom(server); + if (context == null || context.getServer() != server) { + context = server.createContext(chunk.getUp()); + } + + tickRegulars(context); + tickRandom(context); } - private void tickRegulars(Server server) { - tickRegularBlocks(server); - tickRegularTiles(server); + private void tickRegulars(ServerWorldContext context) { + tickRegularBlocks(context); + tickRegularTiles(context); } - private void tickRegularBlocks(Server server) { + private void tickRegularBlocks(ServerWorldContext context) { if (!chunk.hasTickingBlocks()) return; - TickContextMutable context = TickContextMutable.uninitialized(); - chunk.forEachTickingBlock((blockInChunk, block) -> { - context.rebuild().withChunk(chunk).withBlockInChunk(blockInChunk).build(); - ((TickableBlock) block).tick(context); + ((TickableBlock) block).tick(ServerContexts.pushAbs(context, chunk, blockInChunk)); + context.pop(); }); } - private void tickRegularTiles(Server server) { + private void tickRegularTiles(ServerWorldContext context) { if (!chunk.hasTickingTiles()) return; - TickContextMutable context = TickContextMutable.uninitialized(); - chunk.forEachTickingTile((ref, tile) -> { - context.rebuild().withServer(server).withTile(ref); - ((TickableTile) tile).tick(context); + ((TickableTile) tile).tick(ServerContexts.pushAbs(context, chunk.getUp(), ref)); + context.pop(); }); } - private void tickRandom(Server server) { - float ticks = computeRandomTicks(server); + private void tickRandom(ServerWorldContext context) { + float ticks = computeRandomTicks(context.getServer()); /* - * If we are expected to run 3.25 random ticks per tick on average, then - * run 3 random ticks unconditionally and run one extra random tick with - * 0.25 chance + * If we are expected to run 3.25 random ticks per tick + * on average, then run 3 random ticks unconditionally + * and run one extra random tick with 0.25 chance */ float unconditionalTicks = FloatMathUtil.floor(ticks); float extraTickChance = ticks - unconditionalTicks; for (int i = 0; i < unconditionalTicks; ++i) { - tickRandomOnce(server); + tickRandomOnce(context); } - if (server.getAdHocRandom().nextFloat() < extraTickChance) { - tickRandomOnce(server); + if (context.getRandom().nextFloat() < extraTickChance) { + tickRandomOnce(context); } } - private void tickRandomOnce(Server server) { + private void tickRandomOnce(ServerWorldContext context) { // Pick a target at random: a block or one of 3 primary block faces - randomTickMethods.get(server.getAdHocRandom().nextInt(randomTickMethods.size())).accept(server); + randomTickMethods.get( + context.getRandom().nextInt(randomTickMethods.size()) + ).accept(context); } - private void tickRandomBlock(Server server) { - Random random = server.getAdHocRandom(); + private void tickRandomBlock(ServerWorldContext context) { + Random random = context.getRandom(); - Vec3i blockInChunk = new Vec3i(random.nextInt(BLOCKS_PER_CHUNK), random.nextInt(BLOCKS_PER_CHUNK), - random.nextInt(BLOCKS_PER_CHUNK)); + Vec3i blockInChunk = new Vec3i( + random.nextInt(BLOCKS_PER_CHUNK), + random.nextInt(BLOCKS_PER_CHUNK), + random.nextInt(BLOCKS_PER_CHUNK) + ); BlockLogic block = this.chunk.getBlock(blockInChunk); @@ -138,40 +148,56 @@ public class TickChunk extends Evaluation { return; TickableBlock tickable = (TickableBlock) block; - TickContextMutable context = TickContextMutable.start().withChunk(chunk).withBlockInChunk(blockInChunk).build(); + ServerBlockContext blockContext = ServerContexts.pushAbs(context, chunk, blockInChunk); - if (tickable.getTickingPolicy(context) != TickingPolicy.RANDOM) + if (tickable.getTickingPolicy(blockContext) != TickingPolicy.RANDOM) return; - tickable.tick(context); + tickable.tick(blockContext); + + blockContext.pop(); } - private void tickRandomTile(Server server, BlockFace face) { - Random random = server.getAdHocRandom(); + private void tickRandomTile(AbsFace face, ServerWorldContext context) { + Random random = context.getRandom(); - Vec3i blockInChunk = new Vec3i(random.nextInt(BLOCKS_PER_CHUNK), random.nextInt(BLOCKS_PER_CHUNK), - random.nextInt(BLOCKS_PER_CHUNK)); + Vec3i blockInChunk = new Vec3i( + random.nextInt(BLOCKS_PER_CHUNK), + random.nextInt(BLOCKS_PER_CHUNK), + random.nextInt(BLOCKS_PER_CHUNK) + ); TileDataStack tiles = this.chunk.getData().getTilesOrNull(blockInChunk, face); if (tiles == null || tiles.isEmpty()) return; - TSTickContext context = TickContextMutable.start().withServer(server).withTS(tiles).build(); + ServerTileStackContext tsContext = ServerContexts.pushAbs(context, chunk, blockInChunk, face); - context.forEachTile(tctxt -> { - TileLogic logic = tctxt.getTile(); - if (!(logic instanceof TickableTile)) - return; + for (int i = 0; i < tiles.size(); ++i) { + ServerTileContext tileContext = tsContext.push(i); + + TileLogic logic = tileContext.logic().getTile(); + if (!(logic instanceof TickableTile)) { + tileContext.pop(); + continue; + } TickableTile tickable = (TickableTile) logic; - if (tickable.getTickingPolicy(tctxt) != TickingPolicy.RANDOM) - return; - tickable.tick(tctxt); - }); + if (tickable.getTickingPolicy(tileContext) != TickingPolicy.RANDOM) { + tileContext.pop(); + continue; + } + tickable.tick(tileContext); + + tileContext.pop(); + } + + tsContext.pop(); } private float computeRandomTicks(Server server) { - return (float) (server.getTickingSettings().getRandomTickFrequency() * CHUNK_VOLUME * randomTickMethods.size() - * server.getTickLength()); + return (float) (server.getTickingSettings().getRandomTickFrequency() * + CHUNK_VOLUME * randomTickMethods.size() * + server.getTickLength()); } @Override diff --git a/src/main/java/ru/windcorp/progressia/server/world/tasks/TileTriggeredUpdate.java b/src/main/java/ru/windcorp/progressia/server/world/tasks/TileTriggeredUpdate.java index 80b91bd..3635250 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/tasks/TileTriggeredUpdate.java +++ b/src/main/java/ru/windcorp/progressia/server/world/tasks/TileTriggeredUpdate.java @@ -22,15 +22,14 @@ import java.util.function.Consumer; import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.common.world.Coordinates; -import ru.windcorp.progressia.common.world.block.BlockFace; +import ru.windcorp.progressia.common.world.rels.AbsFace; import ru.windcorp.progressia.server.Server; import ru.windcorp.progressia.server.world.TickAndUpdateUtil; -import ru.windcorp.progressia.server.world.WorldLogic; class TileTriggeredUpdate extends CachedEvaluation { private final Vec3i blockInWorld = new Vec3i(); - private BlockFace face = null; + private AbsFace face = null; public TileTriggeredUpdate(Consumer disposer) { super(disposer); @@ -40,20 +39,18 @@ class TileTriggeredUpdate extends CachedEvaluation { public void evaluate(Server server) { Vec3i cursor = new Vec3i(blockInWorld.x, blockInWorld.y, blockInWorld.z); - WorldLogic world = server.getWorld(); - - TickAndUpdateUtil.updateTiles(world, cursor, face); // Update facemates - // (also self) - TickAndUpdateUtil.updateBlock(world, cursor); // Update block on one - // side + // Update facemates (also self) + TickAndUpdateUtil.updateTiles(server, cursor, face); + // Update block on one side + TickAndUpdateUtil.updateBlock(server, cursor); cursor.add(face.getVector()); - TickAndUpdateUtil.updateBlock(world, cursor); // Update block on the - // other side - TickAndUpdateUtil.updateTiles(world, cursor, face.getCounter()); // Update - // complement + // Update block on the other side + TickAndUpdateUtil.updateBlock(server, cursor); + // Update complement + TickAndUpdateUtil.updateTiles(server, cursor, face.getCounter()); } - public void init(Vec3i blockInWorld, BlockFace face) { + public void init(Vec3i blockInWorld, AbsFace face) { this.blockInWorld.set(blockInWorld.x, blockInWorld.y, blockInWorld.z); this.face = face; } diff --git a/src/main/java/ru/windcorp/progressia/server/world/tasks/WorldAccessor.java b/src/main/java/ru/windcorp/progressia/server/world/tasks/WorldAccessor.java index 28d91a4..76ab835 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/tasks/WorldAccessor.java +++ b/src/main/java/ru/windcorp/progressia/server/world/tasks/WorldAccessor.java @@ -15,36 +15,41 @@ * 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; import java.util.function.Consumer; import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.state.StateChange; +import ru.windcorp.progressia.common.state.StatefulObject; import ru.windcorp.progressia.common.util.MultiLOC; import ru.windcorp.progressia.common.world.block.BlockData; -import ru.windcorp.progressia.common.world.block.BlockDataRegistry; -import ru.windcorp.progressia.common.world.block.BlockFace; import ru.windcorp.progressia.common.world.entity.EntityData; +import ru.windcorp.progressia.common.world.generic.EntityGeneric; +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.TileDataRegistry; import ru.windcorp.progressia.server.Server; +import ru.windcorp.progressia.server.world.context.impl.ReportingServerContext; import ru.windcorp.progressia.server.world.ticking.TickerTask; -public class WorldAccessor { +public class WorldAccessor implements ReportingServerContext.ChangeListener { private final MultiLOC cache; { MultiLOC mloc = new MultiLOC(); Consumer disposer = mloc::release; - cache = mloc.addClass(SetBlock.class, () -> new SetBlock(disposer)) - .addClass(AddTile.class, () -> new AddTile(disposer)) - .addClass(RemoveTile.class, () -> new RemoveTile(disposer)) - .addClass(ChangeEntity.class, () -> new ChangeEntity(disposer)) + cache = mloc + .addClass(SetBlock.class, () -> new SetBlock(disposer)) + .addClass(AddTile.class, () -> new AddTile(disposer)) + .addClass(RemoveTile.class, () -> new RemoveTile(disposer)) + .addClass(ChangeEntity.class, () -> new ChangeEntity(disposer)) - .addClass(BlockTriggeredUpdate.class, () -> new BlockTriggeredUpdate(disposer)) - .addClass(TileTriggeredUpdate.class, () -> new TileTriggeredUpdate(disposer)); + .addClass(BlockTriggeredUpdate.class, () -> new BlockTriggeredUpdate(disposer)) + .addClass(TileTriggeredUpdate.class, () -> new TileTriggeredUpdate(disposer)); } private final Server server; @@ -53,40 +58,58 @@ public class WorldAccessor { this.server = server; } - public void setBlock(Vec3i blockInWorld, BlockData block) { + @Override + public void onBlockSet(Vec3i blockInWorld, BlockData block) { SetBlock change = cache.grab(SetBlock.class); change.getPacket().set(block, blockInWorld); server.requestChange(change); } - public void setBlock(Vec3i blockInWorld, String id) { - setBlock(blockInWorld, BlockDataRegistry.getInstance().get(id)); - } - - public void addTile(Vec3i blockInWorld, BlockFace face, TileData tile) { + @Override + public void onTileAdded(Vec3i blockInWorld, RelFace face, TileData tile) { AddTile change = cache.grab(AddTile.class); - change.getPacket().set(tile, blockInWorld, face); + change.getPacket().set(tile, blockInWorld, face.resolve(AbsFace.POS_Z)); server.requestChange(change); } - public void addTile(Vec3i blockInWorld, BlockFace face, String id) { - addTile(blockInWorld, face, TileDataRegistry.getInstance().get(id)); - } - - public void removeTile(Vec3i blockInWorld, BlockFace face, int tag) { + @Override + public void onTileRemoved(Vec3i blockInWorld, RelFace face, int tag) { RemoveTile change = cache.grab(RemoveTile.class); - change.getPacket().set(blockInWorld, face, tag); + change.getPacket().set(blockInWorld, face.resolve(AbsFace.POS_Z), tag); server.requestChange(change); } + + @Override + public void onEntityAdded(EntityData entity) { + // TODO Auto-generated method stub + + } + + @Override + public void onEntityRemoved(long entityId) { + // TODO Auto-generated method stub + + } + + @Override + public void onTimeChanged(float change) { + // TODO Auto-generated method stub + System.err.println("WorldAccessor.onTimeChanged() NYI!"); + } - public void changeEntity(T entity, StateChange stateChange) { + @Override + public void onEntityChanged( + SE entity, + StateChange stateChange + ) { ChangeEntity change = cache.grab(ChangeEntity.class); - change.set(entity, stateChange); + change.set((EntityData) entity, stateChange); server.requestChange(change); } public void tickBlock(Vec3i blockInWorld) { // TODO + System.err.println("WorldAccessor.tickBlock(Vec3i) NYI!"); } /** @@ -110,7 +133,7 @@ public class WorldAccessor { // TODO rename to something meaningful public void triggerUpdates(Vec3i blockInWorld, BlockFace face) { TileTriggeredUpdate evaluation = cache.grab(TileTriggeredUpdate.class); - evaluation.init(blockInWorld, face); + evaluation.init(blockInWorld, face.resolve(AbsFace.POS_Z)); server.requestEvaluation(evaluation); } diff --git a/src/main/java/ru/windcorp/progressia/server/world/ticking/TickerCoordinator.java b/src/main/java/ru/windcorp/progressia/server/world/ticking/TickerCoordinator.java index d4a9bba..aaea0ea 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/ticking/TickerCoordinator.java +++ b/src/main/java/ru/windcorp/progressia/server/world/ticking/TickerCoordinator.java @@ -23,8 +23,8 @@ import java.util.Collection; import java.util.ConcurrentModificationException; import java.util.HashSet; import java.util.Objects; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -34,7 +34,7 @@ import com.google.common.collect.ImmutableList; import ru.windcorp.progressia.common.Units; import ru.windcorp.progressia.common.util.crash.CrashReports; -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.ChunkDataListeners; import ru.windcorp.progressia.server.Server; @@ -46,6 +46,12 @@ import ru.windcorp.progressia.server.Server; * @author javapony */ public class TickerCoordinator { + + public enum TickPhase { + SYNCHRONOUS, + EVALUATION, + CHANGE + } static final int INITIAL_QUEUE_SIZE = 1024; @@ -75,7 +81,7 @@ public class TickerCoordinator { private final AtomicInteger workingTickers = new AtomicInteger(); - private final AtomicBoolean canChange = new AtomicBoolean(true); + private final AtomicReference phase = new AtomicReference<>(TickPhase.SYNCHRONOUS); private boolean isTickStartSet = false; private long tickStart = -1; @@ -94,17 +100,14 @@ public class TickerCoordinator { } this.tickers = ImmutableList.copyOf(tickerCollection); - this.threads = Collections2.transform(this.tickers, Ticker::getThread); // Immutable - // because - // it - // is - // a - // view + + // Immutable because it is a view + this.threads = Collections2.transform(this.tickers, Ticker::getThread); server.getWorld().getData().addListener(ChunkDataListeners.createAdder(new ChunkDataListener() { @Override - public void onChunkChanged(ChunkData chunk) { - if (!canChange.get()) { + public void onChunkChanged(DefaultChunkData chunk) { + if (phase.get() == TickPhase.EVALUATION) { throw CrashReports.report(null, "A change has been detected during evaluation phase"); } } @@ -154,8 +157,14 @@ public class TickerCoordinator { public long getUptimeTicks() { return ticks; } + + public TickPhase getPhase() { + return phase.get(); + } private void onTickStart() { + phase.set(TickPhase.EVALUATION); + long now = System.currentTimeMillis(); if (isTickStartSet) { @@ -169,6 +178,7 @@ public class TickerCoordinator { private void onTickEnd() { ticks++; + phase.set(TickPhase.SYNCHRONOUS); } /* @@ -210,9 +220,9 @@ public class TickerCoordinator { } private synchronized void runOnePass() throws InterruptedException { - canChange.set(false); + phase.set(TickPhase.EVALUATION); runPassStage(pendingEvaluations, "EVALUATION"); - canChange.set(true); + phase.set(TickPhase.CHANGE); runPassStage(pendingChanges, "CHANGE"); } diff --git a/src/main/java/ru/windcorp/progressia/server/world/tile/HangingTileLogic.java b/src/main/java/ru/windcorp/progressia/server/world/tile/HangingTileLogic.java index fdd3a53..55f8ec7 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/tile/HangingTileLogic.java +++ b/src/main/java/ru/windcorp/progressia/server/world/tile/HangingTileLogic.java @@ -19,6 +19,8 @@ package ru.windcorp.progressia.server.world.tile; import ru.windcorp.progressia.server.world.block.BlockLogic; +import ru.windcorp.progressia.server.world.context.ServerTileContext; +import ru.windcorp.progressia.server.world.context.ServerTileContextRO; public class HangingTileLogic extends TileLogic implements UpdateableTile { @@ -27,15 +29,15 @@ public class HangingTileLogic extends TileLogic implements UpdateableTile { } @Override - public void update(TileTickContext context) { + public void update(ServerTileContext context) { if (!canOccupyFace(context)) { - context.removeThisTile(); + context.removeTile(); } } @Override - public boolean canOccupyFace(TileTickContext context) { - BlockLogic host = context.getBlock(); + public boolean canOccupyFace(ServerTileContextRO context) { + BlockLogic host = context.logic().getBlock(); if (host == null) return false; @@ -45,13 +47,12 @@ public class HangingTileLogic extends TileLogic implements UpdateableTile { if (canBeSquashed(context)) return true; - return context.evalComplementary(ctxt -> { - BlockLogic complHost = ctxt.getBlock(); - return complHost == null || !complHost.isSolid(ctxt, context.getFace()); - }); + context.pushOpposite(); + BlockLogic complHost = context.logic().getBlock(); + return context.popAndReturn(complHost == null || !complHost.isSolid(context, context.getFace())); } - public boolean canBeSquashed(TileTickContext context) { + public boolean canBeSquashed(ServerTileContextRO context) { return false; } diff --git a/src/main/java/ru/windcorp/progressia/server/world/tile/TSTickContext.java b/src/main/java/ru/windcorp/progressia/server/world/tile/TSTickContext.java deleted file mode 100644 index 0e746ca..0000000 --- a/src/main/java/ru/windcorp/progressia/server/world/tile/TSTickContext.java +++ /dev/null @@ -1,110 +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.server.world.tile; - -import java.util.Objects; -import java.util.function.Consumer; -import java.util.function.Function; - -import ru.windcorp.progressia.common.world.ChunkData; -import ru.windcorp.progressia.common.world.block.BlockFace; -import ru.windcorp.progressia.common.world.tile.TileDataStack; -import ru.windcorp.progressia.server.world.ChunkLogic; -import ru.windcorp.progressia.server.world.TickContextMutable; -import ru.windcorp.progressia.server.world.block.BlockTickContext; - -public interface TSTickContext extends BlockTickContext { - - /* - * Specifications - */ - - BlockFace getFace(); - - /* - * Getters - */ - - default TileLogicStack getTLSOrNull() { - ChunkLogic chunkLogic = getChunkLogic(); - if (chunkLogic == null) - return null; - - return chunkLogic.getTilesOrNull(getBlockInChunk(), getFace()); - } - - default TileLogicStack getTLS() { - return getChunkLogic().getTiles(getBlockInChunk(), getFace()); - } - - default TileDataStack getTDSOrNull() { - ChunkData chunkData = getChunkData(); - if (chunkData == null) - return null; - - return chunkData.getTilesOrNull(getBlockInChunk(), getFace()); - } - - default TileDataStack getTDS() { - return getChunkData().getTiles(getBlockInChunk(), getFace()); - } - - /* - * Contexts - */ - - default TileTickContext forLayer(int layer) { - return TickContextMutable.start().withServer(getServer()).withBlock(getBlockInWorld()).withFace(getFace()) - .withLayer(layer); - } - - default boolean forEachTile(Consumer action) { - TickContextMutable context = TickContextMutable.uninitialized(); - - TileDataStack stack = getTDSOrNull(); - if (stack == null || stack.isEmpty()) - return false; - - for (int layer = 0; layer < stack.size(); ++layer) { - context.rebuild().withServer(getServer()).withBlock(getBlockInWorld()).withFace(getFace()).withLayer(layer); - action.accept(context); - } - - return true; - } - - default TSTickContext getComplementary() { - return TickContextMutable.copyWorld(this).withBlock(getBlockInWorld().add_(getFace().getVector())) - .withFace(getFace().getCounter()).build(); - } - - default R evalComplementary(Function action) { - Objects.requireNonNull(action, "action"); - return action.apply(getComplementary()); - } - - default void forComplementary(Consumer action) { - Objects.requireNonNull(action, "action"); - evalComplementary((Function) ctxt -> { - action.accept(ctxt); - return null; - }); - } - -} diff --git a/src/main/java/ru/windcorp/progressia/server/world/tile/TickableTile.java b/src/main/java/ru/windcorp/progressia/server/world/tile/TickableTile.java index 50a4b15..60d2031 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/tile/TickableTile.java +++ b/src/main/java/ru/windcorp/progressia/server/world/tile/TickableTile.java @@ -18,12 +18,14 @@ package ru.windcorp.progressia.server.world.tile; +import ru.windcorp.progressia.server.world.context.ServerTileContext; +import ru.windcorp.progressia.server.world.context.ServerTileContextRO; import ru.windcorp.progressia.server.world.ticking.TickingPolicy; public interface TickableTile { - void tick(TileTickContext context); + void tick(ServerTileContext context); - TickingPolicy getTickingPolicy(TileTickContext context); + TickingPolicy getTickingPolicy(ServerTileContextRO context); } diff --git a/src/main/java/ru/windcorp/progressia/server/world/tile/TileLogic.java b/src/main/java/ru/windcorp/progressia/server/world/tile/TileLogic.java index c623999..9a9ff8f 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/tile/TileLogic.java +++ b/src/main/java/ru/windcorp/progressia/server/world/tile/TileLogic.java @@ -19,24 +19,25 @@ package ru.windcorp.progressia.server.world.tile; import ru.windcorp.progressia.common.util.namespaces.Namespaced; -import ru.windcorp.progressia.common.world.block.BlockFace; -import ru.windcorp.progressia.common.world.generic.GenericTile; +import ru.windcorp.progressia.common.world.generic.TileGeneric; +import ru.windcorp.progressia.common.world.rels.RelFace; +import ru.windcorp.progressia.server.world.context.ServerTileContextRO; -public class TileLogic extends Namespaced implements GenericTile { +public class TileLogic extends Namespaced implements TileGeneric { public TileLogic(String id) { super(id); } - public boolean canOccupyFace(TileTickContext context) { + public boolean canOccupyFace(ServerTileContextRO context) { return canOccupyFace(context.getFace()); } - public boolean canOccupyFace(BlockFace face) { + public boolean canOccupyFace(RelFace face) { return true; } - public boolean isSolid(TileTickContext context) { + public boolean isSolid(ServerTileContextRO context) { return isSolid(); } diff --git a/src/main/java/ru/windcorp/progressia/server/world/tile/TileTickContext.java b/src/main/java/ru/windcorp/progressia/server/world/tile/TileTickContext.java deleted file mode 100644 index 69dc77d..0000000 --- a/src/main/java/ru/windcorp/progressia/server/world/tile/TileTickContext.java +++ /dev/null @@ -1,76 +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.server.world.tile; - -import ru.windcorp.progressia.common.world.tile.TileData; -import ru.windcorp.progressia.common.world.tile.TileDataStack; -import ru.windcorp.progressia.common.world.tile.TileReference; - -public interface TileTickContext extends TSTickContext { - - /* - * Specifications - */ - - /** - * Returns the current layer. - * - * @return the layer that the tile being ticked occupies in the tile stack - */ - int getLayer(); - - /* - * Getters - */ - - default TileLogic getTile() { - TileLogicStack stack = getTLSOrNull(); - if (stack == null) - return null; - return stack.get(getLayer()); - } - - default TileData getTileData() { - TileDataStack stack = getTDSOrNull(); - if (stack == null) - return null; - return stack.get(getLayer()); - } - - default TileReference getReference() { - return getTDS().getReference(getLayer()); - } - - default int getTag() { - return getTDS().getTagByIndex(getLayer()); - } - - /* - * Contexts - */ - - /* - * Convenience methods - changes - */ - - default void removeThisTile() { - getAccessor().removeTile(getBlockInWorld(), getFace(), getTag()); - } - -} diff --git a/src/main/java/ru/windcorp/progressia/server/world/tile/UpdateableTile.java b/src/main/java/ru/windcorp/progressia/server/world/tile/UpdateableTile.java index 0a5aca5..c1baadf 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/tile/UpdateableTile.java +++ b/src/main/java/ru/windcorp/progressia/server/world/tile/UpdateableTile.java @@ -18,8 +18,10 @@ package ru.windcorp.progressia.server.world.tile; +import ru.windcorp.progressia.server.world.context.ServerTileContext; + public interface UpdateableTile { - void update(TileTickContext context); + void update(ServerTileContext context); } diff --git a/src/main/java/ru/windcorp/progressia/test/ControlPlaceTileData.java b/src/main/java/ru/windcorp/progressia/test/ControlPlaceTileData.java index 5835d7b..5fd8764 100644 --- a/src/main/java/ru/windcorp/progressia/test/ControlPlaceTileData.java +++ b/src/main/java/ru/windcorp/progressia/test/ControlPlaceTileData.java @@ -20,14 +20,14 @@ package ru.windcorp.progressia.test; import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.common.comms.controls.ControlData; -import ru.windcorp.progressia.common.world.block.BlockFace; +import ru.windcorp.progressia.common.world.rels.AbsFace; import ru.windcorp.progressia.common.world.tile.TileData; public class ControlPlaceTileData extends ControlData { private TileData tile; private final Vec3i blockInWorld = new Vec3i(); - private BlockFace face; + private AbsFace face; public ControlPlaceTileData(String id) { super(id); @@ -41,11 +41,11 @@ public class ControlPlaceTileData extends ControlData { return blockInWorld; } - public BlockFace getFace() { + public AbsFace getFace() { return face; } - public void set(TileData block, Vec3i blockInWorld, BlockFace face) { + public void set(TileData block, Vec3i blockInWorld, AbsFace face) { this.tile = block; this.blockInWorld.set(blockInWorld.x, blockInWorld.y, blockInWorld.z); this.face = face; diff --git a/src/main/java/ru/windcorp/progressia/test/DebugGraphics.java b/src/main/java/ru/windcorp/progressia/test/DebugGraphics.java new file mode 100644 index 0000000..859132f --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/DebugGraphics.java @@ -0,0 +1,95 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ru.windcorp.progressia.test; + +import glm.mat._4.Mat4; +import glm.vec._3.Vec3; +import glm.vec._4.Vec4; +import ru.windcorp.progressia.client.graphics.Colors; +import ru.windcorp.progressia.client.graphics.model.Renderable; +import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper; +import ru.windcorp.progressia.client.graphics.model.Shapes; +import ru.windcorp.progressia.client.graphics.model.StaticModel; +import ru.windcorp.progressia.client.graphics.texture.Texture; +import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram; +import ru.windcorp.progressia.common.util.Vectors; + +public class DebugGraphics { + + private static final float TAIL_THICKNESS = 0.03f; + private static final float HEAD_SIZE = 0.1f; + + private static final Renderable THE_VECTOR = StaticModel.builder().addPart( + new Shapes.PppBuilder(WorldRenderProgram.getDefault(), (Texture) null) + .setSize(1.0f, TAIL_THICKNESS, TAIL_THICKNESS) + .setOrigin(0, -TAIL_THICKNESS / 2, -TAIL_THICKNESS / 2) + .create() + ).addPart( + new Shapes.PppBuilder(WorldRenderProgram.getDefault(), (Texture) null) + .setSize(HEAD_SIZE, HEAD_SIZE, HEAD_SIZE) + .setOrigin((1 - HEAD_SIZE / 2), -HEAD_SIZE / 2, -HEAD_SIZE / 2) + .create() + ).build(); + + public static void drawVector(Vec3 vector, Vec4 color, Vec3 origin, float scale, ShapeRenderHelper renderer) { + float length = vector.length(); + if (length == 0) return; + + if (scale == 0) scale = 1 / length; + + Mat4 mat = renderer.pushTransform(); + + mat.translate(origin); + + Vec3 somePerpendicular = new Vec3(); + + if (Math.abs(vector.z) > (1 - 1e-4f) * length) { + somePerpendicular.set(1, 0, 0); + } else { + somePerpendicular.set(0, 0, 1); + } + + Vec3 f = vector; + Vec3 s = somePerpendicular.cross_(f).normalize(); + Vec3 u = somePerpendicular.set(f).cross(s).normalize(); + + // @formatter:off + mat.mul(new Mat4( + +f.x * scale, +f.y * scale, +f.z * scale, 0, + -s.x, -s.y, -s.z, 0, + +u.x, +u.y, +u.z, 0, + 0, 0, 0, 1 + )); + // @formatter:on + + renderer.pushColorMultiplier().mul(color); + THE_VECTOR.render(renderer); + renderer.popColorMultiplier(); + + renderer.popTransform(); + } + + public static void drawVector(Vec3 vector, ShapeRenderHelper renderer) { + drawVector(vector, Colors.GRAY_A, Vectors.ZERO_3, 1, renderer); + } + + public static void drawDirection(Vec3 vector, ShapeRenderHelper renderer) { + drawVector(vector, Colors.GRAY_A, Vectors.ZERO_3, 0, renderer); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/LayerAbout.java b/src/main/java/ru/windcorp/progressia/test/LayerAbout.java index 20260b0..334b462 100644 --- a/src/main/java/ru/windcorp/progressia/test/LayerAbout.java +++ b/src/main/java/ru/windcorp/progressia/test/LayerAbout.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.test; import ru.windcorp.progressia.client.graphics.Colors; @@ -23,7 +23,7 @@ import ru.windcorp.progressia.client.graphics.font.Font; import ru.windcorp.progressia.client.graphics.font.Typeface; import ru.windcorp.progressia.client.graphics.gui.GUILayer; import ru.windcorp.progressia.client.graphics.gui.Label; -import ru.windcorp.progressia.client.graphics.gui.Panel; +import ru.windcorp.progressia.client.graphics.gui.Group; import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign; import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical; import ru.windcorp.progressia.client.localization.MutableStringLocalized; @@ -33,19 +33,36 @@ public class LayerAbout extends GUILayer { public LayerAbout() { super("LayerAbout", new LayoutAlign(1, 1, 5)); - Panel panel = new Panel("ControlDisplays", new LayoutVertical(5)); + Group group = new Group("ControlDisplays", new LayoutVertical(5)); Font font = new Font().withColor(Colors.WHITE).deriveOutlined().withAlign(Typeface.ALIGN_RIGHT); Font aboutFont = font.withColor(0xFF37A3E6).deriveBold(); - panel.addChild(new Label("About", aboutFont, new MutableStringLocalized("LayerAbout.Title"))); + group.addChild( + new Label( + "About", + aboutFont, + new MutableStringLocalized("LayerAbout.Title") + ) + ); - panel.addChild( - new Label("Version", font, new MutableStringLocalized("LayerAbout.Version").format("pre-alpha 1"))); + group.addChild( + new Label( + "Version", + font, + new MutableStringLocalized("LayerAbout.Version").format("pre-alpha 2") + ) + ); - panel.addChild(new Label("DebugHint", font, new MutableStringLocalized("LayerAbout.DebugHint"))); + group.addChild( + new Label( + "DebugHint", + font, + new MutableStringLocalized("LayerAbout.DebugHint") + ) + ); - getRoot().addChild(panel); + getRoot().addChild(group); } diff --git a/src/main/java/ru/windcorp/progressia/test/LayerButtonTest.java b/src/main/java/ru/windcorp/progressia/test/LayerButtonTest.java new file mode 100644 index 0000000..e505291 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/LayerButtonTest.java @@ -0,0 +1,70 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ru.windcorp.progressia.test; + +import ru.windcorp.progressia.client.graphics.Colors; +import ru.windcorp.progressia.client.graphics.font.Font; +import ru.windcorp.progressia.client.graphics.gui.Button; +import ru.windcorp.progressia.client.graphics.gui.Checkbox; +import ru.windcorp.progressia.client.graphics.gui.Label; +import ru.windcorp.progressia.client.graphics.gui.RadioButton; +import ru.windcorp.progressia.client.graphics.gui.RadioButtonGroup; +import ru.windcorp.progressia.client.graphics.gui.menu.MenuLayer; + +public class LayerButtonTest extends MenuLayer { + + public LayerButtonTest() { + super("ButtonTest"); + + addTitle(); + + Button blockableButton; + getContent().addChild((blockableButton = new Button("BlockableButton", "Blockable")).addAction(b -> { + System.out.println("Button Blockable!"); + })); + blockableButton.setEnabled(false); + + getContent().addChild(new Checkbox("EnableButton", "Enable").addAction(b -> { + blockableButton.setEnabled(((Checkbox) b).isChecked()); + })); + + RadioButtonGroup group = new RadioButtonGroup().addAction(g -> { + System.out.println("RBG! " + g.getSelected().getLabel().getCurrentText()); + }); + + getContent().addChild(new RadioButton("RB1", "Moon").setGroup(group)); + getContent().addChild(new RadioButton("RB2", "Type").setGroup(group)); + getContent().addChild(new RadioButton("RB3", "Ice").setGroup(group)); + getContent().addChild(new RadioButton("RB4", "Cream").setGroup(group)); + + getContent().getChild(getContent().getChildren().size() - 1).setEnabled(false); + + getContent().addChild(new Label("Hint", new Font().withColor(Colors.LIGHT_GRAY), "This is a MenuLayer")); + + getContent().addChild(new Button("Continue", "Continue").addAction(b -> { + getCloseAction().run(); + })); + + getContent().addChild(new Button("Quit", "Quit").addAction(b -> { + System.exit(0); + })); + + getContent().takeFocus(); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/LayerTestGUI.java b/src/main/java/ru/windcorp/progressia/test/LayerTestGUI.java index db42176..65913d2 100755 --- a/src/main/java/ru/windcorp/progressia/test/LayerTestGUI.java +++ b/src/main/java/ru/windcorp/progressia/test/LayerTestGUI.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.test; import glm.vec._3.Vec3; @@ -29,12 +29,13 @@ import ru.windcorp.progressia.client.graphics.font.Font; import ru.windcorp.progressia.client.graphics.gui.DynamicLabel; import ru.windcorp.progressia.client.graphics.gui.GUILayer; import ru.windcorp.progressia.client.graphics.gui.Label; -import ru.windcorp.progressia.client.graphics.gui.Panel; +import ru.windcorp.progressia.client.graphics.gui.Group; import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign; import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical; import ru.windcorp.progressia.client.localization.Localizer; import ru.windcorp.progressia.client.localization.MutableString; import ru.windcorp.progressia.client.localization.MutableStringLocalized; +import ru.windcorp.progressia.client.world.WorldRender; import ru.windcorp.progressia.common.Units; import ru.windcorp.progressia.common.util.dynstr.DynamicStrings; import ru.windcorp.progressia.server.Server; @@ -50,61 +51,160 @@ public class LayerTestGUI extends GUILayer { public LayerTestGUI() { super("LayerTestGui", new LayoutAlign(0, 1, 5)); - Panel panel = new Panel("ControlDisplays", new LayoutVertical(5)); + Group group = new Group("ControlDisplays", new LayoutVertical(5)); Vec4 color = Colors.WHITE; Font font = new Font().withColor(color).deriveOutlined(); TestPlayerControls tpc = TestPlayerControls.getInstance(); - panel.addChild( - new Label("IsFlyingDisplay", font, tmp_dynFormat("LayerTestGUI.IsFlyingDisplay", tpc::isFlying))); + group.addChild( + new Label( + "IsFlyingDisplay", + font, + tmp_dynFormat("LayerTestGUI.IsFlyingDisplay", tpc::isFlying) + ) + ); - panel.addChild(new Label("IsSprintingDisplay", font, - tmp_dynFormat("LayerTestGUI.IsSprintingDisplay", tpc::isSprinting))); + group.addChild( + new Label( + "IsSprintingDisplay", + font, + tmp_dynFormat("LayerTestGUI.IsSprintingDisplay", tpc::isSprinting) + ) + ); - panel.addChild(new Label("IsMouseCapturedDisplay", font, - tmp_dynFormat("LayerTestGUI.IsMouseCapturedDisplay", tpc::isMouseCaptured))); + group.addChild( + new Label( + "CameraModeDisplay", + font, + tmp_dynFormat( + "LayerTestGUI.CameraModeDisplay", + ClientState.getInstance().getCamera()::getCurrentModeIndex + ) + ) + ); - panel.addChild(new Label("CameraModeDisplay", font, tmp_dynFormat("LayerTestGUI.CameraModeDisplay", - ClientState.getInstance().getCamera()::getCurrentModeIndex))); + group.addChild( + new Label( + "LanguageDisplay", + font, + tmp_dynFormat("LayerTestGUI.LanguageDisplay", Localizer.getInstance()::getLanguage) + ) + ); - panel.addChild(new Label("GravityModeDisplay", font, tmp_dynFormat("LayerTestGUI.GravityModeDisplay", - () -> tpc.useMinecraftGravity() ? "Minecraft" : "Realistic"))); + group.addChild( + new Label( + "FullscreenDisplay", + font, + tmp_dynFormat("LayerTestGUI.IsFullscreen", GraphicsBackend::isFullscreen) + ) + ); - panel.addChild(new Label("LanguageDisplay", font, - tmp_dynFormat("LayerTestGUI.LanguageDisplay", Localizer.getInstance()::getLanguage))); + group.addChild( + new Label( + "VSyncDisplay", + font, + tmp_dynFormat("LayerTestGUI.IsVSync", GraphicsBackend::isVSyncEnabled) + ) + ); - panel.addChild(new Label("FullscreenDisplay", font, - tmp_dynFormat("LayerTestGUI.IsFullscreen", GraphicsBackend::isFullscreen))); + group.addChild( + new DynamicLabel( + "FPSDisplay", + font, + DynamicStrings.builder() + .addDyn(new MutableStringLocalized("LayerTestGUI.FPSDisplay")) + .addDyn(() -> FPS_RECORD.update(GraphicsInterface.getFPS()), 5, 1) + .buildSupplier(), + 128 + ) + ); - panel.addChild(new Label("VSyncDisplay", font, - tmp_dynFormat("LayerTestGUI.IsVSync", GraphicsBackend::isVSyncEnabled))); + group.addChild( + new DynamicLabel( + "TPSDisplay", + font, + LayerTestGUI::getTPS, + 128 + ) + ); + + group.addChild( + new DynamicLabel( + "ChunkStatsDisplay", + font, + DynamicStrings.builder() + .addDyn(new MutableStringLocalized("LayerTestGUI.ChunkStatsDisplay")) + .addDyn(() -> { + if (ClientState.getInstance() == null) { + return -1; + } else { + WorldRender world = ClientState.getInstance().getWorld(); + return world.getChunks().size() - world.getPendingChunkUpdates(); + } + }, 4) + .add('/') + .addDyn(() -> { + if (ClientState.getInstance() == null) { + return -1; + } else { + return ClientState.getInstance().getWorld().getPendingChunkUpdates(); + } + }, 4) + .add('/') + .addDyn(() -> { + if (ServerState.getInstance() == null) { + return -1; + } else { + return ServerState.getInstance().getWorld().getChunks().size(); + } + }, 4) + .buildSupplier(), + 128 + ) + ); - panel.addChild( - new DynamicLabel("FPSDisplay", font, - DynamicStrings.builder().addDyn(new MutableStringLocalized("LayerTestGUI.FPSDisplay")) - .addDyn(() -> FPS_RECORD.update(GraphicsInterface.getFPS()), 5, 1).buildSupplier(), - 128)); + group.addChild( + new DynamicLabel( + "PosDisplay", + font, + LayerTestGUI::getPos, + 128 + ) + ); - panel.addChild(new DynamicLabel("TPSDisplay", font, LayerTestGUI::getTPS, 128)); + group.addChild( + new Label( + "SelectedBlockDisplay", + font, + tmp_dynFormat( + "LayerTestGUI.SelectedBlockDisplay", + () -> tpc.isBlockSelected() ? ">" : " ", + () -> tpc.getSelectedBlock().getId() + ) + ) + ); + group.addChild( + new Label( + "SelectedTileDisplay", + font, + tmp_dynFormat( + "LayerTestGUI.SelectedTileDisplay", + () -> tpc.isBlockSelected() ? " " : ">", + () -> tpc.getSelectedTile().getId() + ) + ) + ); + group.addChild( + new Label( + "PlacementModeHint", + font, + new MutableStringLocalized("LayerTestGUI.PlacementModeHint").format("\u2B04") + ) + ); - panel.addChild( - new DynamicLabel("ChunkUpdatesDisplay", font, - DynamicStrings.builder().addDyn(new MutableStringLocalized("LayerTestGUI.ChunkUpdatesDisplay")) - .addDyn(ClientState.getInstance().getWorld()::getPendingChunkUpdates).buildSupplier(), - 128)); - - panel.addChild(new DynamicLabel("PosDisplay", font, LayerTestGUI::getPos, 128)); - - panel.addChild(new Label("SelectedBlockDisplay", font, tmp_dynFormat("LayerTestGUI.SelectedBlockDisplay", - () -> tpc.isBlockSelected() ? ">" : " ", () -> tpc.getSelectedBlock().getId()))); - panel.addChild(new Label("SelectedTileDisplay", font, tmp_dynFormat("LayerTestGUI.SelectedTileDisplay", - () -> tpc.isBlockSelected() ? " " : ">", () -> tpc.getSelectedTile().getId()))); - panel.addChild(new Label("PlacementModeHint", font, - new MutableStringLocalized("LayerTestGUI.PlacementModeHint").format("\u2B04"))); - - getRoot().addChild(panel); + getRoot().addChild(group); } public Runnable getUpdateCallback() { @@ -170,14 +270,16 @@ public class LayerTestGUI extends GUILayer { private static final Averager TPS_RECORD = new Averager(); private static final Supplier TPS_STRING = DynamicStrings.builder() - .addDyn(new MutableStringLocalized("LayerTestGUI.TPSDisplay")) - .addDyn(() -> TPS_RECORD.update(ServerState.getInstance().getTPS()), 5, 1).buildSupplier(); + .addDyn(new MutableStringLocalized("LayerTestGUI.TPSDisplay")) + .addDyn(() -> TPS_RECORD.update(ServerState.getInstance().getTPS()), 5, 1) + .buildSupplier(); private static final Supplier POS_STRING = DynamicStrings.builder() - .addDyn(new MutableStringLocalized("LayerTestGUI.PosDisplay")) - .addDyn(() -> ClientState.getInstance().getCamera().getLastAnchorPosition().x, 7, 1) - .addDyn(() -> ClientState.getInstance().getCamera().getLastAnchorPosition().y, 7, 1) - .addDyn(() -> ClientState.getInstance().getCamera().getLastAnchorPosition().z, 7, 1).buildSupplier(); + .addDyn(new MutableStringLocalized("LayerTestGUI.PosDisplay")) + .addDyn(() -> ClientState.getInstance().getCamera().getLastAnchorPosition().x, 7, 1) + .addDyn(() -> ClientState.getInstance().getCamera().getLastAnchorPosition().y, 7, 1) + .addDyn(() -> ClientState.getInstance().getCamera().getLastAnchorPosition().z, 7, 1) + .buildSupplier(); private static CharSequence getTPS() { Server server = ServerState.getInstance(); @@ -219,95 +321,93 @@ public class LayerTestGUI extends GUILayer { }); } - // private static class DebugComponent extends Component { - // private final int color; - // - // public DebugComponent(String name, Vec2i size, int color) { - // super(name); - // this.color = color; - // - // setPreferredSize(size); - // - // addListener(new Object() { - // @Subscribe - // public void onHoverChanged(HoverEvent e) { - // requestReassembly(); - // } - // }); - // - // addListener(KeyEvent.class, this::onClicked); - // } - // - // private boolean onClicked(KeyEvent event) { - // if (!event.isMouse()) { - // return false; - // } else if (event.isPress() && event.isLeftMouseButton()) { - // System.out.println("You pressed a Component!"); - // } - // return true; - // } - // - // @Override - // protected void assembleSelf(RenderTarget target) { - // target.fill(getX(), getY(), getWidth(), getHeight(), Colors.BLACK); - // - // target.fill( - // getX() + 2, getY() + 2, - // getWidth() - 4, getHeight() - 4, - // isHovered() ? Colors.DEBUG_YELLOW : color - // ); - // } - // } - // - // public LayerTestGUI() { - // super("LayerTestGui", new LayoutAlign(1, 0.75, 5)); - // - // Panel panel = new Panel("Alex", new LayoutVertical(5)); - // - // panel.addChild(new DebugComponent("Bravo", new Vec2i(200, 100), - // 0x44FF44)); - // - // Component charlie = new DebugComponent("Charlie", null, 0x222222); - // charlie.setLayout(new LayoutVertical(5)); - // - // //Debug - // Localizer.getInstance().setLanguage("ru-RU"); - // MutableString epsilon = new MutableStringLocalized("Epsilon") - // .addListener(() -> ((Label)charlie.getChild(0)).update()).format(34, - // "thirty-four"); - // // These two are swapped in code due to a bug in layouts, fixing ATM - // charlie.addChild( - // new Label( - // "Delta", - // new Font().withColor(0xCCBB44).deriveShadow().deriveBold(), - // "Пре-альфа!" - // ) - // ); - // charlie.addChild( - // new Label( - // "Epsilon", - // new Font().withColor(0x4444BB).deriveItalic(), - // () -> epsilon.get().concat("\u269b") - // ) - // ); - // panel.addChild(charlie); - // - // - // charlie.addListener(KeyEvent.class, e -> { - // if(e.isPress() && e.getKey() == GLFW.GLFW_KEY_L) { - // Localizer localizer = Localizer.getInstance(); - // if (localizer.getLanguage().equals("ru-RU")) { - // localizer.setLanguage("en-US"); - // } else { - // localizer.setLanguage("ru-RU"); - // } - // return true; - // } return false; - // }); - // charlie.setFocusable(true); - // charlie.takeFocus(); - // - // getRoot().addChild(panel); - // } +// private static class DebugComponent extends Component { +// private final int color; +// +// public DebugComponent(String name, Vec2i size, int color) { +// super(name); +// this.color = color; +// +// setPreferredSize(size); +// +// addListener(new Object() { +// @Subscribe +// public void onHoverChanged(HoverEvent e) { +// requestReassembly(); +// } +// }); +// +// addListener(KeyEvent.class, this::onClicked); +// } +// +// private boolean onClicked(KeyEvent event) { +// if (!event.isMouse()) { +// return false; +// } else if (event.isPress() && event.isLeftMouseButton()) { +// System.out.println("You pressed a Component!"); +// } +// return true; +// } +// +// @Override +// protected void assembleSelf(RenderTarget target) { +// target.fill(getX(), getY(), getWidth(), getHeight(), Colors.BLACK); +// +// target.fill( +// getX() + 2, getY() + 2, +// getWidth() - 4, getHeight() - 4, +// isHovered() ? Colors.DEBUG_YELLOW : color +// ); +// } +// } +// +// public LayerTestGUI() { +// super("LayerTestGui", new LayoutAlign(1, 0.75, 5)); +// +// Panel panel = new Panel("Alex", new LayoutVertical(5)); +// +// panel.addChild(new DebugComponent("Bravo", new Vec2i(200, 100), 0x44FF44)); +// +// Component charlie = new DebugComponent("Charlie", null, 0x222222); +// charlie.setLayout(new LayoutVertical(5)); +// +// //Debug +// Localizer.getInstance().setLanguage("ru-RU"); +// MutableString epsilon = new MutableStringLocalized("Epsilon") +// .addListener(() -> ((Label)charlie.getChild(0)).update()).format(34, "thirty-four"); +// // These two are swapped in code due to a bug in layouts, fixing ATM +// charlie.addChild( +// new Label( +// "Delta", +// new Font().withColor(0xCCBB44).deriveShadow().deriveBold(), +// "Пре-альфа!" +// ) +// ); +// charlie.addChild( +// new Label( +// "Epsilon", +// new Font().withColor(0x4444BB).deriveItalic(), +// () -> epsilon.get().concat("\u269b") +// ) +// ); +// panel.addChild(charlie); +// +// +// charlie.addListener(KeyEvent.class, e -> { +// if(e.isPress() && e.getKey() == GLFW.GLFW_KEY_L) { +// Localizer localizer = Localizer.getInstance(); +// if (localizer.getLanguage().equals("ru-RU")) { +// localizer.setLanguage("en-US"); +// } else { +// localizer.setLanguage("ru-RU"); +// } +// return true; +// } return false; +// }); +// charlie.setFocusable(true); +// charlie.takeFocus(); +// +// getRoot().addChild(panel); +// } } diff --git a/src/main/java/ru/windcorp/progressia/test/LayerTestUI.java b/src/main/java/ru/windcorp/progressia/test/LayerTestUI.java index 892df0f..19c10aa 100755 --- a/src/main/java/ru/windcorp/progressia/test/LayerTestUI.java +++ b/src/main/java/ru/windcorp/progressia/test/LayerTestUI.java @@ -22,19 +22,13 @@ import org.lwjgl.glfw.GLFW; import com.google.common.eventbus.Subscribe; -import glm.mat._4.Mat4; import glm.vec._4.Vec4; -import ru.windcorp.progressia.client.ClientState; import ru.windcorp.progressia.client.graphics.Colors; import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface; import ru.windcorp.progressia.client.graphics.flat.AssembledFlatLayer; import ru.windcorp.progressia.client.graphics.flat.RenderTarget; import ru.windcorp.progressia.client.graphics.input.KeyEvent; import ru.windcorp.progressia.client.graphics.input.bus.Input; -import ru.windcorp.progressia.client.graphics.model.LambdaModel; -import ru.windcorp.progressia.client.graphics.texture.SimpleTextures; -import ru.windcorp.progressia.client.graphics.texture.Texture; -import ru.windcorp.progressia.client.graphics.world.Camera; public class LayerTestUI extends AssembledFlatLayer { @@ -44,53 +38,13 @@ public class LayerTestUI extends AssembledFlatLayer { GraphicsInterface.subscribeToInputEvents(this); } - private boolean flag = false; - - private static final int WIDTH = 80; - private static final int HEIGHT = 80; - private static final int BORDER = 5; + private boolean drawUI = true; @Override protected void assemble(RenderTarget target) { - final int boxColor = flag ? 0xFFEE8888 : 0xFFEEEE88; - final int borderColor = flag ? 0xFFAA4444 : 0xFFAAAA44; - final int boxShadowColor = flag ? 0xFF440000 : 0xFF444400; - - int x = 2 * BORDER; - int y = 2 * BORDER; - - target.fill(x + BORDER, y - BORDER, WIDTH, HEIGHT, boxShadowColor); - target.fill(x - 1, y - 1, WIDTH + 2, HEIGHT + 2, boxShadowColor); - target.fill(x, y, WIDTH, HEIGHT, borderColor); - target.fill(x + BORDER, y + BORDER, WIDTH - 2 * BORDER, HEIGHT - 2 * BORDER, boxColor); - - final int texShadow = 2; - final int texSize = HEIGHT - 4 * BORDER; - - target.pushTransform(new Mat4().identity().translate(x + 2 * BORDER, y + 2 * BORDER, 0)); - - final Texture compassBg = SimpleTextures.get("compass_icon"); - final Texture compassFg = SimpleTextures.get("compass_icon_arrow"); - - target.drawTexture(texShadow, -texShadow, texSize, texSize, Colors.BLACK, compassBg); - target.drawTexture(0, 0, texSize, texSize, compassBg); - - target.addCustomRenderer(new LambdaModel(LambdaModel.lambdaBuilder().addDynamicPart( - target.createRectagle(0, 0, texSize, texSize, Colors.WHITE, compassFg), - mat -> mat.translate(texSize / 2, texSize / 2, 0).rotateZ(getCompassRotation()).translate(-texSize / 2, - -texSize / 2, 0)))); - target.popTransform(); - - drawCross(target); - } - - private double getCompassRotation() { - Camera.Anchor anchor = ClientState.getInstance().getCamera().getAnchor(); - - if (anchor == null) - return 0; - - return -anchor.getCameraYaw(); + if (drawUI) { + drawCross(target); + } } private void drawCross(RenderTarget target) { @@ -103,29 +57,51 @@ public class LayerTestUI extends AssembledFlatLayer { final Vec4 borderColor = Colors.BLACK; final Vec4 fillColor = Colors.WHITE; - target.fill(cx - length - thickness / 2, cy - thickness / 2, 2 * length + thickness, thickness, borderColor); + target.fill( + cx - length - thickness / 2, + cy - thickness / 2, + 2 * length + thickness, + thickness, + borderColor + ); - target.fill(cx - thickness / 2, cy - length - thickness / 2, thickness, 2 * length + thickness, borderColor); + target.fill( + cx - thickness / 2, + cy - length - thickness / 2, + thickness, + 2 * length + thickness, + borderColor + ); - target.fill(cx - length - thickness / 2 + borderSize, cy - thickness / 2 + borderSize, - 2 * length + thickness - 2 * borderSize, thickness - 2 * borderSize, fillColor); + target.fill( + cx - length - thickness / 2 + borderSize, + cy - thickness / 2 + borderSize, + 2 * length + thickness - 2 * borderSize, + thickness - 2 * borderSize, + fillColor + ); - target.fill(cx - thickness / 2 + borderSize, cy - length - thickness / 2 + borderSize, - thickness - 2 * borderSize, 2 * length + thickness - 2 * borderSize, fillColor); + target.fill( + cx - thickness / 2 + borderSize, + cy - length - thickness / 2 + borderSize, + thickness - 2 * borderSize, + 2 * length + thickness - 2 * borderSize, + fillColor + ); } @Override protected void handleInput(Input input) { - + // Do nothing } @Subscribe public void onKeyEvent(KeyEvent event) { - if (event.isRepeat() || event.getKey() != GLFW.GLFW_KEY_LEFT_CONTROL) { + if (!event.isPress() || event.getKey() != GLFW.GLFW_KEY_F1) { return; } - flag = event.isPress(); + drawUI = !drawUI; invalidate(); } diff --git a/src/main/java/ru/windcorp/progressia/test/Rocks.java b/src/main/java/ru/windcorp/progressia/test/Rocks.java new file mode 100644 index 0000000..ebf31fe --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/Rocks.java @@ -0,0 +1,126 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ru.windcorp.progressia.test; + +import java.util.Collection; +import java.util.Collections; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Map; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; + +import ru.windcorp.progressia.client.world.block.BlockRenderOpaqueCube; +import ru.windcorp.progressia.client.world.block.BlockRenderRegistry; +import ru.windcorp.progressia.common.world.block.BlockData; +import ru.windcorp.progressia.common.world.block.BlockDataRegistry; +import ru.windcorp.progressia.server.world.block.BlockLogic; +import ru.windcorp.progressia.server.world.block.BlockLogicRegistry; + +public class Rocks { + + public enum RockType { + IGNEOUS, METAMORPHIC, SEDIMENTARY; + } + + public enum RockVariant { + + MONOLITH("Monolith"), + CRACKED("Cracked"), + GRAVEL("Gravel"), + SAND("Sand"); + + private final String name; + + private RockVariant(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + } + + public static class Rock { + + private final String name; + private final RockType type; + + private final Map blocks = new EnumMap<>(RockVariant.class); + + public Rock(String name, RockType type) { + this.name = name; + this.type = type; + } + + public String getName() { + return name; + } + + public RockType getType() { + return type; + } + + public BlockData getBlock(RockVariant variant) { + return blocks.get(variant); + } + + private void register() { + for (RockVariant variant : RockVariant.values()) { + + String fullName = name + variant.getName(); + String id = "Test:" + fullName; + + BlockData blockData = new BlockData(id); + blocks.put(variant, blockData); + BlockDataRegistry.getInstance().register(blockData); + BlockLogicRegistry.getInstance().register(new BlockLogic(id)); + BlockRenderRegistry.getInstance() + .register(new BlockRenderOpaqueCube(id, BlockRenderRegistry.getBlockTexture(fullName))); + + } + } + + } + + private final Map rocksByName = Collections.synchronizedMap(new HashMap<>()); + private final Multimap rocksByType = Multimaps.synchronizedMultimap(HashMultimap.create()); + + public Rock create(RockType type, String name) { + Rock rock = new Rock(name, type); + rocksByName.put(name, rock); + rocksByType.put(type, rock); + return rock; + } + + public void registerAllRocks() { + getRocks().forEach(Rock::register); + } + + public Collection getRocks() { + return rocksByName.values(); + } + + public Collection getRocks(RockType type) { + return rocksByType.get(type); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/TestBlockLogicAir.java b/src/main/java/ru/windcorp/progressia/test/TestBlockLogicAir.java index e9877e9..d18f850 100644 --- a/src/main/java/ru/windcorp/progressia/test/TestBlockLogicAir.java +++ b/src/main/java/ru/windcorp/progressia/test/TestBlockLogicAir.java @@ -18,7 +18,7 @@ package ru.windcorp.progressia.test; -import ru.windcorp.progressia.common.world.block.BlockFace; +import ru.windcorp.progressia.common.world.rels.RelFace; import ru.windcorp.progressia.server.world.block.BlockLogic; public class TestBlockLogicAir extends BlockLogic { @@ -28,7 +28,7 @@ public class TestBlockLogicAir extends BlockLogic { } @Override - public boolean isSolid(BlockFace face) { + public boolean isSolid(RelFace face) { return false; } diff --git a/src/main/java/ru/windcorp/progressia/test/TestBlockLogicGlass.java b/src/main/java/ru/windcorp/progressia/test/TestBlockLogicGlass.java index e58cc3d..f2603cc 100644 --- a/src/main/java/ru/windcorp/progressia/test/TestBlockLogicGlass.java +++ b/src/main/java/ru/windcorp/progressia/test/TestBlockLogicGlass.java @@ -18,7 +18,7 @@ package ru.windcorp.progressia.test; -import ru.windcorp.progressia.common.world.block.BlockFace; +import ru.windcorp.progressia.common.world.rels.RelFace; import ru.windcorp.progressia.server.world.block.BlockLogic; public class TestBlockLogicGlass extends BlockLogic { @@ -28,7 +28,7 @@ public class TestBlockLogicGlass extends BlockLogic { } @Override - public boolean isSolid(BlockFace face) { + public boolean isSolid(RelFace face) { return false; } diff --git a/src/main/java/ru/windcorp/progressia/test/TestContent.java b/src/main/java/ru/windcorp/progressia/test/TestContent.java index 573fd1f..1244560 100644 --- a/src/main/java/ru/windcorp/progressia/test/TestContent.java +++ b/src/main/java/ru/windcorp/progressia/test/TestContent.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.test; import static ru.windcorp.progressia.client.world.block.BlockRenderRegistry.getBlockTexture; @@ -27,10 +27,8 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.function.Consumer; - import org.lwjgl.glfw.GLFW; -import glm.vec._3.Vec3; import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.client.ClientState; import ru.windcorp.progressia.client.audio.Sound; @@ -47,25 +45,31 @@ import ru.windcorp.progressia.common.collision.AABB; import ru.windcorp.progressia.common.collision.CollisionModel; import ru.windcorp.progressia.common.comms.controls.*; import ru.windcorp.progressia.common.state.StatefulObjectRegistry.Factory; +import ru.windcorp.progressia.common.world.GravityModelRegistry; import ru.windcorp.progressia.common.world.block.*; import ru.windcorp.progressia.common.world.entity.*; import ru.windcorp.progressia.common.world.io.ChunkIO; +import ru.windcorp.progressia.common.world.rels.AbsFace; import ru.windcorp.progressia.common.world.tile.*; import ru.windcorp.progressia.server.Server; import ru.windcorp.progressia.server.comms.controls.*; import ru.windcorp.progressia.server.world.block.*; import ru.windcorp.progressia.server.world.entity.*; +import ru.windcorp.progressia.server.world.generation.planet.PlanetGravityModel; import ru.windcorp.progressia.server.world.tile.*; +import ru.windcorp.progressia.test.Rocks.RockType; +import ru.windcorp.progressia.test.gen.TestGravityModel; public class TestContent { public static final String PLAYER_LOGIN = "Sasha"; public static final long PLAYER_ENTITY_ID = 0x42; public static final long STATIE_ENTITY_ID = 0xDEADBEEF; - public static final Vec3 SPAWN = new Vec3(8, 8, 880); public static final List PLACEABLE_BLOCKS = new ArrayList<>(); public static final List PLACEABLE_TILES = new ArrayList<>(); + + public static final Rocks ROCKS = new Rocks(); public static void registerContent() { registerWorldContent(); @@ -100,13 +104,7 @@ public class TestContent { register(new BlockRenderOpaqueCube("Test:Stone", getBlockTexture("Stone"))); register(new BlockLogic("Test:Stone")); - for (String type : new String[] { "Monolith", "Cracked", "Gravel" }) { - String id = "Test:Granite" + type; - - register(new BlockData(id)); - register(new BlockRenderOpaqueCube(id, getBlockTexture("Granite" + type))); - register(new BlockLogic(id)); - } + registerRocks(); register(new BlockData("Test:Brick")); register(new BlockRenderOpaqueCube("Test:Brick", getBlockTexture("Brick"))); @@ -129,10 +127,18 @@ public class TestContent { register(new BlockLogic("Test:Concrete")); register(new BlockData("Test:Log")); - register(new BlockRenderOpaqueCube("Test:Log", getBlockTexture("LogTop"), getBlockTexture("LogTop"), - getBlockTexture("LogSide"), getBlockTexture("LogSide"), getBlockTexture("LogSide"), - getBlockTexture("LogSide"))); + register( + new BlockRenderOpaqueCube( + "Test:Log", + getBlockTexture("LogTop"), + getBlockTexture("LogTop"), + getBlockTexture("LogSide") + ) + ); register(new BlockLogic("Test:Log")); + register(new BlockData("Test:TemporaryLeaves")); + register(new BlockRenderTransparentCube("Test:TemporaryLeaves", getBlockTexture("TemporaryLeaves"))); + register(new TestBlockLogicGlass("Test:TemporaryLeaves")); // Sic, using Glass logic for leaves because Test register(new BlockData("Test:WoodenPlank")); register(new BlockRenderOpaqueCube("Test:WoodenPlank", getBlockTexture("WoodenPlank"))); @@ -144,11 +150,24 @@ public class TestContent { } + private static void registerRocks() { + + ROCKS.create(RockType.IGNEOUS, "BlackGranite"); + ROCKS.create(RockType.IGNEOUS, "RedGranite"); + ROCKS.create(RockType.IGNEOUS, "Gabbro"); + ROCKS.create(RockType.METAMORPHIC, "Marble"); + ROCKS.create(RockType.METAMORPHIC, "Eclogite"); + ROCKS.create(RockType.SEDIMENTARY, "Limestone"); + ROCKS.create(RockType.SEDIMENTARY, "Dolomite"); + + ROCKS.registerAllRocks(); + } + private static void registerTiles() { Set placeableBlacklist = new HashSet<>(); register(new TileData("Test:Grass")); - register(new TileRenderGrass("Test:Grass", getTileTexture("GrassTop"), getTileTexture("GrassSide"))); + register(new TestTileRenderGrass("Test:Grass", getTileTexture("GrassTop"), getTileTexture("GrassSide"))); register(new TestTileLogicGrass("Test:Grass")); register(new TileData("Test:Stones")); @@ -237,10 +256,6 @@ public class TestContent { register("Test:Statie", TestEntityDataStatie::new); register(new TestEntityRenderStatie("Test:Statie")); register(new TestEntityLogicStatie("Test:Statie")); - - register("Test:FallingBlock", TestEntityDataFallingBlock::new); - register(new TestEntityRenderFallingBlock("Test:FallingBlock")); - register(new TestEntityLogicFallingBlock("Test:FallingBlock")); } private static void regsiterControls() { @@ -249,24 +264,49 @@ public class TestContent { ControlLogicRegistry logic = ControlLogicRegistry.getInstance(); data.register("Test:BreakBlock", ControlBreakBlockData::new); - triggers.register(ControlTriggers.of("Test:BreakBlock", KeyEvent.class, TestContent::onBlockBreakTrigger, - KeyMatcher.of(GLFW.GLFW_MOUSE_BUTTON_LEFT).matcher(), i -> isAnythingSelected())); + triggers.register( + ControlTriggers.of( + "Test:BreakBlock", + KeyEvent.class, + TestContent::onBlockBreakTrigger, + KeyMatcher.of(GLFW.GLFW_MOUSE_BUTTON_LEFT).matcher(), + i -> isAnythingSelected() + ) + ); logic.register(ControlLogic.of("Test:BreakBlock", TestContent::onBlockBreakReceived)); data.register("Test:PlaceBlock", ControlPlaceBlockData::new); - triggers.register(ControlTriggers.of("Test:PlaceBlock", KeyEvent.class, TestContent::onBlockPlaceTrigger, + triggers.register( + ControlTriggers.of( + "Test:PlaceBlock", + KeyEvent.class, + TestContent::onBlockPlaceTrigger, KeyMatcher.of(GLFW.GLFW_MOUSE_BUTTON_RIGHT).matcher(), - i -> isAnythingSelected() && TestPlayerControls.getInstance().isBlockSelected())); + i -> isAnythingSelected() && TestPlayerControls.getInstance().isBlockSelected() + ) + ); logic.register(ControlLogic.of("Test:PlaceBlock", TestContent::onBlockPlaceReceived)); data.register("Test:PlaceTile", ControlPlaceTileData::new); - triggers.register(ControlTriggers.of("Test:PlaceTile", KeyEvent.class, TestContent::onTilePlaceTrigger, + triggers.register( + ControlTriggers.of( + "Test:PlaceTile", + KeyEvent.class, + TestContent::onTilePlaceTrigger, KeyMatcher.of(GLFW.GLFW_MOUSE_BUTTON_RIGHT).matcher(), - i -> isAnythingSelected() && !TestPlayerControls.getInstance().isBlockSelected())); + i -> isAnythingSelected() && !TestPlayerControls.getInstance().isBlockSelected() + ) + ); logic.register(ControlLogic.of("Test:PlaceTile", TestContent::onTilePlaceReceived)); - - triggers.register(ControlTriggers.localOf("Test:StartNextMusic", KeyEvent.class, TestMusicPlayer::startNextNow, - KeyMatcher.of(GLFW.GLFW_KEY_M).matcher())); + + triggers.register( + ControlTriggers.localOf( + "Test:StartNextMusic", + KeyEvent.class, + TestMusicPlayer::startNextNow, + KeyMatcher.of(GLFW.GLFW_KEY_M).matcher() + ) + ); } private static void register(BlockData x) { @@ -277,11 +317,17 @@ public class TestContent { TileDataRegistry.getInstance().register(x); } - private static void register(String id, Factory factory) { + private static void register( + String id, + Factory factory + ) { EntityDataRegistry.getInstance().register(id, factory); } - private static void registerEntityData(String id, Consumer transform) { + private static void registerEntityData( + String id, + Consumer transform + ) { EntityDataRegistry.getInstance().register(id, new Factory() { @Override public EntityData build() { @@ -340,49 +386,65 @@ public class TestContent { sfx.play(false); } - private static void onBlockBreakReceived(Server server, PacketControl packet, - ru.windcorp.progressia.server.comms.Client client) { + private static void onBlockBreakReceived( + Server server, + PacketControl packet, + ru.windcorp.progressia.server.comms.Client client + ) { Vec3i blockInWorld = ((ControlBreakBlockData) packet.getControl()).getBlockInWorld(); - server.getWorldAccessor().setBlock(blockInWorld, BlockDataRegistry.getInstance().get("Test:Air")); + server.createAbsoluteContext().setBlock(blockInWorld, BlockDataRegistry.getInstance().get("Test:Air")); } private static void onBlockPlaceTrigger(ControlData control) { - ((ControlPlaceBlockData) control).set(TestPlayerControls.getInstance().getSelectedBlock(), - getSelection().getBlock().add_(getSelection().getSurface().getVector())); + ((ControlPlaceBlockData) control).set( + TestPlayerControls.getInstance().getSelectedBlock(), + getSelection().getBlock().add_(getSelection().getSurface().getVector()) + ); } - private static void onBlockPlaceReceived(Server server, PacketControl packet, - ru.windcorp.progressia.server.comms.Client client) { + private static void onBlockPlaceReceived( + Server server, + PacketControl packet, + ru.windcorp.progressia.server.comms.Client client + ) { ControlPlaceBlockData controlData = ((ControlPlaceBlockData) packet.getControl()); BlockData block = controlData.getBlock(); Vec3i blockInWorld = controlData.getBlockInWorld(); if (server.getWorld().getData().getChunkByBlock(blockInWorld) == null) return; - server.getWorldAccessor().setBlock(blockInWorld, block); + server.createAbsoluteContext().setBlock(blockInWorld, block); } private static void onTilePlaceTrigger(ControlData control) { - ((ControlPlaceTileData) control).set(TestPlayerControls.getInstance().getSelectedTile(), - getSelection().getBlock(), getSelection().getSurface()); + ((ControlPlaceTileData) control).set( + TestPlayerControls.getInstance().getSelectedTile(), + getSelection().getBlock(), + getSelection().getSurface() + ); } - private static void onTilePlaceReceived(Server server, PacketControl packet, - ru.windcorp.progressia.server.comms.Client client) { + private static void onTilePlaceReceived( + Server server, + PacketControl packet, + ru.windcorp.progressia.server.comms.Client client + ) { ControlPlaceTileData controlData = ((ControlPlaceTileData) packet.getControl()); TileData tile = controlData.getTile(); Vec3i blockInWorld = controlData.getBlockInWorld(); - BlockFace face = controlData.getFace(); + AbsFace face = controlData.getFace(); if (server.getWorld().getData().getChunkByBlock(blockInWorld) == null) return; if (server.getWorld().getData().getTiles(blockInWorld, face).isFull()) return; - server.getWorldAccessor().addTile(blockInWorld, face, tile); + server.createAbsoluteContext().addTile(blockInWorld, face.relativize(AbsFace.POS_Z), tile); } private static void registerMisc() { ChunkIO.registerCodec(new TestChunkCodec()); ChunkRenderOptimizerRegistry.getInstance().register("Core:SurfaceOptimizer", ChunkRenderOptimizerSurface::new); + GravityModelRegistry.getInstance().register("Test:TheGravityModel", TestGravityModel::new); + GravityModelRegistry.getInstance().register("Test:PlanetGravityModel", PlanetGravityModel::new); } } diff --git a/src/main/java/ru/windcorp/progressia/test/TestEntityLogicStatie.java b/src/main/java/ru/windcorp/progressia/test/TestEntityLogicStatie.java index f27abfe..4862d59 100644 --- a/src/main/java/ru/windcorp/progressia/test/TestEntityLogicStatie.java +++ b/src/main/java/ru/windcorp/progressia/test/TestEntityLogicStatie.java @@ -19,7 +19,7 @@ package ru.windcorp.progressia.test; import ru.windcorp.progressia.common.world.entity.EntityData; -import ru.windcorp.progressia.server.world.TickContext; +import ru.windcorp.progressia.server.world.context.ServerWorldContext; import ru.windcorp.progressia.server.world.entity.EntityLogic; public class TestEntityLogicStatie extends EntityLogic { @@ -29,13 +29,13 @@ public class TestEntityLogicStatie extends EntityLogic { } @Override - public void tick(EntityData entity, TickContext context) { + public void tick(EntityData entity, ServerWorldContext context) { super.tick(entity, context); TestEntityDataStatie statie = (TestEntityDataStatie) entity; int size = (int) (18 + 6 * Math.sin(entity.getAge())); - context.getServer().getWorldAccessor().changeEntity(statie, e -> e.setSizeNow(size)); + context.changeEntity(statie, e -> e.setSizeNow(size)); } } diff --git a/src/main/java/ru/windcorp/progressia/test/TestEntityRenderStatie.java b/src/main/java/ru/windcorp/progressia/test/TestEntityRenderStatie.java index af5ed11..fa595e7 100644 --- a/src/main/java/ru/windcorp/progressia/test/TestEntityRenderStatie.java +++ b/src/main/java/ru/windcorp/progressia/test/TestEntityRenderStatie.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.test; import ru.windcorp.progressia.client.graphics.model.Renderable; @@ -29,8 +29,12 @@ import ru.windcorp.progressia.common.world.entity.EntityData; public class TestEntityRenderStatie extends EntityRender { - private final Renderable cube = new Shapes.PppBuilder(WorldRenderProgram.getDefault(), (Texture) null) - .setColorMultiplier(1, 1, 0).create(); + private final Renderable cube = new Shapes.PppBuilder( + WorldRenderProgram.getDefault(), + (Texture) null + ) + .setColorMultiplier(1, 1, 0) + .create(); public TestEntityRenderStatie(String id) { super(id); @@ -40,8 +44,10 @@ public class TestEntityRenderStatie extends EntityRender { public EntityRenderable createRenderable(EntityData entity) { return new EntityRenderable(entity) { @Override - public void render(ShapeRenderHelper renderer) { - renderer.pushTransform().scale(((TestEntityDataStatie) entity).getSize() / 24.0f); + public void doRender(ShapeRenderHelper renderer) { + renderer.pushTransform().scale( + ((TestEntityDataStatie) entity).getSize() / 24.0f + ); cube.render(renderer); diff --git a/src/main/java/ru/windcorp/progressia/test/TestPlayerControls.java b/src/main/java/ru/windcorp/progressia/test/TestPlayerControls.java index f9b4439..ee42396 100644 --- a/src/main/java/ru/windcorp/progressia/test/TestPlayerControls.java +++ b/src/main/java/ru/windcorp/progressia/test/TestPlayerControls.java @@ -15,12 +15,12 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + package ru.windcorp.progressia.test; import glm.Glm; import glm.mat._3.Mat3; -import glm.vec._2.Vec2; +import glm.mat._4.Mat4; import glm.vec._3.Vec3; import org.lwjgl.glfw.GLFW; import ru.windcorp.progressia.client.ClientState; @@ -36,7 +36,9 @@ import ru.windcorp.progressia.client.graphics.input.bus.Input; import ru.windcorp.progressia.client.graphics.world.LocalPlayer; import ru.windcorp.progressia.client.localization.Localizer; import ru.windcorp.progressia.common.Units; -import ru.windcorp.progressia.common.util.FloatMathUtil; +import ru.windcorp.progressia.common.util.Matrices; +import ru.windcorp.progressia.common.util.VectorUtil; +import ru.windcorp.progressia.common.util.Vectors; import ru.windcorp.progressia.common.world.block.BlockData; import ru.windcorp.progressia.common.world.entity.EntityData; import ru.windcorp.progressia.common.world.tile.TileData; @@ -82,9 +84,6 @@ public class TestPlayerControls { private double lastSpacePress = Double.NEGATIVE_INFINITY; private double lastSprintPress = Double.NEGATIVE_INFINITY; - private boolean captureMouse = true; - private boolean useMinecraftGravity = false; - private int selectedBlock = 0; private int selectedTile = 0; private boolean isBlockSelected = true; @@ -109,34 +108,56 @@ public class TestPlayerControls { authority = WALKING_CONTROL_AUTHORITY; } - Mat3 angMat = new Mat3().identity().rotateZ(player.getYaw()); - Vec3 desiredVelocity = new Vec3(movementForward, -movementRight, 0); - - if (movementForward != 0 && movementRight != 0) + Mat3 movementTransform = getMovementTransform(player, null); + Vec3 desiredVelocity = new Vec3(movementForward, movementRight, 0); + if (movementForward != 0 && movementRight != 0) { desiredVelocity.normalize(); - angMat.mul_(desiredVelocity); // bug in jglm, .mul() and mul_() are - // swapped + } desiredVelocity.z = movementUp; + movementTransform.mul_(desiredVelocity); // bug in jglm, .mul() and .mul_() are + // swapped desiredVelocity.mul(speed); - Vec3 change = new Vec3().set(desiredVelocity).sub(player.getVelocity()) - .mul((float) Math.exp(-authority * GraphicsInterface.getFrameLength())).negate().add(desiredVelocity); + Vec3 newVelocity = new Vec3() + .set(desiredVelocity) + .sub(player.getVelocity()) + .mul((float) Math.exp(-authority * GraphicsInterface.getFrameLength())) + .negate() + .add(desiredVelocity); if (!isFlying) { - change.z = player.getVelocity().z; + Vec3 up = player.getUpVector(); + Vec3 wantedVertical = VectorUtil.projectOnVector(player.getVelocity(), up, null); + VectorUtil.projectOnSurface(newVelocity, up).add(wantedVertical); } - player.getVelocity().set(change); + player.getVelocity().set(newVelocity); // THIS IS TERRIBLE TEST EntityData serverEntity = ServerState.getInstance().getWorld().getData() - .getEntity(TestContent.PLAYER_ENTITY_ID); + .getEntity(TestContent.PLAYER_ENTITY_ID); if (serverEntity != null) { serverEntity.setPosition(player.getPosition()); } } + private Mat3 getMovementTransform(EntityData player, Mat3 mat) { + if (mat == null) { + mat = new Mat3(); + } + + Vec3 f = player.getForwardVector(null); + Vec3 u = player.getUpVector(); + Vec3 s = u.cross_(f); + + return mat.set( + +f.x, +f.y, +f.z, + -s.x, -s.y, -s.z, + +u.x, +u.y, +u.z + ); + } + public void handleInput(Input input) { InputEvent event = input.getEvent(); @@ -180,6 +201,7 @@ public class TestPlayerControls { case GLFW.GLFW_KEY_ESCAPE: if (!event.isPress()) return false; + handleEscape(); break; @@ -209,12 +231,6 @@ public class TestPlayerControls { handleCameraMode(); break; - case GLFW.GLFW_KEY_G: - if (!event.isPress()) - return false; - handleGravitySwitch(); - break; - case GLFW.GLFW_KEY_L: if (!event.isPress()) return false; @@ -279,7 +295,13 @@ public class TestPlayerControls { return; } - getEntity().getVelocity().add(0, 0, JUMP_VELOCITY * (useMinecraftGravity ? 2 : 1)); + Vec3 up = getEntity().getUpVector(); + + getEntity().getVelocity().add( + up.x * JUMP_VELOCITY, + up.y * JUMP_VELOCITY, + up.z * JUMP_VELOCITY + ); } private void handleShift(int multiplier) { @@ -289,14 +311,10 @@ public class TestPlayerControls { } private void handleEscape() { - if (captureMouse) { - GLFW.glfwSetInputMode(GraphicsBackend.getWindowHandle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_NORMAL); - } else { - GLFW.glfwSetInputMode(GraphicsBackend.getWindowHandle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED); - } - - captureMouse = !captureMouse; - updateGUI(); + movementForward = 0; + movementRight = 0; + movementUp = 0; + GUI.addTopLayer(new LayerButtonTest()); } private void handleDebugLayerSwitch() { @@ -323,11 +341,6 @@ public class TestPlayerControls { } } - private void handleGravitySwitch() { - useMinecraftGravity = !useMinecraftGravity; - updateGUI(); - } - private void handleLanguageSwitch() { Localizer localizer = Localizer.getInstance(); if (localizer.getLanguage().equals("ru-RU")) { @@ -340,28 +353,40 @@ public class TestPlayerControls { } private void onMouseMoved(CursorMoveEvent event) { - if (!captureMouse) - return; - if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) { return; } - final float yawScale = -0.002f; - final float pitchScale = yawScale; + final double yawScale = -0.002f; + final double pitchScale = -yawScale; + final double pitchExtremum = Math.PI/2 * 0.95f; + + double yawChange = event.getChangeX() * yawScale; + double pitchChange = event.getChangeY() * pitchScale; EntityData player = getEntity(); + + double startPitch = player.getPitch(); + double endPitch = startPitch + pitchChange; + endPitch = Glm.clamp(endPitch, -pitchExtremum, +pitchExtremum); + pitchChange = endPitch - startPitch; + + Mat4 mat = Matrices.grab4(); + Vec3 lookingAt = Vectors.grab3(); + Vec3 rightVector = Vectors.grab3(); - normalizeAngles(player.getDirection().add((float) (event.getChangeX() * yawScale), - (float) (event.getChangeY() * pitchScale))); - } - - private void normalizeAngles(Vec2 dir) { - // Normalize yaw - dir.x = FloatMathUtil.normalizeAngle(dir.x); - - // Clamp pitch - dir.y = Glm.clamp(dir.y, -FloatMathUtil.PI_F / 2, +FloatMathUtil.PI_F / 2); + rightVector.set(player.getLookingAt()).cross(player.getUpVector()).normalize(); + + mat.identity() + .rotate((float) yawChange, player.getUpVector()) + .rotate((float) pitchChange, rightVector); + + VectorUtil.applyMat4(player.getLookingAt(), mat, lookingAt); + player.setLookingAt(lookingAt); + + Vectors.release(rightVector); + Vectors.release(lookingAt); + Matrices.release(mat); } private void onWheelScroll(WheelScrollEvent event) { @@ -425,14 +450,6 @@ public class TestPlayerControls { return isSprinting; } - public boolean isMouseCaptured() { - return captureMouse; - } - - public boolean useMinecraftGravity() { - return useMinecraftGravity; - } - public BlockData getSelectedBlock() { return TestContent.PLACEABLE_BLOCKS.get(selectedBlock); } diff --git a/src/main/java/ru/windcorp/progressia/test/TestTileLogicGrass.java b/src/main/java/ru/windcorp/progressia/test/TestTileLogicGrass.java index e6514ca..c2df863 100644 --- a/src/main/java/ru/windcorp/progressia/test/TestTileLogicGrass.java +++ b/src/main/java/ru/windcorp/progressia/test/TestTileLogicGrass.java @@ -18,13 +18,13 @@ package ru.windcorp.progressia.test; -import ru.windcorp.progressia.common.world.block.BlockFace; +import ru.windcorp.progressia.common.world.rels.RelFace; import ru.windcorp.progressia.server.world.block.BlockLogic; -import ru.windcorp.progressia.server.world.block.BlockTickContext; +import ru.windcorp.progressia.server.world.context.ServerTileContext; +import ru.windcorp.progressia.server.world.context.ServerTileContextRO; import ru.windcorp.progressia.server.world.ticking.TickingPolicy; import ru.windcorp.progressia.server.world.tile.HangingTileLogic; import ru.windcorp.progressia.server.world.tile.TickableTile; -import ru.windcorp.progressia.server.world.tile.TileTickContext; public class TestTileLogicGrass extends HangingTileLogic implements TickableTile { @@ -33,44 +33,40 @@ public class TestTileLogicGrass extends HangingTileLogic implements TickableTile } @Override - public boolean canOccupyFace(TileTickContext context) { - return context.getFace() != BlockFace.BOTTOM && super.canOccupyFace(context); + public boolean canOccupyFace(ServerTileContextRO context) { + return context.getFace() != RelFace.DOWN && super.canOccupyFace(context); } @Override - public boolean canOccupyFace(BlockFace face) { - return face != BlockFace.BOTTOM; + public boolean canOccupyFace(RelFace face) { + return face != RelFace.DOWN; } @Override - public TickingPolicy getTickingPolicy(TileTickContext context) { + public TickingPolicy getTickingPolicy(ServerTileContextRO context) { return TickingPolicy.RANDOM; } @Override - public void tick(TileTickContext context) { + public void tick(ServerTileContext context) { if (!isLocationSuitable(context)) { - context.removeThisTile(); + context.removeTile(); } } @Override - public boolean canBeSquashed(TileTickContext context) { + public boolean canBeSquashed(ServerTileContextRO context) { return true; } - private boolean isLocationSuitable(TileTickContext context) { + private boolean isLocationSuitable(ServerTileContextRO context) { return canOccupyFace(context) && isBlockAboveTransparent(context); } - private boolean isBlockAboveTransparent(BlockTickContext context) { - return context.evalNeighbor(BlockFace.TOP, bctxt -> { - BlockLogic block = bctxt.getBlock(); - if (block == null) - return true; - - return block.isTransparent(bctxt); - }); + private boolean isBlockAboveTransparent(ServerTileContextRO context) { + context.pushRelative(RelFace.UP); + BlockLogic block = context.logic().getBlock(); + return context.popAndReturn(block == null || block.isTransparent(context)); } } diff --git a/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderGrass.java b/src/main/java/ru/windcorp/progressia/test/TestTileRenderGrass.java similarity index 67% rename from src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderGrass.java rename to src/main/java/ru/windcorp/progressia/test/TestTileRenderGrass.java index e50d8d4..14b56c7 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderGrass.java +++ b/src/main/java/ru/windcorp/progressia/test/TestTileRenderGrass.java @@ -15,31 +15,36 @@ * 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; + +package ru.windcorp.progressia.test; import ru.windcorp.progressia.client.graphics.texture.Texture; -import ru.windcorp.progressia.common.world.block.BlockFace; +import ru.windcorp.progressia.client.world.tile.TileRenderSurface; +import ru.windcorp.progressia.common.world.rels.RelFace; -public class TileRenderGrass extends TileRenderSurface { +public class TestTileRenderGrass extends TileRenderSurface { private final Texture topTexture; private final Texture sideTexture; - public TileRenderGrass(String id, Texture top, Texture side) { + public TestTileRenderGrass( + String id, + Texture top, + Texture side + ) { super(id); this.topTexture = top; this.sideTexture = side; } @Override - public Texture getTexture(BlockFace face) { - return (face == BlockFace.TOP) ? topTexture : sideTexture; + public Texture getTexture(RelFace face) { + return (face == RelFace.UP) ? topTexture : sideTexture; } @Override - public boolean isOpaque(BlockFace face) { - return face == BlockFace.TOP; + public boolean isOpaque(RelFace face) { + return face == RelFace.UP; } } diff --git a/src/main/java/ru/windcorp/progressia/test/TestWorldDiskIO.java b/src/main/java/ru/windcorp/progressia/test/TestWorldDiskIO.java index 7d10013..ebfafde 100644 --- a/src/main/java/ru/windcorp/progressia/test/TestWorldDiskIO.java +++ b/src/main/java/ru/windcorp/progressia/test/TestWorldDiskIO.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.test; import java.io.BufferedInputStream; @@ -34,9 +34,9 @@ import org.apache.logging.log4j.Logger; import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.common.state.IOContext; -import ru.windcorp.progressia.common.world.ChunkData; +import ru.windcorp.progressia.common.world.DefaultChunkData; import ru.windcorp.progressia.common.world.DecodingException; -import ru.windcorp.progressia.common.world.WorldData; +import ru.windcorp.progressia.common.world.DefaultWorldData; import ru.windcorp.progressia.common.world.io.ChunkIO; import ru.windcorp.progressia.server.Server; @@ -47,20 +47,34 @@ public class TestWorldDiskIO { private static final boolean ENABLE = false; - public static void saveChunk(ChunkData chunk, Server server) { + public static void saveChunk(DefaultChunkData chunk, Server server) { if (!ENABLE) return; try { - LOG.debug("Saving {} {} {}", chunk.getPosition().x, chunk.getPosition().y, chunk.getPosition().z); + LOG.debug( + "Saving {} {} {}", + chunk.getPosition().x, + chunk.getPosition().y, + chunk.getPosition().z + ); Files.createDirectories(SAVE_DIR); - Path path = SAVE_DIR.resolve(String.format("chunk_%+d_%+d_%+d.progressia_chunk", chunk.getPosition().x, - chunk.getPosition().y, chunk.getPosition().z)); + Path path = SAVE_DIR.resolve( + String.format( + "chunk_%+d_%+d_%+d.progressia_chunk", + chunk.getPosition().x, + chunk.getPosition().y, + chunk.getPosition().z + ) + ); - try (DataOutputStream output = new DataOutputStream( - new DeflaterOutputStream(new BufferedOutputStream(Files.newOutputStream(path))))) { + try ( + DataOutputStream output = new DataOutputStream( + new DeflaterOutputStream(new BufferedOutputStream(Files.newOutputStream(path))) + ) + ) { ChunkIO.save(chunk, output, IOContext.SAVE); writeGenerationHint(chunk, output, server); } @@ -69,49 +83,75 @@ public class TestWorldDiskIO { } } - private static void writeGenerationHint(ChunkData chunk, DataOutputStream output, Server server) - throws IOException { + private static void writeGenerationHint(DefaultChunkData chunk, DataOutputStream output, Server server) + throws IOException { server.getWorld().getGenerator().writeGenerationHint(output, chunk.getGenerationHint()); } - public static ChunkData tryToLoad(Vec3i chunkPos, WorldData world, Server server) { + public static DefaultChunkData tryToLoad(Vec3i chunkPos, DefaultWorldData world, Server server) { if (!ENABLE) return null; - Path path = SAVE_DIR - .resolve(String.format("chunk_%+d_%+d_%+d.progressia_chunk", chunkPos.x, chunkPos.y, chunkPos.z)); + Path path = SAVE_DIR.resolve( + String.format( + "chunk_%+d_%+d_%+d.progressia_chunk", + chunkPos.x, + chunkPos.y, + chunkPos.z + ) + ); if (!Files.exists(path)) { - LOG.debug("Not found {} {} {}", chunkPos.x, chunkPos.y, chunkPos.z); + LOG.debug( + "Not found {} {} {}", + chunkPos.x, + chunkPos.y, + chunkPos.z + ); return null; } try { - ChunkData result = load(path, chunkPos, world, server); + DefaultChunkData result = load(path, chunkPos, world, server); - LOG.debug("Loaded {} {} {}", chunkPos.x, chunkPos.y, chunkPos.z); + LOG.debug( + "Loaded {} {} {}", + chunkPos.x, + chunkPos.y, + chunkPos.z + ); return result; } catch (Exception e) { e.printStackTrace(); - LOG.debug("Could not load {} {} {}", chunkPos.x, chunkPos.y, chunkPos.z); + LOG.debug( + "Could not load {} {} {}", + chunkPos.x, + chunkPos.y, + chunkPos.z + ); return null; } } - private static ChunkData load(Path path, Vec3i chunkPos, WorldData world, Server server) - throws IOException, DecodingException { - try (DataInputStream input = new DataInputStream( - new InflaterInputStream(new BufferedInputStream(Files.newInputStream(path))))) { - ChunkData chunk = ChunkIO.load(world, chunkPos, input, IOContext.SAVE); + private static DefaultChunkData load(Path path, Vec3i chunkPos, DefaultWorldData world, Server server) + throws IOException, + DecodingException { + try ( + DataInputStream input = new DataInputStream( + new InflaterInputStream(new BufferedInputStream(Files.newInputStream(path))) + ) + ) { + DefaultChunkData chunk = ChunkIO.load(world, chunkPos, input, IOContext.SAVE); readGenerationHint(chunk, input, server); return chunk; } } - private static void readGenerationHint(ChunkData chunk, DataInputStream input, Server server) - throws IOException, DecodingException { + private static void readGenerationHint(DefaultChunkData chunk, DataInputStream input, Server server) + throws IOException, + DecodingException { chunk.setGenerationHint(server.getWorld().getGenerator().readGenerationHint(input)); } diff --git a/src/main/java/ru/windcorp/progressia/test/gen/Fields.java b/src/main/java/ru/windcorp/progressia/test/gen/Fields.java new file mode 100644 index 0000000..86741df --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/gen/Fields.java @@ -0,0 +1,321 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ru.windcorp.progressia.test.gen; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Random; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import kdotjpg.opensimplex2.areagen.OpenSimplex2S; +import ru.windcorp.progressia.common.util.namespaces.Namespaced; +import ru.windcorp.progressia.common.world.Coordinates; +import ru.windcorp.progressia.common.world.DefaultChunkData; +import ru.windcorp.progressia.common.world.rels.AbsFace; +import ru.windcorp.progressia.server.world.generation.planet.Planet; +import ru.windcorp.progressia.server.world.generation.surface.SurfaceFloatField; + +public class Fields { + + @FunctionalInterface + public interface Field { + double compute(double x, double y); + } + + public static class FieldSet extends Namespaced implements SurfaceFloatField { + + private final Field[] fields = new Field[AbsFace.getFaces().size()]; + + public FieldSet(String id) { + super(id); + } + + public void put(AbsFace face, Field field) { + fields[face.getId()] = field; + } + + public Field get(AbsFace face) { + return fields[face.getId()]; + } + + @Override + public float get(AbsFace face, float x, float y) { + float offset = DefaultChunkData.CHUNK_RADIUS - 0.5f; + return (float) fields[face.getId()].compute(x - offset, y - offset); + } + + } + + private final Random primitiveRandomizer; + + private final OpenSimplex2S noise; + + private final Map registeredFields = Collections.synchronizedMap(new HashMap<>()); + + private final Logger logger = LogManager.getLogger(getClass()); + + public Fields(long seed) { + this.primitiveRandomizer = new Random(seed); + this.noise = new OpenSimplex2S(seed); + } + + public Field register(String id, AbsFace face, Field f) { + Objects.requireNonNull(f, "f"); + Objects.requireNonNull(face, "face"); + + synchronized (registeredFields) { + FieldSet fieldSet = registeredFields.computeIfAbsent(id, FieldSet::new); + + Field previous = fieldSet.get(face); + if (previous != null) { + throw new IllegalArgumentException( + "Duplicate field definition " + id + ":" + face + " for fields " + f + " and " + previous + ); + } + fieldSet.put(face, f); + + logger.debug("Registering {}:{} in {}", id, face, getClass().getSimpleName()); + } + + return f; + } + + public SurfaceFloatField register(String id, Function fieldGenerator) { + for (AbsFace face : AbsFace.getFaces()) { + register(id, face, fieldGenerator.apply(face)); + } + return get(id); + } + + public SurfaceFloatField register(String id, Supplier fieldGenerator) { + for (AbsFace face : AbsFace.getFaces()) { + register(id, face, fieldGenerator.get()); + } + return get(id); + } + + public SurfaceFloatField get(String id) { + return registeredFields.get(id); + } + + public Field get(String id, AbsFace face) { + return registeredFields.get(id).get(face); + } + + public static Field cutoff(Field f, double outerRadius, double thickness) { + return (x, y) -> { + + double cutoffCoefficient = 1; + cutoffCoefficient *= cutoffFunction(outerRadius - x, thickness); + cutoffCoefficient *= cutoffFunction(outerRadius + x, thickness); + cutoffCoefficient *= cutoffFunction(outerRadius - y, thickness); + cutoffCoefficient *= cutoffFunction(outerRadius + y, thickness); + + if (cutoffCoefficient == 0) { + return 0; + } + + return cutoffCoefficient * f.compute(x, y); + + }; + } + + public static Field cutoff(Field f, Planet planet, double thickness) { + return cutoff(f, planet.getRadius() - Coordinates.CHUNK_SIZE, thickness); + } + + private static double cutoffFunction(double distanceToCutoffPoint, double thickness) { + if (distanceToCutoffPoint < 0) { + return 0; + } else if (distanceToCutoffPoint < thickness) { + double t = distanceToCutoffPoint / thickness; + return (1 - Math.cos(Math.PI * t)) / 2; + } else { + return 1; + } + } + + public Field primitive() { + double xOffset = primitiveRandomizer.nextDouble() * 200 - 100; + double yOffset = primitiveRandomizer.nextDouble() * 200 - 100; + double rotation = primitiveRandomizer.nextDouble() * 2 * Math.PI; + + double sin = Math.sin(rotation); + double cos = Math.cos(rotation); + + return (x, y) -> noise.noise2(x * cos - y * sin + xOffset, x * sin + y * cos + yOffset); + } + + public static Field add(Field a, Field b) { + return (x, y) -> a.compute(x, y) + b.compute(x, y); + } + + public static Field multiply(Field a, Field b) { + return (x, y) -> a.compute(x, y) * b.compute(x, y); + } + + public static Field add(Field... functions) { + return (x, y) -> { + double sum = 0; + for (Field function : functions) { + sum += function.compute(x, y); + } + return sum; + }; + } + + public static Field multiply(Field... functions) { + return (x, y) -> { + double product = 1; + for (Field function : functions) { + product *= function.compute(x, y); + } + return product; + }; + } + + public static Field tweak(Field f, double scale, double amplitude, double bias) { + return (x, y) -> f.compute(x / scale, y / scale) * amplitude + bias; + } + + public static Field tweak(Field f, double scale, double amplitude) { + return tweak(f, scale, amplitude, 0); + } + + public static Field scale(Field f, double scale) { + return tweak(f, scale, 1, 0); + } + + public static Field amplify(Field f, double amplitude) { + return tweak(f, 1, amplitude, 0); + } + + public static Field bias(Field f, double bias) { + return tweak(f, 1, 1, bias); + } + + public static Field octaves(Field f, double scaleFactor, double amplitudeFactor, int octaves) { + return (x, y) -> { + double result = 0; + + double scale = 1; + double amplitude = 1; + + for (int i = 0; i < octaves; ++i) { + result += f.compute(x * scale, y * scale) * amplitude; + scale *= scaleFactor; + amplitude /= amplitudeFactor; + } + + return result; + }; + } + + public static Field octaves(Field f, double factor, int octaves) { + return octaves(f, factor, factor, octaves); + } + + public static Field squash(Field f, double slope) { + return (x, y) -> 1 / (1 + Math.exp(-slope * f.compute(x, y))); + } + + public static Field ridge(Field f) { + return (x, y) -> 1 - Math.abs(f.compute(x, y)); + } + + public static Field clamp(Field f, double min, double max) { + return (x, y) -> Math.min(Math.max(f.compute(x, y), min), max); + } + + public static Field withMin(Field f, double min) { + return (x, y) -> Math.max(f.compute(x, y), min); + } + + public static Field withMax(Field f, double max) { + return (x, y) -> Math.min(f.compute(x, y), max); + } + + public static Field select(Field f, double target, double width) { + return (x, y) -> { + double value = f.compute(x, y); + if (value < target - width) { + return 0; + } else if (value < target) { + return (width - (target - value)) / width; + } else if (value < target + width) { + return (width - (value - target)) / width; + } else { + return 0; + } + }; + } + + public static Field selectPositive(Field f, double target, double width) { + return (x, y) -> { + double value = f.compute(x, y); + if (value < target - width) { + return 0; + } else if (value < target + width) { + return (width - (target - value)) / (2*width); + } else { + return 1; + } + }; + } + + public static Field selectNegative(Field f, double target, double width) { + return (x, y) -> { + double value = target - f.compute(x, y); + if (value < target - width) { + return 0; + } else if (value < target + width) { + return (width - (target - value)) / (2*width); + } else { + return 1; + } + }; + } + + public static Field cliff(Field f, double target, double featureWidth, double slopeWidth) { + return (x, y) -> { + double value = f.compute(x, y); + + if (value < target - featureWidth) { + return 0; + } else if (value < target - slopeWidth) { + double t = (value - (target - featureWidth)) / (featureWidth - slopeWidth); + return -0.5 + Math.cos(t * Math.PI) / 2; + } else if (value < target + slopeWidth) { + double t = (value - (target - slopeWidth)) / (2 * slopeWidth); + return -Math.cos(t * Math.PI); + } else if (value < target + featureWidth) { + double t = (value - (target + slopeWidth)) / (featureWidth - slopeWidth); + return +0.5 + Math.cos(t * Math.PI) / 2; + } else { + return 0; + } + }; + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/gen/MultiblockVegetationFeature.java b/src/main/java/ru/windcorp/progressia/test/gen/MultiblockVegetationFeature.java new file mode 100644 index 0000000..5cfcc43 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/gen/MultiblockVegetationFeature.java @@ -0,0 +1,148 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ru.windcorp.progressia.test.gen; + +import java.util.Set; +import java.util.function.Consumer; + +import com.google.common.collect.ImmutableSet; + +import glm.vec._3.i.Vec3i; +import kdotjpg.opensimplex2.areagen.OpenSimplex2S; +import ru.windcorp.progressia.common.util.VectorUtil; +import ru.windcorp.progressia.common.world.block.BlockData; +import ru.windcorp.progressia.common.world.rels.RelFace; +import ru.windcorp.progressia.server.world.generation.surface.SurfaceFloatField; +import ru.windcorp.progressia.server.world.generation.surface.SurfaceTopLayerFeature; +import ru.windcorp.progressia.server.world.generation.surface.context.SurfaceBlockContext; +import ru.windcorp.progressia.server.world.generation.surface.context.SurfaceWorldContext; + +public abstract class MultiblockVegetationFeature extends SurfaceTopLayerFeature { + + private final SurfaceFloatField selector; + private final OpenSimplex2S wavinessGenerator = new OpenSimplex2S(0); + + private final double maximumDensity; + + private final Set soilWhitelist; + + public MultiblockVegetationFeature(String id, SurfaceFloatField selector, double minimumPeriod) { + super(id); + this.selector = selector; + this.maximumDensity = 1 / minimumPeriod; + + ImmutableSet.Builder soilWhitelistBuilder = ImmutableSet.builder(); + createSoilWhitelist(soilWhitelistBuilder::add); + this.soilWhitelist = soilWhitelistBuilder.build(); + } + + protected void createSoilWhitelist(Consumer output) { + output.accept("Test:Dirt"); + } + + @Override + protected void processTopBlock(SurfaceBlockContext context) { + Vec3i location = context.getLocation(); + + if (location.z < 0) { + return; + } + + if (!soilWhitelist.isEmpty() && !soilWhitelist.contains(context.getBlock().getId())) { + return; + } + + double selectorValue = selector.get(context); + double chance = selectorValue * maximumDensity; + if (context.getRandom().nextDouble() >= chance) { + return; + } + + grow(context, selectorValue); + } + + protected abstract void grow(SurfaceBlockContext context, double selectorValue); + + @Override + protected boolean isSolid(SurfaceBlockContext context) { + return context.logic().getBlock().isSolid(RelFace.UP); + } + + /* + * Utilities + */ + + protected void setLeaves(SurfaceWorldContext context, Vec3i location, BlockData leaves) { + if (context.getBlock(location).getId().equals("Test:Air")) { + context.setBlock(location, leaves); + } + } + + protected void iterateBlob( + Vec3i center, + double horDiameter, + double vertDiameter, + double wavinessAmplitude, + double wavinessScale, + Consumer action + ) { + VectorUtil.iterateCuboidAround( + center.x, + center.y, + center.z, + (int) Math.ceil(horDiameter) / 2 * 2 + 5, + (int) Math.ceil(horDiameter) / 2 * 2 + 5, + (int) Math.ceil(vertDiameter) / 2 * 2 + 5, + pos -> { + + double sx = (pos.x - center.x) / horDiameter; + double sy = (pos.y - center.y) / horDiameter; + double sz = (pos.z - center.z) / vertDiameter; + + double radius = 1; + + if (wavinessAmplitude > 0) { + radius += wavinessAmplitude * wavinessGenerator.noise3_Classic( + sx / wavinessScale, + sy / wavinessScale, + sz / wavinessScale + ); + } + + if (sx * sx + sy * sy + sz * sz <= radius * radius) { + action.accept(pos); + } + + } + ); + } + + protected void iterateSpheroid( + Vec3i center, + double horDiameter, + double vertDiameter, + Consumer action + ) { + iterateBlob(center, horDiameter, vertDiameter, 0, 0, action); + } + + protected void iterateSphere(Vec3i center, double diameter, Consumer action) { + iterateBlob(center, diameter, diameter, 0, 0, action); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/gen/RockLayer.java b/src/main/java/ru/windcorp/progressia/test/gen/RockLayer.java new file mode 100644 index 0000000..5b41ef1 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/gen/RockLayer.java @@ -0,0 +1,57 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ru.windcorp.progressia.test.gen; + +import ru.windcorp.progressia.common.util.noise.discrete.DiscreteNoise; +import ru.windcorp.progressia.common.world.block.BlockData; +import ru.windcorp.progressia.server.world.generation.surface.SurfaceFloatField; +import ru.windcorp.progressia.server.world.generation.surface.TerrainLayer; +import ru.windcorp.progressia.server.world.generation.surface.context.SurfaceBlockContext; + +public class RockLayer implements TerrainLayer { + + private final DiscreteNoise strata; + private final SurfaceFloatField depthOffsets; + + private final double horizontalScale = 200; + private final double verticalScale = 10; + private final double depthInfluense = 0.1; + + public RockLayer(DiscreteNoise strata, SurfaceFloatField depthOffsets) { + this.strata = strata; + this.depthOffsets = depthOffsets; + } + + @Override + public BlockData get(SurfaceBlockContext context, float depth) { + + double z = context.getLocation().z; + z -= depth * depthInfluense; + z += depthOffsets.get(context); + z /= verticalScale; + + return strata + .get( + context.getLocation().x / horizontalScale, + context.getLocation().y / horizontalScale, + z + ) + .get(context, depth); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/gen/TestBushFeature.java b/src/main/java/ru/windcorp/progressia/test/gen/TestBushFeature.java new file mode 100644 index 0000000..ca2765c --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/gen/TestBushFeature.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.test.gen; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.block.BlockData; +import ru.windcorp.progressia.common.world.block.BlockDataRegistry; +import ru.windcorp.progressia.server.world.generation.surface.SurfaceFloatField; +import ru.windcorp.progressia.server.world.generation.surface.context.SurfaceBlockContext; + +public class TestBushFeature extends MultiblockVegetationFeature { + + private final BlockData trunk = BlockDataRegistry.getInstance().get("Test:Log"); + private final BlockData leaves = BlockDataRegistry.getInstance().get("Test:TemporaryLeaves"); + + public TestBushFeature(String id, SurfaceFloatField selector) { + super(id, selector, 7 * 7); + } + + @Override + protected void grow(SurfaceBlockContext context, double selectorValue) { + double size = selectorValue * randomDouble(context, 0.8, 1.2); + + Vec3i center = context.getLocation().add_(0, 0, 1); + + context.setBlock(center, trunk); + context.setBlock(center.add_(0, 0, 1), leaves); + + iterateBlob(center, stretch(size, 1.3, 2.5), stretch(size, 0.6, 1.5), 0.7, 2, p -> { + setLeaves(context, p, leaves); + }); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/gen/TestGenerationConfig.java b/src/main/java/ru/windcorp/progressia/test/gen/TestGenerationConfig.java new file mode 100644 index 0000000..cc1ff8b --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/gen/TestGenerationConfig.java @@ -0,0 +1,128 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ru.windcorp.progressia.test.gen; + +import static ru.windcorp.progressia.test.gen.Fields.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +import ru.windcorp.progressia.common.Units; +import ru.windcorp.progressia.common.util.ArrayFloatRangeMap; +import ru.windcorp.progressia.common.util.FloatRangeMap; +import ru.windcorp.progressia.common.util.noise.discrete.WorleyProceduralNoise; +import ru.windcorp.progressia.common.world.Coordinates; +import ru.windcorp.progressia.common.world.block.BlockData; +import ru.windcorp.progressia.common.world.block.BlockDataRegistry; +import ru.windcorp.progressia.server.Server; +import ru.windcorp.progressia.server.world.generation.WorldGenerator; +import ru.windcorp.progressia.server.world.generation.planet.Planet; +import ru.windcorp.progressia.server.world.generation.planet.PlanetGenerator; +import ru.windcorp.progressia.server.world.generation.surface.SurfaceFeature; +import ru.windcorp.progressia.server.world.generation.surface.SurfaceFloatField; +import ru.windcorp.progressia.server.world.generation.surface.TerrainLayer; +import ru.windcorp.progressia.test.Rocks.RockVariant; +import ru.windcorp.progressia.test.TestContent; + +public class TestGenerationConfig { + + private static final long SEED = "No bugs please".hashCode(); + + private static final float PLANET_RADIUS = Units.get("0.5 km"); + private static final float SURFACE_GRAVITY = Units.get("9.8 m/s^2"); + private static final float CURVATURE = Units.get("100 m"); + private static final float INNER_RADIUS = Units.get("200 m"); + + private static final Fields FIELDS = new Fields(SEED); + + public static Function createGenerator() { + + Planet planet = new Planet( + ((int) PLANET_RADIUS) / Coordinates.CHUNK_SIZE, + SURFACE_GRAVITY, + CURVATURE, + INNER_RADIUS + ); + + TestHeightMap heightMap = new TestHeightMap(planet, planet.getRadius() / 4, FIELDS); + + FloatRangeMap layers = new ArrayFloatRangeMap<>(); + registerTerrainLayers(layers); + + List features = new ArrayList<>(); + registerFeatures(features); + + return server -> new PlanetGenerator("Test:PlanetGenerator", server, planet, heightMap, layers, features); + + } + + private static void registerTerrainLayers(FloatRangeMap layers) { + BlockData dirt = BlockDataRegistry.getInstance().get("Test:Dirt"); + BlockData air = BlockDataRegistry.getInstance().get("Test:Air"); + + SurfaceFloatField cliffs = FIELDS.get("Test:CliffSelector"); + + WorleyProceduralNoise.Builder builder = WorleyProceduralNoise.builder(); + TestContent.ROCKS.getRocks().forEach(rock -> { + builder.add((c, d) -> { + if (c.getRandom().nextInt(3) == 0) { + return rock.getBlock(RockVariant.CRACKED); + } else { + return rock.getBlock(RockVariant.MONOLITH); + } + }, 1); + }); + SurfaceFloatField rockDepthOffsets = FIELDS.register( + "Test:RockDepthOffsets", + () -> tweak(FIELDS.primitive(), 40, 5) + ); + RockLayer rockLayer = new RockLayer(builder.build(SEED), rockDepthOffsets); + + layers.put(Float.NEGATIVE_INFINITY, 0, (c, d) -> air); + layers.put(0, 4, (c, d) -> { + if (cliffs.get(c.getSurface().getUp(), c.getLocation().x, c.getLocation().y) > 0) { + return rockLayer.get(c, d); + } else { + return dirt; + } + }); + layers.put(4, Float.POSITIVE_INFINITY, rockLayer); + } + + private static void registerFeatures(List features) { + + SurfaceFloatField forestiness = FIELDS.register( + "Test:Forestiness", + () -> squash(scale(FIELDS.primitive(), 200), 5) + ); + + SurfaceFloatField floweriness = FIELDS.register( + "Test:Floweriness", + f -> multiply( + scale(octaves(FIELDS.primitive(), 2, 2), 40), + tweak(FIELDS.get("Test:Forestiness", f), 1, -1, 1.1) + ) + ); + + features.add(new TestBushFeature("Test:BushFeature", forestiness)); + features.add(new TestTreeFeature("Test:TreeFeature", forestiness)); + features.add(new TestGrassFeature("Test:GrassFeature", FIELDS.get("Test:CliffSelector"), floweriness)); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/gen/TestGrassFeature.java b/src/main/java/ru/windcorp/progressia/test/gen/TestGrassFeature.java new file mode 100644 index 0000000..d1d73e5 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/gen/TestGrassFeature.java @@ -0,0 +1,122 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ru.windcorp.progressia.test.gen; + +import java.util.List; +import java.util.Set; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; + +import ru.windcorp.progressia.common.world.rels.RelFace; +import ru.windcorp.progressia.common.world.tile.TileData; +import ru.windcorp.progressia.common.world.tile.TileDataRegistry; +import ru.windcorp.progressia.server.world.generation.surface.SurfaceFloatField; +import ru.windcorp.progressia.server.world.generation.surface.SurfaceTopLayerFeature; +import ru.windcorp.progressia.server.world.generation.surface.context.SurfaceBlockContext; + +public class TestGrassFeature extends SurfaceTopLayerFeature { + + private static final Set WHITELIST = ImmutableSet.of( + "Test:Dirt", + "Test:Stone", + "Test:GraniteMonolith", + "Test:GraniteCracked", + "Test:GraniteGravel" + ); + + private final SurfaceFloatField grassiness; + private final SurfaceFloatField floweriness; + private final double scatterDensity = 1.0 / (3*3); + + private final TileData grass = TileDataRegistry.getInstance().get("Test:Grass"); + private final List flowers = ImmutableList.of( + TileDataRegistry.getInstance().get("Test:YellowFlowers") + ); + private final List scatter = ImmutableList.of( + TileDataRegistry.getInstance().get("Test:Stones"), + TileDataRegistry.getInstance().get("Test:Sand") + ); + + public TestGrassFeature(String id, SurfaceFloatField grassiness, SurfaceFloatField floweriness) { + super(id); + this.grassiness = grassiness; + this.floweriness = floweriness; + } + + @Override + protected void processTopBlock(SurfaceBlockContext context) { + if (context.getLocation().z < 0) { + return; + } + if (!WHITELIST.contains(context.getBlock().getId())) { + return; + } + + if (!context.pushRelative(RelFace.UP).logic().getBlock().isTransparent()) { + context.pop(); + return; + } + context.pop(); + + double grassiness = this.grassiness.get(context); + if (grassiness < 0.2) { + growGrass(context); + } + + placeScatter(context); + + if (grassiness < 0.2) { + growFlowers(context); + } + } + + private void placeScatter(SurfaceBlockContext context) { + if (context.getRandom().nextDouble() < scatterDensity) { + TileData tile = pickRandom(context, scatter); + context.addTile(RelFace.UP, tile); + } + } + + private void growGrass(SurfaceBlockContext context) { + for (RelFace face : RelFace.getFaces()) { + if (face == RelFace.DOWN) continue; + + if (context.pushRelative(face).logic().getBlock().isTransparent()) { + context.pop(); + context.addTile(face, grass); + } else { + context.pop(); + } + + } + } + + private void growFlowers(SurfaceBlockContext context) { + if (context.getRandom().nextDouble() < floweriness.get(context)) { + TileData tile = pickRandom(context, flowers); + context.addTile(RelFace.UP, tile); + } + } + + @Override + protected boolean isSolid(SurfaceBlockContext context) { + return context.logic().getBlock().isSolid(RelFace.UP); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/gen/TestGravityModel.java b/src/main/java/ru/windcorp/progressia/test/gen/TestGravityModel.java new file mode 100644 index 0000000..a2a8a8d --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/gen/TestGravityModel.java @@ -0,0 +1,57 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ru.windcorp.progressia.test.gen; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import glm.vec._3.Vec3; +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.DecodingException; +import ru.windcorp.progressia.common.world.GravityModel; +import ru.windcorp.progressia.common.world.rels.AbsFace; + +public class TestGravityModel extends GravityModel { + + public TestGravityModel(String id) { + super(id); + } + + @Override + protected void doGetGravity(Vec3 pos, Vec3 output) { + output.set(0, 0, -9.8f); + } + + @Override + protected AbsFace doGetDiscreteUp(Vec3i chunkPos) { + AbsFace rounded = AbsFace.roundToFace(chunkPos.x, chunkPos.y, chunkPos.z - 54); + return rounded == null ? AbsFace.POS_Z : rounded; + } + + @Override + protected void doReadSettings(DataInput input) throws IOException, DecodingException { + // Do nothing + } + + @Override + protected void doWriteSettings(DataOutput output) throws IOException { + // Do nothing + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/gen/TestHeightMap.java b/src/main/java/ru/windcorp/progressia/test/gen/TestHeightMap.java new file mode 100644 index 0000000..c09aec1 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/gen/TestHeightMap.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.test.gen; + +import static ru.windcorp.progressia.test.gen.Fields.*; + +import ru.windcorp.progressia.common.world.rels.AbsFace; +import ru.windcorp.progressia.server.world.generation.planet.Planet; +import ru.windcorp.progressia.server.world.generation.surface.SurfaceFloatField; + +public class TestHeightMap implements SurfaceFloatField { + + private final SurfaceFloatField shape; + + public TestHeightMap( + Planet planet, + float cutoffThickness, + Fields fields + ) { + + for (AbsFace face : AbsFace.getFaces()) { + + Field landmassDistribution = scale(octaves(fields.primitive(), 2, 5), 400); + Field landmasses = tweak(squash(landmassDistribution, 4), 1, 20, -20); + + Field plainsSelector = squash(withMin(landmassDistribution, 0), 10); + Field plains = tweak(octaves(fields.primitive(), 2, 3), 100, 3); + + Field randomCliffSelector = scale(fields.primitive(), 200); + randomCliffSelector = add( + select(randomCliffSelector, +0.7, 0.3), + amplify(select(randomCliffSelector, -0.7, 0.3), -1) + ); + Field randomCliffs = octaves(scale(fields.primitive(), 300), 3, 5); + + Field shoreCliffSelector = withMin(scale(fields.primitive(), 200), 0); + Field shoreCliffs = add( + landmassDistribution, + tweak(octaves(fields.primitive(), 2, 3), 50, 0.2) + ); + + fields.register("Test:CliffSelector", face, multiply( + shoreCliffSelector, + bias(select(shoreCliffs, 0, 0.07), 0) + )); + + fields.register("Test:Height", face, cutoff(add( + landmasses, + multiply(plains, plainsSelector), + multiply(amplify(cliff(randomCliffs, 0, 0.5, 0.03), 10), randomCliffSelector), + multiply(tweak(cliff(shoreCliffs, 0, 0.5, 0.03), 1, 15, 15), shoreCliffSelector) + ), planet, cutoffThickness)); + + } + + this.shape = fields.get("Test:Height"); + } + + @Override + public float get(AbsFace face, float x, float y) { + return (float) shape.get(face, x, y); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/gen/TestTerrainGenerator.java b/src/main/java/ru/windcorp/progressia/test/gen/TestTerrainGenerator.java deleted file mode 100644 index 851845a..0000000 --- a/src/main/java/ru/windcorp/progressia/test/gen/TestTerrainGenerator.java +++ /dev/null @@ -1,120 +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.test.gen; - -import kdotjpg.opensimplex2.areagen.OpenSimplex2S; -import ru.windcorp.progressia.server.world.WorldLogic; - -class TestTerrainGenerator { - - @FunctionalInterface - private interface Func2D { - double compute(double x, double y); - } - - private final OpenSimplex2S noise; - private final Func2D shape; - - public TestTerrainGenerator(TestWorldGenerator testWorldGenerator, WorldLogic world) { - this.noise = new OpenSimplex2S("We're getting somewhere".hashCode()); - - Func2D plainsHeight = tweak(octaves(tweak(primitive(), 0.01, 0.5), 2, 3), 1, 0.2, 0.2); - - Func2D mountainsHeight = tweak(octaves(ridge(tweak(primitive(), 0.01, 1)), 2, 1.5, 12), 1, 3); - - Func2D mountainousity = tweak(octaves(tweak(primitive(), 0.007, 1), 2, 3), 1, 1, -0.25); - - shape = tweak(add(multiply(squash(mountainousity, 10), mountainsHeight), plainsHeight), 0.001, 1000, 0); - } - - public void compute(int startX, int startY, double[][] heightMap, double[][] slopeMap) { - for (int x = 0; x < heightMap.length; ++x) { - for (int y = 0; y < heightMap.length; ++y) { - heightMap[x][y] = shape.compute(x + startX, y + startY); - slopeMap[x][y] = computeSlope(shape, x + startX, y + startY, heightMap[x][y]); - } - } - } - - private double computeSlope(Func2D f, double x0, double y0, double f0) { - double di = 0.5; - - double dfdx = (f.compute(x0 + di, y0) - f0) / di; - double dfdy = (f.compute(x0, y0 + di) - f0) / di; - - return Math.hypot(dfdx, dfdy); - } - - /* - * Utility functions - */ - - private Func2D primitive() { - return noise::noise2; - } - - private Func2D add(Func2D a, Func2D b) { - return (x, y) -> a.compute(x, y) + b.compute(x, y); - } - - private Func2D multiply(Func2D a, Func2D b) { - return (x, y) -> a.compute(x, y) * b.compute(x, y); - } - - private Func2D tweak(Func2D f, double scale, double amplitude, double bias) { - return (x, y) -> f.compute(x * scale, y * scale) * amplitude + bias; - } - - private Func2D tweak(Func2D f, double scale, double amplitude) { - return tweak(f, scale, amplitude, 0); - } - - private Func2D octaves(Func2D f, double scaleFactor, double amplitudeFactor, int octaves) { - return (x, y) -> { - double result = 0; - - double scale = 1; - double amplitude = 1; - - for (int i = 0; i < octaves; ++i) { - result += f.compute(x * scale, y * scale) * amplitude; - scale *= scaleFactor; - amplitude /= amplitudeFactor; - } - - return result; - }; - } - - private Func2D octaves(Func2D f, double factor, int octaves) { - return octaves(f, factor, factor, octaves); - } - - private Func2D squash(Func2D f, double slope) { - return (x, y) -> 1 / (1 + Math.exp(-slope * f.compute(x, y))); - } - - private Func2D ridge(Func2D f) { - return (x, y) -> { - double result = 1 - Math.abs(f.compute(x, y)); - return result * result; - }; - } - -} diff --git a/src/main/java/ru/windcorp/progressia/test/gen/TestTreeFeature.java b/src/main/java/ru/windcorp/progressia/test/gen/TestTreeFeature.java new file mode 100644 index 0000000..d08862e --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/gen/TestTreeFeature.java @@ -0,0 +1,69 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ru.windcorp.progressia.test.gen; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.block.BlockData; +import ru.windcorp.progressia.common.world.block.BlockDataRegistry; +import ru.windcorp.progressia.server.world.generation.surface.SurfaceFloatField; +import ru.windcorp.progressia.server.world.generation.surface.context.SurfaceBlockContext; + +public class TestTreeFeature extends MultiblockVegetationFeature { + + private final BlockData trunk = BlockDataRegistry.getInstance().get("Test:Log"); + private final BlockData leaves = BlockDataRegistry.getInstance().get("Test:TemporaryLeaves"); + + public TestTreeFeature(String id, SurfaceFloatField selector) { + super(id, selector, 10 * 10); + } + + @Override + protected void grow(SurfaceBlockContext context, double selectorValue) { + + Vec3i start = context.getLocation().add_(0, 0, 1); + Vec3i center = start.add_(0); + + double size = selectorValue * randomDouble(context, 0.8, 1.2); + + int height = (int) stretch(size, 3, 7); + for (; center.z < start.z + height; ++center.z) { + context.setBlock(center, trunk); + } + + double branchHorDistance = 0; + + do { + double branchSize = 0.5 + randomDouble(context, 1, 2) * size; + double branchHorAngle = randomDouble(context, 0, 2 * Math.PI); + int branchVertOffset = (int) randomDouble(context, -2, 0); + + Vec3i branchCenter = center.add_( + (int) (Math.sin(branchHorAngle) * branchHorDistance), + (int) (Math.cos(branchHorAngle) * branchHorDistance), + branchVertOffset + ); + + iterateBlob(branchCenter, 1 * branchSize, 2.3 * branchSize, 0.5, 3, p -> { + setLeaves(context, p, leaves); + }); + + branchHorDistance = randomDouble(context, 0.7, 1.5); + } while (context.getRandom().nextInt(8) > 1); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/gen/TestWorldGenerator.java b/src/main/java/ru/windcorp/progressia/test/gen/TestWorldGenerator.java deleted file mode 100644 index de9e38d..0000000 --- a/src/main/java/ru/windcorp/progressia/test/gen/TestWorldGenerator.java +++ /dev/null @@ -1,407 +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.test.gen; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.Random; - -import org.apache.logging.log4j.LogManager; - -import glm.vec._3.Vec3; -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.ChunkDataListener; -import ru.windcorp.progressia.common.world.Coordinates; -import ru.windcorp.progressia.common.world.DecodingException; -import ru.windcorp.progressia.common.world.WorldData; -import ru.windcorp.progressia.common.world.WorldDataListener; -import ru.windcorp.progressia.common.world.block.BlockData; -import ru.windcorp.progressia.common.world.block.BlockDataRegistry; -import ru.windcorp.progressia.common.world.block.BlockFace; -import ru.windcorp.progressia.common.world.tile.TileData; -import ru.windcorp.progressia.common.world.tile.TileDataRegistry; -import ru.windcorp.progressia.server.world.WorldLogic; -import ru.windcorp.progressia.server.world.generation.AbstractWorldGenerator; -import ru.windcorp.progressia.test.TestEntityDataFallingBlock; -import ru.windcorp.progressia.test.TestEntityLogicFallingBlock; - -public class TestWorldGenerator extends AbstractWorldGenerator { - - private final TestTerrainGenerator terrainGen; - - public TestWorldGenerator(WorldLogic world) { - super("Test:WorldGenerator", Boolean.class); - this.terrainGen = new TestTerrainGenerator(this, world); - - world.getData().addListener(new WorldDataListener() { - @Override - public void onChunkLoaded(WorldData world, ChunkData chunk) { - findAndPopulate(chunk.getPosition(), world); - chunk.addListener(new ChunkDataListener() { // Falling Block - // spawning logic - @Override - public void onChunkBlockChanged(ChunkData chunk, Vec3i blockInChunk, BlockData previous, - BlockData current) { - Vec3i chunkWorldPos = new Vec3i(0,0,0); - Coordinates.getInWorld(chunk.getPosition(), blockInChunk, chunkWorldPos); //chunk.getPosition().mul_(16).add_(blockInChunk); - - if (TestEntityLogicFallingBlock.FallingBlocks - .contains(chunk.getWorld().getBlock(chunkWorldPos.add_(0, 0, 1)).getId())) { - chunk.getWorld().setBlock(chunkWorldPos.add_(0, 0, 1), BlockDataRegistry.getInstance() - .get(chunk.getWorld().getBlock(chunkWorldPos.add_(0, 0, 1)).getId()), true); - } - if (!TestEntityLogicFallingBlock.FallingBlocks.contains(current.getId())) { - return; - } - if (chunk.getWorld().getBlock(chunkWorldPos.add_(0, 0, -1)).getId() == "Test:Air") { - LogManager.getLogger().info("Inserting FallingBlock"); - - TestEntityDataFallingBlock fallingBlock = new TestEntityDataFallingBlock(); - - Vec3i worldPos = chunk.getPosition().mul_(16).add_(blockInChunk); - Vec3 floatWorldPos = new Vec3(worldPos.x, worldPos.y, worldPos.z); - fallingBlock.setPosition(floatWorldPos); - - fallingBlock.setEntityId(("Test:FallingBlock" + floatWorldPos.toString() - + String.valueOf(new Random().nextFloat())).hashCode()); - - chunk.getWorld().addEntity(fallingBlock); - - chunk.setBlock(blockInChunk, previous, false); - - LogManager.getLogger().info(String.valueOf(chunkWorldPos.x) + " " - + String.valueOf(chunkWorldPos.y) + " " + String.valueOf(chunkWorldPos.z)); - } - } - }); - } - }); - } - - @Override - protected Boolean doReadGenerationHint(DataInputStream input) throws IOException, DecodingException { - return input.readBoolean(); - } - - @Override - protected void doWriteGenerationHint(DataOutputStream output, Boolean hint) throws IOException { - output.writeBoolean(hint); - } - - @Override - protected boolean checkIsChunkReady(Boolean hint) { - return hint; - } - - @Override - public ChunkData generate(Vec3i chunkPos, WorldData world) { - ChunkData chunk = generateUnpopulated(chunkPos, world); - world.addChunk(chunk); - return chunk; - } - - private ChunkData generateUnpopulated(Vec3i chunkPos, WorldData world) { - ChunkData chunk = new ChunkData(chunkPos, world); - chunk.setGenerationHint(false); - - final int bpc = ChunkData.BLOCKS_PER_CHUNK; - Random random = new Random(chunkPos.x + chunkPos.y + chunkPos.z); - - BlockData dirt = BlockDataRegistry.getInstance().get("Test:Dirt"); - BlockData stone = BlockDataRegistry.getInstance().get("Test:Stone"); - BlockData air = BlockDataRegistry.getInstance().get("Test:Air"); - - BlockData[] granites = new BlockData[] { BlockDataRegistry.getInstance().get("Test:GraniteGravel"), - BlockDataRegistry.getInstance().get("Test:GraniteGravel"), - BlockDataRegistry.getInstance().get("Test:GraniteCracked"), - BlockDataRegistry.getInstance().get("Test:GraniteMonolith") }; - - double[][] heightMap = new double[bpc][bpc]; - double[][] gradMap = new double[bpc][bpc]; - - int startX = Coordinates.getInWorld(chunk.getX(), 0); - int startY = Coordinates.getInWorld(chunk.getY(), 0); - int startZ = Coordinates.getInWorld(chunk.getZ(), 0); - - terrainGen.compute(startX, startY, heightMap, gradMap); - - VectorUtil.iterateCuboid(0, 0, 0, bpc, bpc, bpc, pos -> { - double layer = pos.z - heightMap[pos.x][pos.y] + startZ; - - if (layer < -4) { - chunk.setBlock(pos, stone, false); - } else if (layer < 0) { - if (gradMap[pos.x][pos.y] > 0.5) { - BlockData granite = granites[random.nextInt(4)]; - chunk.setBlock(pos, granite, false); - } else { - chunk.setBlock(pos, dirt, false); - } - } else { - chunk.setBlock(pos, air, false); - } - }); - - return chunk; - } - - private void findAndPopulate(Vec3i changePos, WorldData world) { - VectorUtil.iterateCuboidAround(changePos, 3, candidatePos -> { - if (canBePopulated(candidatePos, world)) { - populate(candidatePos, world); - } - }); - } - - private boolean canBePopulated(Vec3i candidatePos, WorldData world) { - Vec3i cursor = Vectors.grab3i(); - - ChunkData candidate = world.getChunk(candidatePos); - if (candidate == null || isChunkReady(candidate.getGenerationHint())) - return false; - - for (int dx = -1; dx <= 1; ++dx) { - cursor.x = candidatePos.x + dx; - for (int dy = -1; dy <= 1; ++dy) { - cursor.y = candidatePos.y + dy; - for (int dz = -1; dz <= 1; ++dz) { - - if ((dx | dy | dz) == 0) - continue; - - cursor.z = candidatePos.z + dz; - - ChunkData chunk = world.getChunk(cursor); - if (chunk == null) { - return false; - } - - } - } - } - - Vectors.release(cursor); - return true; - } - - private void populate(Vec3i chunkPos, WorldData world) { - Random random = new Random(chunkPos.x + chunkPos.y + chunkPos.z); - - ChunkData chunk = world.getChunk(chunkPos); - assert chunk != null : "Something went wrong when populating chunk at (" + chunkPos.x + "; " + chunkPos.y + "; " - + chunkPos.z + ")"; - - BlockData air = BlockDataRegistry.getInstance().get("Test:Air"); - BlockData dirt = BlockDataRegistry.getInstance().get("Test:Dirt"); - - Vec3i biw = new Vec3i(); - - int minX = chunk.getMinX(); - int maxX = chunk.getMaxX() + 1; - int minY = chunk.getMinY(); - int maxY = chunk.getMaxY() + 1; - int minZ = chunk.getMinZ(); - int maxZ = chunk.getMaxZ() + 1; - - final int bpc = ChunkData.BLOCKS_PER_CHUNK; - double[][] heightMap = new double[bpc][bpc]; - double[][] gradMap = new double[bpc][bpc]; - - terrainGen.compute(minX, minY, heightMap, gradMap); - - for (biw.x = minX; biw.x < maxX; ++biw.x) { - for (biw.y = minY; biw.y < maxY; ++biw.y) { - - for (biw.z = minZ; biw.z < maxZ + 1 && world.getBlock(biw) != air; ++biw.z) - ; - biw.z -= 1; - - if (biw.z == maxZ) - continue; - if (biw.z < minZ) - continue; - - int xic = Coordinates.convertInWorldToInChunk(biw.x); - int yic = Coordinates.convertInWorldToInChunk(biw.y); - - addTiles(chunk, biw, world, random, world.getBlock(biw) == dirt, heightMap[xic][yic], - gradMap[xic][yic]); - - } - } - - chunk.setGenerationHint(true); - } - - private void addTiles(ChunkData chunk, Vec3i biw, WorldData world, Random random, boolean isDirt, double height, - double grad) { - if (isDirt) - addGrass(chunk, biw, world, random); - addDecor(chunk, biw, world, random, isDirt); - addSnow(chunk, biw, world, random, isDirt, height, grad); - } - - private void addGrass(ChunkData chunk, Vec3i biw, WorldData world, Random random) { - BlockData air = BlockDataRegistry.getInstance().get("Test:Air"); - TileData grass = TileDataRegistry.getInstance().get("Test:Grass"); - - world.getTiles(biw, BlockFace.TOP).add(grass); - - for (BlockFace face : BlockFace.getFaces()) { - if (face.getVector().z != 0) - continue; - biw.add(face.getVector()); - - if (world.getBlock(biw) == air) { - biw.sub(face.getVector()); - world.getTiles(biw, face).add(grass); - } else { - biw.sub(face.getVector()); - } - } - } - - private void addDecor(ChunkData chunk, Vec3i biw, WorldData world, Random random, boolean isDirt) { - if (isDirt) { - if (random.nextInt(8) == 0) { - world.getTiles(biw, BlockFace.TOP).addFarthest(TileDataRegistry.getInstance().get("Test:Sand")); - } - - if (random.nextInt(8) == 0) { - world.getTiles(biw, BlockFace.TOP).addFarthest(TileDataRegistry.getInstance().get("Test:Stones")); - } - - if (random.nextInt(8) == 0) { - world.getTiles(biw, BlockFace.TOP) - .addFarthest(TileDataRegistry.getInstance().get("Test:YellowFlowers")); - } - } else { - if (random.nextInt(2) == 0) { - world.getTiles(biw, BlockFace.TOP).addFarthest(TileDataRegistry.getInstance().get("Test:Stones")); - } - } - } - - private void addSnow(ChunkData chunk, Vec3i biw, WorldData world, Random random, boolean isDirt, double height, - double grad) { - if (height < 1500) - return; - - BlockData air = BlockDataRegistry.getInstance().get("Test:Air"); - - double quarterChance = computeSnowQuarterChance(height, grad); - double halfChance = computeSnowHalfChance(height, grad); - double opaqueChance = computeSnowOpaqueChance(height, grad); - - for (BlockFace face : BlockFace.getFaces()) { - if (face == BlockFace.BOTTOM) - continue; - - if (face.getVector().z == 0) { - biw.add(face.getVector()); - BlockData neighbour = world.getBlock(biw); - biw.sub(face.getVector()); - - if (neighbour != air) - continue; - } - - TileData tile; - - double maxValue = height > 3000 ? 3 : (1 + 2 * ((height - 1500) / 1500)); - double value = random.nextDouble() * maxValue; - if (value < quarterChance) { - tile = TileDataRegistry.getInstance().get("Test:SnowQuarter"); - } else if ((value -= quarterChance) < halfChance) { - tile = TileDataRegistry.getInstance().get("Test:SnowHalf"); - } else if ((value -= halfChance) < opaqueChance) { - tile = TileDataRegistry.getInstance().get("Test:SnowOpaque"); - } else { - tile = null; - } - - if (tile != null) { - world.getTiles(biw, face).addFarthest(tile); - } - } - } - - private double computeSnowQuarterChance(double height, double grad) { - double heightCoeff; - - if (height < 1500) - heightCoeff = 0; - else if (height < 2000) - heightCoeff = (height - 1500) / 500; - else - heightCoeff = 1; - - if (heightCoeff < 1e-4) - return 0; - - double gradCoeff = computeSnowGradCoeff(height, grad); - return heightCoeff * gradCoeff; - } - - private double computeSnowHalfChance(double height, double grad) { - double heightCoeff; - - if (height < 2000) - heightCoeff = 0; - else if (height < 2500) - heightCoeff = (height - 2000) / 500; - else - heightCoeff = 1; - - if (heightCoeff < 1e-4) - return 0; - - double gradCoeff = computeSnowGradCoeff(height, grad); - return heightCoeff * gradCoeff; - } - - private double computeSnowOpaqueChance(double height, double grad) { - double heightCoeff; - - if (height < 2500) - heightCoeff = 0; - else if (height < 3000) - heightCoeff = (height - 2500) / 500; - else - heightCoeff = 1; - - if (heightCoeff < 1e-4) - return 0; - - double gradCoeff = computeSnowGradCoeff(height, grad); - return heightCoeff * gradCoeff; - } - - private double computeSnowGradCoeff(double height, double grad) { - final double a = -0.00466666666666667; - final double b = 12.66666666666667; - double characteristicGrad = 1 / (a * height + b); - return Math.exp(-grad / characteristicGrad); - } - -} diff --git a/src/main/resources/assets/languages/en-US.lang b/src/main/resources/assets/languages/en-US.lang index 2697fda..e2ff70b 100644 --- a/src/main/resources/assets/languages/en-US.lang +++ b/src/main/resources/assets/languages/en-US.lang @@ -6,14 +6,12 @@ LayerAbout.DebugHint = Debug GUI: F3 LayerTestGUI.IsFlyingDisplay = Flying: %5s (Space bar x2) LayerTestGUI.IsSprintingDisplay = Sprinting: %5s (W x2) -LayerTestGUI.IsMouseCapturedDisplay = Mouse captured: %5s (Esc) LayerTestGUI.CameraModeDisplay = Camera mode: %5d (F5) -LayerTestGUI.GravityModeDisplay = Gravity: %9s (G) LayerTestGUI.LanguageDisplay = Language: %5s (L) LayerTestGUI.FPSDisplay = FPS: LayerTestGUI.TPSDisplay = TPS: LayerTestGUI.TPSDisplay.NA = TPS: n/a -LayerTestGUI.ChunkUpdatesDisplay = Pending updates: +LayerTestGUI.ChunkStatsDisplay = Chunks vis/pnd/load: LayerTestGUI.PosDisplay = Pos: LayerTestGUI.PosDisplay.NA.Client = Pos: client n/a LayerTestGUI.PosDisplay.NA.Entity = Pos: entity n/a @@ -21,4 +19,6 @@ LayerTestGUI.SelectedBlockDisplay = %s Block: %s LayerTestGUI.SelectedTileDisplay = %s Tile: %s LayerTestGUI.PlacementModeHint = (Blocks %s Tiles: Ctrl + Mouse Wheel) LayerTestGUI.IsFullscreen = Fullscreen: %5s (F11) -LayerTestGUI.IsVSync = VSync: %5s (F12) \ No newline at end of file +LayerTestGUI.IsVSync = VSync: %5s (F12) + +LayerButtonTest.Title = Button Test \ No newline at end of file diff --git a/src/main/resources/assets/languages/ru-RU.lang b/src/main/resources/assets/languages/ru-RU.lang index 1b67b24..97935d6 100644 --- a/src/main/resources/assets/languages/ru-RU.lang +++ b/src/main/resources/assets/languages/ru-RU.lang @@ -6,14 +6,12 @@ LayerAbout.DebugHint = Отладочный GUI: F3 LayerTestGUI.IsFlyingDisplay = Полёт: %5s (Пробел x2) LayerTestGUI.IsSprintingDisplay = Бег: %5s (W x2) -LayerTestGUI.IsMouseCapturedDisplay = Захват мыши: %5s (Esc) LayerTestGUI.CameraModeDisplay = Камера: %5d (F5) -LayerTestGUI.GravityModeDisplay = Гравитация: %9s (G) LayerTestGUI.LanguageDisplay = Язык: %5s (L) LayerTestGUI.FPSDisplay = FPS: LayerTestGUI.TPSDisplay = TPS: LayerTestGUI.TPSDisplay.NA = TPS: н/д -LayerTestGUI.ChunkUpdatesDisplay = Обновления в очереди: +LayerTestGUI.ChunkStatsDisplay = Чанки вид/очр/загр: LayerTestGUI.PosDisplay = Поз: LayerTestGUI.PosDisplay.NA.Client = Поз: клиент н/д LayerTestGUI.PosDisplay.NA.Entity = Поз: сущность н/д @@ -21,4 +19,6 @@ LayerTestGUI.SelectedBlockDisplay = %s Блок: %s LayerTestGUI.SelectedTileDisplay = %s Плитка: %s LayerTestGUI.PlacementModeHint = (Блок %s плитки: Ctrl + прокрутка) LayerTestGUI.IsFullscreen = Полный экран: %5s (F11) -LayerTestGUI.IsVSync = Верт. синхр.: %5s (F12) \ No newline at end of file +LayerTestGUI.IsVSync = Верт. синхр.: %5s (F12) + +LayerButtonTest.Title = Тест Кнопок \ No newline at end of file diff --git a/src/main/resources/assets/textures/blocks/BlackGraniteCracked.png b/src/main/resources/assets/textures/blocks/BlackGraniteCracked.png new file mode 100644 index 0000000..5d3afa4 Binary files /dev/null and b/src/main/resources/assets/textures/blocks/BlackGraniteCracked.png differ diff --git a/src/main/resources/assets/textures/blocks/BlackGraniteGravel.png b/src/main/resources/assets/textures/blocks/BlackGraniteGravel.png new file mode 100644 index 0000000..bd49f48 Binary files /dev/null and b/src/main/resources/assets/textures/blocks/BlackGraniteGravel.png differ diff --git a/src/main/resources/assets/textures/blocks/BlackGraniteMonolith.png b/src/main/resources/assets/textures/blocks/BlackGraniteMonolith.png new file mode 100644 index 0000000..2aff002 Binary files /dev/null and b/src/main/resources/assets/textures/blocks/BlackGraniteMonolith.png differ diff --git a/src/main/resources/assets/textures/blocks/BlackGraniteSand.png b/src/main/resources/assets/textures/blocks/BlackGraniteSand.png new file mode 100644 index 0000000..71b867e Binary files /dev/null and b/src/main/resources/assets/textures/blocks/BlackGraniteSand.png differ diff --git a/src/main/resources/assets/textures/blocks/DolomiteCracked.png b/src/main/resources/assets/textures/blocks/DolomiteCracked.png new file mode 100644 index 0000000..46feff6 Binary files /dev/null and b/src/main/resources/assets/textures/blocks/DolomiteCracked.png differ diff --git a/src/main/resources/assets/textures/blocks/DolomiteGravel.png b/src/main/resources/assets/textures/blocks/DolomiteGravel.png new file mode 100644 index 0000000..7b28fa6 Binary files /dev/null and b/src/main/resources/assets/textures/blocks/DolomiteGravel.png differ diff --git a/src/main/resources/assets/textures/blocks/DolomiteMonolith.png b/src/main/resources/assets/textures/blocks/DolomiteMonolith.png new file mode 100644 index 0000000..0cdc259 Binary files /dev/null and b/src/main/resources/assets/textures/blocks/DolomiteMonolith.png differ diff --git a/src/main/resources/assets/textures/blocks/DolomiteSand.png b/src/main/resources/assets/textures/blocks/DolomiteSand.png new file mode 100644 index 0000000..b419bcf Binary files /dev/null and b/src/main/resources/assets/textures/blocks/DolomiteSand.png differ diff --git a/src/main/resources/assets/textures/blocks/EclogiteCracked.png b/src/main/resources/assets/textures/blocks/EclogiteCracked.png new file mode 100644 index 0000000..585efcf Binary files /dev/null and b/src/main/resources/assets/textures/blocks/EclogiteCracked.png differ diff --git a/src/main/resources/assets/textures/blocks/EclogiteGravel.png b/src/main/resources/assets/textures/blocks/EclogiteGravel.png new file mode 100644 index 0000000..9e72e13 Binary files /dev/null and b/src/main/resources/assets/textures/blocks/EclogiteGravel.png differ diff --git a/src/main/resources/assets/textures/blocks/EclogiteMonolith.png b/src/main/resources/assets/textures/blocks/EclogiteMonolith.png new file mode 100644 index 0000000..b5e61ff Binary files /dev/null and b/src/main/resources/assets/textures/blocks/EclogiteMonolith.png differ diff --git a/src/main/resources/assets/textures/blocks/EclogiteSand.png b/src/main/resources/assets/textures/blocks/EclogiteSand.png new file mode 100644 index 0000000..d00b06d Binary files /dev/null and b/src/main/resources/assets/textures/blocks/EclogiteSand.png differ diff --git a/src/main/resources/assets/textures/blocks/GabbroCracked.png b/src/main/resources/assets/textures/blocks/GabbroCracked.png new file mode 100644 index 0000000..5388eb3 Binary files /dev/null and b/src/main/resources/assets/textures/blocks/GabbroCracked.png differ diff --git a/src/main/resources/assets/textures/blocks/GabbroGravel.png b/src/main/resources/assets/textures/blocks/GabbroGravel.png new file mode 100644 index 0000000..0a1038b Binary files /dev/null and b/src/main/resources/assets/textures/blocks/GabbroGravel.png differ diff --git a/src/main/resources/assets/textures/blocks/GabbroMonolith.png b/src/main/resources/assets/textures/blocks/GabbroMonolith.png new file mode 100644 index 0000000..4ef7045 Binary files /dev/null and b/src/main/resources/assets/textures/blocks/GabbroMonolith.png differ diff --git a/src/main/resources/assets/textures/blocks/GabbroSand.png b/src/main/resources/assets/textures/blocks/GabbroSand.png new file mode 100644 index 0000000..b562d38 Binary files /dev/null and b/src/main/resources/assets/textures/blocks/GabbroSand.png differ diff --git a/src/main/resources/assets/textures/blocks/GraniteCracked.png b/src/main/resources/assets/textures/blocks/GraniteCracked.png deleted file mode 100644 index 508a193..0000000 Binary files a/src/main/resources/assets/textures/blocks/GraniteCracked.png and /dev/null differ diff --git a/src/main/resources/assets/textures/blocks/GraniteGravel.png b/src/main/resources/assets/textures/blocks/GraniteGravel.png deleted file mode 100644 index 7d7e35b..0000000 Binary files a/src/main/resources/assets/textures/blocks/GraniteGravel.png and /dev/null differ diff --git a/src/main/resources/assets/textures/blocks/LimestoneCracked.png b/src/main/resources/assets/textures/blocks/LimestoneCracked.png new file mode 100644 index 0000000..588f836 Binary files /dev/null and b/src/main/resources/assets/textures/blocks/LimestoneCracked.png differ diff --git a/src/main/resources/assets/textures/blocks/LimestoneGravel.png b/src/main/resources/assets/textures/blocks/LimestoneGravel.png new file mode 100644 index 0000000..d5229d7 Binary files /dev/null and b/src/main/resources/assets/textures/blocks/LimestoneGravel.png differ diff --git a/src/main/resources/assets/textures/blocks/LimestoneMonolith.png b/src/main/resources/assets/textures/blocks/LimestoneMonolith.png new file mode 100644 index 0000000..e644446 Binary files /dev/null and b/src/main/resources/assets/textures/blocks/LimestoneMonolith.png differ diff --git a/src/main/resources/assets/textures/blocks/LimestoneSand.png b/src/main/resources/assets/textures/blocks/LimestoneSand.png new file mode 100644 index 0000000..2dec5bd Binary files /dev/null and b/src/main/resources/assets/textures/blocks/LimestoneSand.png differ diff --git a/src/main/resources/assets/textures/blocks/MarbleCracked.png b/src/main/resources/assets/textures/blocks/MarbleCracked.png new file mode 100644 index 0000000..b1bd018 Binary files /dev/null and b/src/main/resources/assets/textures/blocks/MarbleCracked.png differ diff --git a/src/main/resources/assets/textures/blocks/MarbleGravel.png b/src/main/resources/assets/textures/blocks/MarbleGravel.png new file mode 100644 index 0000000..5ef8d57 Binary files /dev/null and b/src/main/resources/assets/textures/blocks/MarbleGravel.png differ diff --git a/src/main/resources/assets/textures/blocks/MarbleMonolith.png b/src/main/resources/assets/textures/blocks/MarbleMonolith.png new file mode 100644 index 0000000..dc666dc Binary files /dev/null and b/src/main/resources/assets/textures/blocks/MarbleMonolith.png differ diff --git a/src/main/resources/assets/textures/blocks/MarbleSand.png b/src/main/resources/assets/textures/blocks/MarbleSand.png new file mode 100644 index 0000000..a2738e3 Binary files /dev/null and b/src/main/resources/assets/textures/blocks/MarbleSand.png differ diff --git a/src/main/resources/assets/textures/blocks/RedGraniteCracked.png b/src/main/resources/assets/textures/blocks/RedGraniteCracked.png new file mode 100644 index 0000000..2d4cb78 Binary files /dev/null and b/src/main/resources/assets/textures/blocks/RedGraniteCracked.png differ diff --git a/src/main/resources/assets/textures/blocks/RedGraniteGravel.png b/src/main/resources/assets/textures/blocks/RedGraniteGravel.png new file mode 100644 index 0000000..077b14e Binary files /dev/null and b/src/main/resources/assets/textures/blocks/RedGraniteGravel.png differ diff --git a/src/main/resources/assets/textures/blocks/GraniteMonolith.png b/src/main/resources/assets/textures/blocks/RedGraniteMonolith.png similarity index 100% rename from src/main/resources/assets/textures/blocks/GraniteMonolith.png rename to src/main/resources/assets/textures/blocks/RedGraniteMonolith.png diff --git a/src/main/resources/assets/textures/blocks/RedGraniteSand.png b/src/main/resources/assets/textures/blocks/RedGraniteSand.png new file mode 100644 index 0000000..28abf60 Binary files /dev/null and b/src/main/resources/assets/textures/blocks/RedGraniteSand.png differ diff --git a/src/main/resources/assets/textures/blocks/TemporaryLeaves.png b/src/main/resources/assets/textures/blocks/TemporaryLeaves.png new file mode 100644 index 0000000..938c740 Binary files /dev/null and b/src/main/resources/assets/textures/blocks/TemporaryLeaves.png differ diff --git a/src/main/resources/assets/textures/items/MoonTypeIceCream.png b/src/main/resources/assets/textures/items/MoonTypeIceCream.png new file mode 100644 index 0000000..a37c756 Binary files /dev/null and b/src/main/resources/assets/textures/items/MoonTypeIceCream.png differ diff --git a/src/test/java/ru/windcorp/progressia/common/world/generic/GenericChunkRotationsTest.java b/src/test/java/ru/windcorp/progressia/common/world/generic/GenericChunkRotationsTest.java new file mode 100644 index 0000000..ef3d4c3 --- /dev/null +++ b/src/test/java/ru/windcorp/progressia/common/world/generic/GenericChunkRotationsTest.java @@ -0,0 +1,83 @@ +/* + * 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 static org.junit.Assert.fail; +import static ru.windcorp.progressia.common.world.generic.GenericChunks.*; + +import java.util.Random; + +import org.junit.Test; + +import glm.Glm; +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.rels.AbsFace; + +public class GenericChunkRotationsTest { + + private static void assertVecEquals(String message, AbsFace up, Vec3i a, Vec3i b) { + if (a == b) { + return; + } + + if (Glm.equals(a, b)) { + return; + } + + fail(String.format("%s. x = (%4d; %4d; %4d), got (%4d; %4d; %4d) (up = %s)", message, a.x, a.y, a.z, b.x, b.y, b.z, up)); + } + + private void check(int x, int y, int z) { + for (AbsFace up : AbsFace.getFaces()) { + + Vec3i veci = new Vec3i(x, y, z); + + assertVecEquals("Vec3i, x != resolve(relativize(x))", up, veci, resolve(relativize(veci, up, null), up, null)); + assertVecEquals("Vec3i, x != relativize(resolve(x))", up, veci, relativize(resolve(veci, up, null), up, null)); + + } + } + + @Test + public void specialCases() { + + for (int x = -1; x <= 1; ++x) { + for (int y = -1; y <= 1; ++y) { + for (int z = -1; z <= 1; ++z) { + check(x, y, z); + } + } + } + + } + + @Test + public void randomValues() { + + final int iterations = 2 << 16; + final long seed = 0; + + Random random = new Random(seed); + + for (int i = 0; i < iterations; ++i) { + check(random.nextInt(200) - 100, random.nextInt(200) - 100, random.nextInt(200) - 100); + } + + } + +} diff --git a/src/test/java/ru/windcorp/progressia/common/world/rels/AxisRotationsTest.java b/src/test/java/ru/windcorp/progressia/common/world/rels/AxisRotationsTest.java new file mode 100644 index 0000000..27d6368 --- /dev/null +++ b/src/test/java/ru/windcorp/progressia/common/world/rels/AxisRotationsTest.java @@ -0,0 +1,100 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ru.windcorp.progressia.common.world.rels; + +import static org.junit.Assert.fail; +import static ru.windcorp.progressia.common.world.rels.AxisRotations.*; + +import java.util.Random; + +import org.junit.Test; + +import glm.Glm; +import glm.vec._3.Vec3; +import glm.vec._3.i.Vec3i; + +public class AxisRotationsTest { + + private static void assertVecEquals(String message, AbsFace up, Vec3i a, Vec3i b) { + if (a == b) { + return; + } + + if (Glm.equals(a, b)) { + return; + } + + fail(String.format("%s. x = (%4d; %4d; %4d), got (%4d; %4d; %4d) (up = %s)", message, a.x, a.y, a.z, b.x, b.y, b.z, up)); + } + + private static void assertVecEquals(String message, AbsFace up, Vec3 a, Vec3 b) { + if (a == b) { + return; + } + + if (b.sub_(a).length() <= 1e-3) { + return; + } + + fail(String.format("%s. x = (%4f; %4f; %4f), got (%4f; %4f; %4f), d = %f (up = %s)", message, a.x, a.y, a.z, b.x, b.y, b.z, b.sub_(a).length(), up)); + } + + private void check(int x, int y, int z) { + for (AbsFace up : AbsFace.getFaces()) { + + Vec3i veci = new Vec3i(x, y, z); + + assertVecEquals("Vec3i, x != resolve(relativize(x))", up, veci, resolve(relativize(veci, up, null), up, null)); + assertVecEquals("Vec3i, x != relativize(resolve(x))", up, veci, relativize(resolve(veci, up, null), up, null)); + + Vec3 vecf = new Vec3(x, y, z); + + assertVecEquals("Vec3, x != resolve(relativize(x))", up, vecf, resolve(relativize(vecf, up, null), up, null)); + assertVecEquals("Vec3, x != relativize(resolve(x))", up, vecf, relativize(resolve(vecf, up, null), up, null)); + + } + } + + @Test + public void specialCases() { + + for (int x = -1; x <= 1; ++x) { + for (int y = -1; y <= 1; ++y) { + for (int z = -1; z <= 1; ++z) { + check(x, y, z); + } + } + } + + } + + @Test + public void randomValues() { + + final int iterations = 2 << 16; + final long seed = 0; + + Random random = new Random(seed); + + for (int i = 0; i < iterations; ++i) { + check(random.nextInt(200) - 100, random.nextInt(200) - 100, random.nextInt(200) - 100); + } + + } + +}