Merge branch 'master' into falling-block
This commit is contained in:
		@@ -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;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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());
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,20 +15,32 @@
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
package ru.windcorp.progressia.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());
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
package ru.windcorp.progressia.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<TransformedMask> masks, Mat4 transform, Renderable renderable) {
 | 
			
		||||
		public Clip(
 | 
			
		||||
			Iterable<TransformedMask> masks,
 | 
			
		||||
			Mat4 transform,
 | 
			
		||||
			Renderable renderable
 | 
			
		||||
		) {
 | 
			
		||||
			for (TransformedMask mask : masks) {
 | 
			
		||||
				this.masks.pushMask(mask);
 | 
			
		||||
			}
 | 
			
		||||
@@ -80,7 +84,7 @@ public class RenderTarget {
 | 
			
		||||
 | 
			
		||||
	private final Deque<TransformedMask> maskStack = new LinkedList<>();
 | 
			
		||||
	private final Deque<Mat4> transformStack = new LinkedList<>();
 | 
			
		||||
	private final List<Face> currentClipFaces = new ArrayList<>();
 | 
			
		||||
	private final List<ShapePart> 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);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
package ru.windcorp.progressia.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<CharSequence> supplier, int style, float align, float maxWidth, Vec4 color) {
 | 
			
		||||
		public DynamicText(
 | 
			
		||||
			Supplier<CharSequence> 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<Face> faces = new ArrayList<>();
 | 
			
		||||
			private final Collection<ShapePart> 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<CharSequence> supplier, int style, float align, float maxWidth,
 | 
			
		||||
			Vec4 color) {
 | 
			
		||||
	public Renderable assembleDynamic(
 | 
			
		||||
		Supplier<CharSequence> 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))
 | 
			
		||||
				)
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
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<Consumer<BasicButton>> 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<BasicButton> action) {
 | 
			
		||||
		this.actions.add(Objects.requireNonNull(action, "action"));
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public boolean removeAction(Consumer<BasicButton> action) {
 | 
			
		||||
		return this.actions.remove(action);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public void virtualClick() {
 | 
			
		||||
		this.actions.forEach(action -> {
 | 
			
		||||
			action.accept(this);
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public Label getLabel() {
 | 
			
		||||
		return label;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
}
 | 
			
		||||
@@ -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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
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));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
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));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
}
 | 
			
		||||
@@ -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.
 | 
			
		||||
	 * <p>
 | 
			
		||||
	 * 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.
 | 
			
		||||
	 * <p>
 | 
			
		||||
	 * 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.
 | 
			
		||||
	 * <p>
 | 
			
		||||
	 * 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.
 | 
			
		||||
		 * <p>
 | 
			
		||||
		 * <em>Component must be focusable to be able to gain focus.</em> 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<ARTrigger, Object> 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.
 | 
			
		||||
	 * <p>
 | 
			
		||||
	 * {@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.
 | 
			
		||||
	 * <p>
 | 
			
		||||
	 * {@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.
 | 
			
		||||
 
 | 
			
		||||
@@ -15,19 +15,14 @@
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 
 | 
			
		||||
package ru.windcorp.progressia.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);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -15,7 +15,7 @@
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
package ru.windcorp.progressia.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));
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,14 +15,66 @@
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package ru.windcorp.progressia.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);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
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));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
}
 | 
			
		||||
@@ -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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
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<Consumer<RadioButtonGroup>> actions = Collections.synchronizedCollection(new ArrayList<>());
 | 
			
		||||
	final List<RadioButton> buttons = Collections.synchronizedList(new ArrayList<>());
 | 
			
		||||
	
 | 
			
		||||
	private RadioButton selected = null;
 | 
			
		||||
	
 | 
			
		||||
	Consumer<BasicButton> listener = b -> {
 | 
			
		||||
		if (b instanceof RadioButton && ((RadioButton) b).isChecked() && buttons.contains(b)) {
 | 
			
		||||
			select((RadioButton) b);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	public RadioButtonGroup addAction(Consumer<RadioButtonGroup> action) {
 | 
			
		||||
		this.actions.add(Objects.requireNonNull(action, "action"));
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public boolean removeAction(Consumer<BasicButton> action) {
 | 
			
		||||
		return this.actions.remove(action);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public List<RadioButton> 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);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
}
 | 
			
		||||
@@ -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);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
}
 | 
			
		||||
@@ -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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 
 | 
			
		||||
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 + ")";
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -15,7 +15,7 @@
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
package ru.windcorp.progressia.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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
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();
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
}
 | 
			
		||||
@@ -15,63 +15,83 @@
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
package ru.windcorp.progressia.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<BlockFace, Vec3> originBuilder = ImmutableMap.builder();
 | 
			
		||||
		ImmutableMap.Builder<AbsFace, Vec3> originBuilder = ImmutableMap.builder();
 | 
			
		||||
 | 
			
		||||
		ImmutableMap.Builder<BlockFace, Vec3> widthBuilder = ImmutableMap.builder();
 | 
			
		||||
		ImmutableMap.Builder<AbsFace, Vec3> widthBuilder = ImmutableMap.builder();
 | 
			
		||||
 | 
			
		||||
		ImmutableMap.Builder<BlockFace, Vec3> heightBuilder = ImmutableMap.builder();
 | 
			
		||||
		ImmutableMap.Builder<AbsFace, Vec3> 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.<BlockFace, Vec3>builder()
 | 
			
		||||
		OUTER = new BlockFaceVectors(
 | 
			
		||||
			ImmutableMap.<AbsFace, Vec3>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.<BlockFace, Vec3>builder()
 | 
			
		||||
			ImmutableMap.<AbsFace, Vec3>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.<BlockFace, Vec3>builder()
 | 
			
		||||
			ImmutableMap.<AbsFace, Vec3>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<BlockFace, Vec3> origins;
 | 
			
		||||
	private final ImmutableMap<BlockFace, Vec3> widths;
 | 
			
		||||
	private final ImmutableMap<BlockFace, Vec3> heights;
 | 
			
		||||
	private final ImmutableMap<AbsFace, Vec3> origins;
 | 
			
		||||
	private final ImmutableMap<AbsFace, Vec3> widths;
 | 
			
		||||
	private final ImmutableMap<AbsFace, Vec3> heights;
 | 
			
		||||
 | 
			
		||||
	public BlockFaceVectors(ImmutableMap<BlockFace, Vec3> origins, ImmutableMap<BlockFace, Vec3> widths,
 | 
			
		||||
			ImmutableMap<BlockFace, Vec3> heights) {
 | 
			
		||||
	public BlockFaceVectors(
 | 
			
		||||
		ImmutableMap<AbsFace, Vec3> origins,
 | 
			
		||||
		ImmutableMap<AbsFace, Vec3> widths,
 | 
			
		||||
		ImmutableMap<AbsFace, Vec3> 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);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
package ru.windcorp.progressia.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<Face> {
 | 
			
		||||
public class ShapePart implements Comparable<ShapePart> {
 | 
			
		||||
 | 
			
		||||
	private static final ShortBuffer GENERATE_SUCCESSIVE_LATER = null;
 | 
			
		||||
 | 
			
		||||
@@ -40,13 +40,20 @@ public class Face implements Comparable<Face> {
 | 
			
		||||
	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<Face> {
 | 
			
		||||
 | 
			
		||||
	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<Face> {
 | 
			
		||||
				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<Face> {
 | 
			
		||||
		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<Face> {
 | 
			
		||||
		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<Face> {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public int compareTo(Face o) {
 | 
			
		||||
	public int compareTo(ShapePart o) {
 | 
			
		||||
		return Integer.compare(getSortingIndex(), o.getSortingIndex());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -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);
 | 
			
		||||
@@ -15,7 +15,7 @@
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
package ru.windcorp.progressia.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
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
package ru.windcorp.progressia.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<BlockFace, Texture> 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<AbsFace, Texture> 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
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,13 +15,13 @@
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
package ru.windcorp.progressia.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<BlockFace, Texture> 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<AbsFace, Texture> 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<BlockFace, Texture> getCuboidTextures(int x, int y, int size) {
 | 
			
		||||
	public Map<AbsFace, Texture> getCuboidTextures(
 | 
			
		||||
		int x,
 | 
			
		||||
		int y,
 | 
			
		||||
		int size
 | 
			
		||||
	) {
 | 
			
		||||
		return getCuboidTextures(x, y, size, size, size);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
package ru.windcorp.progressia.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<Vec3> offsetGetter, Consumer<Mat4> rotator) {
 | 
			
		||||
			public static Mode of(
 | 
			
		||||
				Consumer<Vec3> offsetGetter,
 | 
			
		||||
				Consumer<Mat4> 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<Mode> 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;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
package ru.windcorp.progressia.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<ChunkRender, BlockRender, TileRender, TileRenderStack> {
 | 
			
		||||
public class ChunkRender
 | 
			
		||||
	implements ChunkGenericRO<BlockRender, TileRender, TileRenderStack, TileRenderReference, ChunkRender> {
 | 
			
		||||
 | 
			
		||||
	private final WorldRender world;
 | 
			
		||||
	private final ChunkData data;
 | 
			
		||||
	private final DefaultChunkData data;
 | 
			
		||||
 | 
			
		||||
	private final ChunkRenderModel model;
 | 
			
		||||
 | 
			
		||||
	private final Map<TileDataStack, TileRenderStackImpl> 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<ChunkRender, BlockRender, TileR
 | 
			
		||||
	public Vec3i getPosition() {
 | 
			
		||||
		return getData().getPosition();
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	@Override
 | 
			
		||||
	public AbsFace getUp() {
 | 
			
		||||
		return getData().getUp();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public BlockRender getBlock(Vec3i posInChunk) {
 | 
			
		||||
		return BlockRenderRegistry.getInstance().get(getData().getBlock(posInChunk).getId());
 | 
			
		||||
		return BlockRenderRegistry.getInstance().get(
 | 
			
		||||
			getData().getBlock(posInChunk).getId()
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
@@ -71,18 +83,21 @@ public class ChunkRender implements GenericChunk<ChunkRender, BlockRender, TileR
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private TileRenderStack getTileStackWrapper(TileDataStack tileDataList) {
 | 
			
		||||
		return tileRenderLists.computeIfAbsent(tileDataList, TileRenderStackImpl::new);
 | 
			
		||||
		return tileRenderLists.computeIfAbsent(
 | 
			
		||||
			tileDataList,
 | 
			
		||||
			TileRenderStackImpl::new
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public WorldRender getWorld() {
 | 
			
		||||
		return world;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public ChunkData getData() {
 | 
			
		||||
	public DefaultChunkData getData() {
 | 
			
		||||
		return data;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public synchronized void markForUpdate() {
 | 
			
		||||
	public void markForUpdate() {
 | 
			
		||||
		getWorld().markChunkForUpdate(getPosition());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -95,6 +110,28 @@ public class ChunkRender implements GenericChunk<ChunkRender, BlockRender, TileR
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private class TileRenderStackImpl extends TileRenderStack {
 | 
			
		||||
		private class TileRenderReferenceImpl implements TileRenderReference {
 | 
			
		||||
			private final TileDataReference parent;
 | 
			
		||||
 | 
			
		||||
			public TileRenderReferenceImpl(TileDataReference parent) {
 | 
			
		||||
				this.parent = parent;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			@Override
 | 
			
		||||
			public TileRender get() {
 | 
			
		||||
				return TileRenderRegistry.getInstance().get(parent.get().getId());
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			@Override
 | 
			
		||||
			public int getIndex() {
 | 
			
		||||
				return parent.getIndex();
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			@Override
 | 
			
		||||
			public TileRenderStack getStack() {
 | 
			
		||||
				return TileRenderStackImpl.this;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private final TileDataStack parent;
 | 
			
		||||
 | 
			
		||||
@@ -113,9 +150,24 @@ public class ChunkRender implements GenericChunk<ChunkRender, BlockRender, TileR
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		@Override
 | 
			
		||||
		public BlockFace getFace() {
 | 
			
		||||
		public RelFace getFace() {
 | 
			
		||||
			return parent.getFace();
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		@Override
 | 
			
		||||
		public TileRenderReference getReference(int index) {
 | 
			
		||||
			return new TileRenderReferenceImpl(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 TileRender get(int index) {
 | 
			
		||||
 
 | 
			
		||||
@@ -35,13 +35,15 @@ import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerRegistry;
 | 
			
		||||
import ru.windcorp.progressia.client.world.tile.TileRender;
 | 
			
		||||
import ru.windcorp.progressia.client.world.tile.TileRenderNone;
 | 
			
		||||
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.DefaultChunkData;
 | 
			
		||||
import ru.windcorp.progressia.common.world.generic.GenericChunks;
 | 
			
		||||
import ru.windcorp.progressia.common.world.rels.AxisRotations;
 | 
			
		||||
import ru.windcorp.progressia.common.world.rels.RelFace;
 | 
			
		||||
 | 
			
		||||
public class ChunkRenderModel implements Renderable {
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
	private final ChunkRender chunk;
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
	private final Collection<ChunkRenderOptimizer> 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<String> 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);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,16 +15,17 @@
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
package ru.windcorp.progressia.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);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
package ru.windcorp.progressia.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<BlockRender, TileRender, TileRenderStack, ChunkRender, EntityRenderable> {
 | 
			
		||||
	implements WorldGenericRO<BlockRender, TileRender, TileRenderStack, TileRenderReference, ChunkRender, EntityRenderable> {
 | 
			
		||||
 | 
			
		||||
	private final WorldData data;
 | 
			
		||||
	private final DefaultWorldData data;
 | 
			
		||||
	private final Client client;
 | 
			
		||||
 | 
			
		||||
	private final Map<ChunkData, ChunkRender> chunks = Collections.synchronizedMap(new HashMap<>());
 | 
			
		||||
	private final Map<DefaultChunkData, ChunkRender> chunks = Collections.synchronizedMap(new HashMap<>());
 | 
			
		||||
	private final Map<EntityData, EntityRenderable> 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<EntityRenderable> 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) {
 | 
			
		||||
 
 | 
			
		||||
@@ -15,27 +15,22 @@
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
package ru.windcorp.progressia.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;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,25 +15,60 @@
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
package ru.windcorp.progressia.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;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,12 +15,11 @@
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
package ru.windcorp.progressia.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<BlockFace, Texture> textures = new HashMap<>();
 | 
			
		||||
	private final Map<RelFace, Texture> 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<Face> output, Vec3 offset) {
 | 
			
		||||
	public final void getShapeParts(
 | 
			
		||||
		DefaultChunkData chunk, Vec3i blockInChunk, RelFace blockFace,
 | 
			
		||||
		boolean inner,
 | 
			
		||||
		Consumer<ShapePart> 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);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,25 +15,60 @@
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
package ru.windcorp.progressia.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;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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.
 | 
			
		||||
 * <h3>CRO lifecycle</h3> 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:
 | 
			
		||||
 * <p>
 | 
			
		||||
 * 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.
 | 
			
		||||
 * <h3>CRO lifecycle</h3>
 | 
			
		||||
 * 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:
 | 
			
		||||
 * <ol>
 | 
			
		||||
 * <li>{@link #setup(ChunkRender)} is invoked to provide the {@link ChunkRender}
 | 
			
		||||
 * instance.</li>
 | 
			
		||||
 * <li>{@link #startRender()} is invoked. The CRO must reset its state.</li>
 | 
			
		||||
 * <li>{@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.</li>
 | 
			
		||||
 * <li>{@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
 | 
			
		||||
 
 | 
			
		||||
@@ -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<Face> output,
 | 
			
		||||
				Vec3 offset /* kostyl 156% */
 | 
			
		||||
		void getShapeParts(
 | 
			
		||||
			DefaultChunkData chunk,
 | 
			
		||||
			Vec3i relBlockInChunk,
 | 
			
		||||
			RelFace blockFace,
 | 
			
		||||
			boolean inner,
 | 
			
		||||
			Consumer<ShapePart> 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<Face> shapeFaces = new ArrayList<>(BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * 3);
 | 
			
		||||
		Collection<ShapePart> shapeParts = new ArrayList<>(
 | 
			
		||||
			BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * 3
 | 
			
		||||
		);
 | 
			
		||||
 | 
			
		||||
		Vec3i cursor = new Vec3i();
 | 
			
		||||
		Consumer<Face> consumer = shapeFaces::add;
 | 
			
		||||
		Consumer<ShapePart> 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<Face> output) {
 | 
			
		||||
		for (BlockFace blockFace : BlockFace.getFaces()) {
 | 
			
		||||
			processOuterFace(blockInChunk, blockFace, output);
 | 
			
		||||
	private void processOuterFaces(
 | 
			
		||||
		Vec3i relBlockInChunk,
 | 
			
		||||
		Consumer<ShapePart> output
 | 
			
		||||
	) {
 | 
			
		||||
		for (RelFace blockFace : RelFace.getFaces()) {
 | 
			
		||||
			processOuterFace(relBlockInChunk, blockFace, output);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void processOuterFace(Vec3i blockInChunk, BlockFace blockFace, Consumer<Face> output) {
 | 
			
		||||
		if (!shouldRenderOuterFace(blockInChunk, blockFace))
 | 
			
		||||
	private void processOuterFace(Vec3i relBlockInChunk, RelFace blockFace, Consumer<ShapePart> 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<Face> output) {
 | 
			
		||||
		for (BlockFace blockFace : BlockFace.getFaces()) {
 | 
			
		||||
			processInnerFace(blockInChunk, blockFace, output);
 | 
			
		||||
	private void processInnerFaces(Vec3i relBlockInChunk, Consumer<ShapePart> output) {
 | 
			
		||||
		for (RelFace blockFace : RelFace.getFaces()) {
 | 
			
		||||
			processInnerFace(relBlockInChunk, blockFace, output);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void processInnerFace(Vec3i blockInChunk, BlockFace blockFace, Consumer<Face> output) {
 | 
			
		||||
		if (!shouldRenderInnerFace(blockInChunk, blockFace))
 | 
			
		||||
	private void processInnerFace(Vec3i relBlockInChunk, RelFace blockFace, Consumer<ShapePart> 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;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
 
 | 
			
		||||
@@ -15,14 +15,11 @@
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
package ru.windcorp.progressia.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
 | 
			
		||||
	 */
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
 
 | 
			
		||||
@@ -15,29 +15,24 @@
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
package ru.windcorp.progressia.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;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
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<BlockRender, TileRender, TileRenderStack, TileRenderReference, ChunkRender> {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -15,14 +15,18 @@
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
package ru.windcorp.progressia.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<TileRenderStack, TileRender, ChunkRender> {
 | 
			
		||||
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<TileRender>
 | 
			
		||||
	implements TileGenericStackRO<BlockRender, TileRender, TileRenderStack, TileRenderReference, ChunkRender> {
 | 
			
		||||
 | 
			
		||||
	public abstract TileDataStack getData();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
package ru.windcorp.progressia.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<Face> output, Vec3 offset) {
 | 
			
		||||
		output.accept(createFace(chunk, blockInChunk, blockFace, inner, offset));
 | 
			
		||||
	public final void getShapeParts(
 | 
			
		||||
		DefaultChunkData chunk, Vec3i relBlockInChunk, RelFace blockFace,
 | 
			
		||||
		boolean inner,
 | 
			
		||||
		Consumer<ShapePart> 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
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
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<AbsFace> upSupplier;
 | 
			
		||||
	private final Supplier<Vec3> 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<AbsFace> upSupplier, Supplier<Vec3> 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<AbsFace> upSupplier, Supplier<Vec3> hingeSupplier, CollisionModel parent) {
 | 
			
		||||
		if (parent instanceof AABBoid) {
 | 
			
		||||
			return new AABBRotator(upSupplier, hingeSupplier, (AABBoid) parent);
 | 
			
		||||
		} else if (parent instanceof CompoundCollisionModel) {
 | 
			
		||||
			ImmutableList.Builder<CollisionModel> 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");
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
package ru.windcorp.progressia.common.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();
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
package ru.windcorp.progressia.common.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,
 | 
			
		||||
	 * <p>
 | 
			
		||||
	 * When {@linkplain #advanceTime(Collection, Collision, WorldData, float)
 | 
			
		||||
	 * advancing time}, time step for all entities <em>except</em> 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 <em>except</em> 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.
 | 
			
		||||
	 * <p>
 | 
			
		||||
	 * 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.
 | 
			
		||||
	 * <p>
 | 
			
		||||
	 * Your faithful student,<br />
 | 
			
		||||
	 * Kostyl.
 | 
			
		||||
@@ -55,14 +59,21 @@ public class Collider {
 | 
			
		||||
																						 * 1f
 | 
			
		||||
																						 */;
 | 
			
		||||
 | 
			
		||||
	public static void performCollisions(List<? extends Collideable> colls, WorldData world, float tickLength,
 | 
			
		||||
			ColliderWorkspace workspace) {
 | 
			
		||||
	public static void performCollisions(
 | 
			
		||||
		List<? extends Collideable> colls,
 | 
			
		||||
		DefaultWorldData world,
 | 
			
		||||
		float tickLength,
 | 
			
		||||
		ColliderWorkspace workspace
 | 
			
		||||
	) {
 | 
			
		||||
		int collisionCount = 0;
 | 
			
		||||
		int maxCollisions = colls.size() * MAX_COLLISIONS_PER_ENTITY;
 | 
			
		||||
 | 
			
		||||
		while (true) {
 | 
			
		||||
			if (collisionCount > maxCollisions) {
 | 
			
		||||
				LogManager.getLogger().warn("Attempted to handle more than {} collisions", maxCollisions);
 | 
			
		||||
				LogManager.getLogger().warn(
 | 
			
		||||
					"Attempted to handle more than {} collisions",
 | 
			
		||||
					maxCollisions
 | 
			
		||||
				);
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
@@ -82,8 +93,12 @@ public class Collider {
 | 
			
		||||
		advanceTime(colls, null, world, tickLength);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static Collision getFirstCollision(List<? extends Collideable> colls, float tickLength, WorldData world,
 | 
			
		||||
			ColliderWorkspace workspace) {
 | 
			
		||||
	private static Collision getFirstCollision(
 | 
			
		||||
		List<? extends Collideable> colls,
 | 
			
		||||
		float tickLength,
 | 
			
		||||
		DefaultWorldData world,
 | 
			
		||||
		ColliderWorkspace workspace
 | 
			
		||||
	) {
 | 
			
		||||
		Collision result = null;
 | 
			
		||||
		Collideable worldColl = workspace.worldCollisionHelper.getCollideable();
 | 
			
		||||
 | 
			
		||||
@@ -93,7 +108,10 @@ public class Collider {
 | 
			
		||||
 | 
			
		||||
			tuneWorldCollisionHelper(a, tickLength, world, workspace);
 | 
			
		||||
 | 
			
		||||
			result = workspace.updateLatestCollision(result, getCollision(a, worldColl, tickLength, workspace));
 | 
			
		||||
			result = workspace.updateLatestCollision(
 | 
			
		||||
				result,
 | 
			
		||||
				getCollision(a, worldColl, tickLength, workspace)
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			for (int j = i + 1; j < colls.size(); ++j) {
 | 
			
		||||
				Collideable b = colls.get(j);
 | 
			
		||||
@@ -105,42 +123,81 @@ public class Collider {
 | 
			
		||||
		return result;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static void tuneWorldCollisionHelper(Collideable coll, float tickLength, WorldData world,
 | 
			
		||||
			ColliderWorkspace workspace) {
 | 
			
		||||
	private static void tuneWorldCollisionHelper(
 | 
			
		||||
		Collideable coll,
 | 
			
		||||
		float tickLength,
 | 
			
		||||
		DefaultWorldData world,
 | 
			
		||||
		ColliderWorkspace workspace
 | 
			
		||||
	) {
 | 
			
		||||
		WorldCollisionHelper wch = workspace.worldCollisionHelper;
 | 
			
		||||
		wch.tuneToCollideable(world, coll, tickLength);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static Collision getCollision(Collideable a, Collideable b, float tickLength, ColliderWorkspace workspace) {
 | 
			
		||||
	static Collision getCollision(
 | 
			
		||||
		Collideable a,
 | 
			
		||||
		Collideable b,
 | 
			
		||||
		float tickLength,
 | 
			
		||||
		ColliderWorkspace workspace
 | 
			
		||||
	) {
 | 
			
		||||
		CollisionModel aModel = a.getCollisionModel();
 | 
			
		||||
		CollisionModel bModel = b.getCollisionModel();
 | 
			
		||||
		return getCollision(a, b, aModel, bModel, tickLength, workspace);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static Collision getCollision(Collideable aBody, Collideable bBody, CollisionModel aModel, CollisionModel bModel,
 | 
			
		||||
			float tickLength, ColliderWorkspace workspace) {
 | 
			
		||||
	static Collision getCollision(
 | 
			
		||||
		Collideable aBody,
 | 
			
		||||
		Collideable bBody,
 | 
			
		||||
		CollisionModel aModel,
 | 
			
		||||
		CollisionModel bModel,
 | 
			
		||||
		float tickLength,
 | 
			
		||||
		ColliderWorkspace workspace
 | 
			
		||||
	) {
 | 
			
		||||
		if (aModel instanceof AABBoid && bModel instanceof AABBoid) {
 | 
			
		||||
			return AABBoidCollider.computeModelCollision(aBody, bBody, (AABBoid) aModel, (AABBoid) bModel, tickLength,
 | 
			
		||||
					workspace);
 | 
			
		||||
			return AABBoidCollider.computeModelCollision(
 | 
			
		||||
				aBody,
 | 
			
		||||
				bBody,
 | 
			
		||||
				(AABBoid) aModel,
 | 
			
		||||
				(AABBoid) bModel,
 | 
			
		||||
				tickLength,
 | 
			
		||||
				workspace
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (aModel instanceof CompoundCollisionModel) {
 | 
			
		||||
			return AnythingWithCompoundCollider.computeModelCollision(aBody, bBody, (CompoundCollisionModel) aModel,
 | 
			
		||||
					bModel, tickLength, workspace);
 | 
			
		||||
			return AnythingWithCompoundCollider.computeModelCollision(
 | 
			
		||||
				aBody,
 | 
			
		||||
				bBody,
 | 
			
		||||
				(CompoundCollisionModel) aModel,
 | 
			
		||||
				bModel,
 | 
			
		||||
				tickLength,
 | 
			
		||||
				workspace
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (bModel instanceof CompoundCollisionModel) {
 | 
			
		||||
			return AnythingWithCompoundCollider.computeModelCollision(bBody, aBody, (CompoundCollisionModel) bModel,
 | 
			
		||||
					aModel, tickLength, workspace);
 | 
			
		||||
			return AnythingWithCompoundCollider.computeModelCollision(
 | 
			
		||||
				bBody,
 | 
			
		||||
				aBody,
 | 
			
		||||
				(CompoundCollisionModel) bModel,
 | 
			
		||||
				aModel,
 | 
			
		||||
				tickLength,
 | 
			
		||||
				workspace
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		throw new UnsupportedOperationException(
 | 
			
		||||
				"Collisions between " + aModel + " and " + bModel + " are not yet implemented");
 | 
			
		||||
			"Collisions between " + aModel + " and " + bModel + " are not yet implemented"
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static void collide(Collision collision,
 | 
			
		||||
	private static void collide(
 | 
			
		||||
		Collision collision,
 | 
			
		||||
 | 
			
		||||
			Collection<? extends Collideable> colls, WorldData world, float tickLength, ColliderWorkspace workspace) {
 | 
			
		||||
		Collection<? extends Collideable> colls,
 | 
			
		||||
		DefaultWorldData world,
 | 
			
		||||
		float tickLength,
 | 
			
		||||
		ColliderWorkspace workspace
 | 
			
		||||
	) {
 | 
			
		||||
		advanceTime(colls, collision, world, collision.time);
 | 
			
		||||
 | 
			
		||||
		boolean doNotHandle = false;
 | 
			
		||||
@@ -155,40 +212,72 @@ public class Collider {
 | 
			
		||||
		handlePhysics(collision);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// @formatter:off
 | 
			
		||||
	/*
 | 
			
		||||
	 * Here we compute the change in body velocities due to a collision. We make
 | 
			
		||||
	 * the following simplifications: 1) The bodies are perfectly rigid; 2) The
 | 
			
		||||
	 * collision is perfectly inelastic (no bouncing); 3) The bodies are
 | 
			
		||||
	 * spherical; 4) No tangential friction exists (bodies do not experience
 | 
			
		||||
	 * friction when sliding against each other); 5) Velocities are not
 | 
			
		||||
	 * relativistic. Angular momentum is ignored per 3) and 4), e.g. when
 | 
			
		||||
	 * something pushes an end of a long stick, the stick does not rotate.
 | 
			
		||||
	 * DETAILED EXPLANATION: Two spherical (sic) bodies, a and b, experience a
 | 
			
		||||
	 * perfectly inelastic collision along a unit vector _ _ _ _ _ n = (w ⨯ h)
 | 
			
		||||
	 * / (|w ⨯ h|), _ _ where w and h are two noncollinear nonzero vectors on
 | 
			
		||||
	 * the dividing plane. ___ ___ Body masses and velocities are M_a, M_b and
 | 
			
		||||
	 * v_a, v_b, respectively. ___ ___ After the collision desired velocities
 | 
			
		||||
	 * are u_a and u_b, respectively. _ (Notation convention: suffix 'n' denotes
 | 
			
		||||
	 * a vector projection onto vector n, and suffix 't' denotes a vector
 | 
			
		||||
	 * projection onto the dividing plane.) Consider the law of conservation of
 | 
			
		||||
	 * momentum for axis n and the dividing plane: ____________ ____________
 | 
			
		||||
	 * ________________ n: ⎧ p_a_before_n + p_b_before_n = p_common_after_n;
 | 
			
		||||
	 * ⎨ ___________ ____________ t: ⎩ p_i_after_t = p_i_before_t for any i
 | 
			
		||||
	 * in {a, b}. Expressing all p_* in given terms: ___ _ ___ _ ___ ___ ____
 | 
			
		||||
	 * ____ n: ⎧ M_a * (v_a ⋅ n) + M_b * (v_b ⋅ n) = (M_a + M_b) * u_n,
 | 
			
		||||
	 * where u_n ≡ u_an = u_bn; ⎨ ____ ___ _ ___ _ t: ⎩ u_it = v_i - n *
 | 
			
		||||
	 * (v_i ⋅ n) for any i in {a, b}. Therefore: ___ _ ___ _ ___ _ u_n = n * (
 | 
			
		||||
	 * M_a/(M_a + M_b) * v_a ⋅ n + M_b/(M_a + M_b) * v_b ⋅ n ); or,
 | 
			
		||||
	 * equivalently, ___ _ ___ _ ___ _ u_n = n * ( m_a * v_a ⋅ n + m_b * v_b
 | 
			
		||||
	 * ⋅ n ), where m_a and m_b are relative masses (see below). Finally, ___
 | 
			
		||||
	 * ____ ___ u_i = u_it + u_n for any i in {a, b}. The usage of relative
 | 
			
		||||
	 * masses m_i permits a convenient generalization of the algorithm for
 | 
			
		||||
	 * infinite masses, signifying masses "significantly greater" than finite
 | 
			
		||||
	 * masses: 1) If both M_a and M_b are finite, let m_i = M_i / (M_a + M_b)
 | 
			
		||||
	 * for any i in {a, b}. 2) If M_i is finite but M_j is infinite, let m_i = 0
 | 
			
		||||
	 * and m_j = 1. 3) If both M_a and M_b are infinite, let m_i = 1/2 for any i
 | 
			
		||||
	 * in {a, b}.
 | 
			
		||||
	 * Here we compute the change in body velocities due to a collision.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * We make the following simplifications:
 | 
			
		||||
	 *   1)  The bodies are perfectly rigid;
 | 
			
		||||
	 *   2)  The collision is perfectly inelastic
 | 
			
		||||
	 *       (no bouncing);
 | 
			
		||||
	 *   3)  The bodies are spherical;
 | 
			
		||||
	 *   4)  No tangential friction exists
 | 
			
		||||
	 *       (bodies do not experience friction when sliding against each other);
 | 
			
		||||
	 *   5)  Velocities are not relativistic.
 | 
			
		||||
	 *   
 | 
			
		||||
	 * Angular momentum is ignored per 3) and 4),
 | 
			
		||||
	 * e.g. when something pushes an end of a long stick, the stick does not rotate.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * DETAILED EXPLANATION:
 | 
			
		||||
	 * 
 | 
			
		||||
	 * Two spherical (sic) bodies, a and b, experience a perfectly inelastic collision
 | 
			
		||||
	 * along a unit vector
 | 
			
		||||
	 *   _    _   _      _   _
 | 
			
		||||
	 *   n = (w ⨯ h) / (|w ⨯ h|),
 | 
			
		||||
	 *       _     _
 | 
			
		||||
	 * where w and h are two noncollinear nonzero vectors on the dividing plane.
 | 
			
		||||
	 *                                             ___  ___
 | 
			
		||||
	 * Body masses and velocities are M_a, M_b and v_a, v_b, respectively.
 | 
			
		||||
	 *                                            ___     ___
 | 
			
		||||
	 * After the collision desired velocities are u_a and u_b, respectively.
 | 
			
		||||
	 *                                                                          _
 | 
			
		||||
	 * (Notation convention: suffix 'n' denotes a vector projection onto vector n,
 | 
			
		||||
	 * and suffix 't' denotes a vector projection onto the dividing plane.)
 | 
			
		||||
	 * 
 | 
			
		||||
	 * Consider the law of conservation of momentum for axis n and the dividing plane:
 | 
			
		||||
	 *        ____________   ____________   ________________
 | 
			
		||||
	 *   n: ⎧ p_a_before_n + p_b_before_n = p_common_after_n;
 | 
			
		||||
	 *      ⎨ ___________   ____________
 | 
			
		||||
	 *   t: ⎩ p_i_after_t = p_i_before_t    for any i in {a, b}.
 | 
			
		||||
	 *   
 | 
			
		||||
	 * Expressing all p_* in given terms:
 | 
			
		||||
	 *               ___   _           ___   _                  ___           ___   ____   ____
 | 
			
		||||
	 *   n: ⎧ M_a * (v_a ⋅ n) + M_b * (v_b ⋅ n) = (M_a + M_b) * u_n,    where u_n ≡ u_an = u_bn;
 | 
			
		||||
	 *      ⎨ ____   ___   _    ___   _
 | 
			
		||||
	 *   t: ⎩ u_it = v_i - n * (v_i ⋅ n)                                for any i in {a, b}.
 | 
			
		||||
	 *   
 | 
			
		||||
	 * Therefore:
 | 
			
		||||
	 *   ___   _                       ___   _                       ___   _
 | 
			
		||||
	 *   u_n = n * ( M_a/(M_a + M_b) * v_a ⋅ n  +  M_b/(M_a + M_b) * v_b ⋅ n );
 | 
			
		||||
	 * 
 | 
			
		||||
	 * or, equivalently,
 | 
			
		||||
	 *   ___   _           ___   _           ___   _
 | 
			
		||||
	 *   u_n = n * ( m_a * v_a ⋅ n  +  m_b * v_b ⋅ n ),
 | 
			
		||||
	 * 
 | 
			
		||||
	 * where m_a and m_b are relative masses (see below).
 | 
			
		||||
	 * 
 | 
			
		||||
	 * Finally,
 | 
			
		||||
	 *   ___   ____   ___
 | 
			
		||||
	 *   u_i = u_it + u_n    for any i in {a, b}.
 | 
			
		||||
	 *   
 | 
			
		||||
	 * The usage of relative masses m_i permits a convenient generalization of the algorithm
 | 
			
		||||
	 * for infinite masses, signifying masses "significantly greater" than finite masses:
 | 
			
		||||
	 * 
 | 
			
		||||
	 *   1)  If both M_a and M_b are finite,       let m_i = M_i / (M_a + M_b)    for any i in {a, b}.
 | 
			
		||||
	 *   2)  If M_i is finite but M_j is infinite, let m_i = 0 and m_j = 1.
 | 
			
		||||
	 *   3)  If both M_a and M_b are infinite,     let m_i = 1/2                  for any i in {a, b}.
 | 
			
		||||
	 */
 | 
			
		||||
	// @formatter:on
 | 
			
		||||
	private static void handlePhysics(Collision collision) {
 | 
			
		||||
		// Fuck JGLM
 | 
			
		||||
		Vec3 n = Vectors.grab3();
 | 
			
		||||
@@ -250,7 +339,12 @@ public class Collider {
 | 
			
		||||
		Vectors.release(du_b);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static void separate(Collision collision, Vec3 normal, float aRelativeMass, float bRelativeMass) {
 | 
			
		||||
	private static void separate(
 | 
			
		||||
		Collision collision,
 | 
			
		||||
		Vec3 normal,
 | 
			
		||||
		float aRelativeMass,
 | 
			
		||||
		float bRelativeMass
 | 
			
		||||
	) {
 | 
			
		||||
		final float margin = 1e-4f;
 | 
			
		||||
 | 
			
		||||
		Vec3 displacement = Vectors.grab3();
 | 
			
		||||
@@ -264,8 +358,12 @@ public class Collider {
 | 
			
		||||
		Vectors.release(displacement);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static void advanceTime(Collection<? extends Collideable> colls, Collision exceptions, WorldData world,
 | 
			
		||||
			float step) {
 | 
			
		||||
	private static void advanceTime(
 | 
			
		||||
		Collection<? extends Collideable> colls,
 | 
			
		||||
		Collision exceptions,
 | 
			
		||||
		DefaultWorldData world,
 | 
			
		||||
		float step
 | 
			
		||||
	) {
 | 
			
		||||
		world.advanceTime(step);
 | 
			
		||||
 | 
			
		||||
		Vec3 tmp = Vectors.grab3();
 | 
			
		||||
@@ -333,12 +431,17 @@ public class Collider {
 | 
			
		||||
		public final Vec3 wallHeight = new Vec3();
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * Time offset from the start of the tick. 0 means right now, tickLength
 | 
			
		||||
		 * means at the end of the tick.
 | 
			
		||||
		 * Time offset from the start of the tick.
 | 
			
		||||
		 * 0 means right now, tickLength means at the end of the tick.
 | 
			
		||||
		 */
 | 
			
		||||
		public float time;
 | 
			
		||||
 | 
			
		||||
		public Collision set(Collideable a, Collideable b, Wall wall, float time) {
 | 
			
		||||
		public Collision set(
 | 
			
		||||
			Collideable a,
 | 
			
		||||
			Collideable b,
 | 
			
		||||
			Wall wall,
 | 
			
		||||
			float time
 | 
			
		||||
		) {
 | 
			
		||||
			this.a = a;
 | 
			
		||||
			this.b = b;
 | 
			
		||||
			wall.getWidth(wallWidth);
 | 
			
		||||
 
 | 
			
		||||
@@ -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 <em>without a clearly stated
 | 
			
		||||
 * reason</em>; looks like some dirty reflection will have to do.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * 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?");
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,8 +15,8 @@
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package ru.windcorp.progressia.server.world.tasks;
 | 
			
		||||
 
 | 
			
		||||
package ru.windcorp.progressia.common.state;
 | 
			
		||||
 | 
			
		||||
@FunctionalInterface
 | 
			
		||||
public interface StateChange<T> {
 | 
			
		||||
@@ -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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
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<E> implements FloatRangeMap<E> {
 | 
			
		||||
 | 
			
		||||
	protected static class Node<E> implements Comparable<Node<E>> {
 | 
			
		||||
		public float pos;
 | 
			
		||||
		public E value;
 | 
			
		||||
 | 
			
		||||
		public Node(float pos, E value) {
 | 
			
		||||
			this.pos = pos;
 | 
			
		||||
			this.value = value;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		@Override
 | 
			
		||||
		public int compareTo(Node<E> o) {
 | 
			
		||||
			return Float.compare(pos, o.pos);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	 * Expects a random-access list
 | 
			
		||||
	 */
 | 
			
		||||
	protected final List<Node<E>> 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<E> iterator() {
 | 
			
		||||
		return new Iterator<E>() {
 | 
			
		||||
			
 | 
			
		||||
			private int nextIndex = 0;
 | 
			
		||||
			
 | 
			
		||||
			{
 | 
			
		||||
				assert nodes.isEmpty() || nodes.get(nextIndex).value != null;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			private void findNext() {
 | 
			
		||||
				while (nextIndex < nodes.size()) {
 | 
			
		||||
					nextIndex++;
 | 
			
		||||
					Node<E> 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<E> getEffectiveNode(float at) {
 | 
			
		||||
		int effectiveNodeIndex = findFloor(at);
 | 
			
		||||
		if (effectiveNodeIndex < 0) return null;
 | 
			
		||||
		return nodes.get(effectiveNodeIndex);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	@Override
 | 
			
		||||
	public E get(float at) {
 | 
			
		||||
		Node<E> 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<E>(min, element));
 | 
			
		||||
		ranges++;
 | 
			
		||||
		
 | 
			
		||||
		ListIterator<Node<E>> it = nodes.listIterator(indexOfInsertionOfMin + 1);
 | 
			
		||||
		E elementEffectiveImmediatelyAfterInsertedRange = null;
 | 
			
		||||
		
 | 
			
		||||
		if (indexOfInsertionOfMin > 0) {
 | 
			
		||||
			elementEffectiveImmediatelyAfterInsertedRange = nodes.get(indexOfInsertionOfMin - 1).value;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		while (it.hasNext()) {
 | 
			
		||||
			Node<E> 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<E>(max, elementEffectiveImmediatelyAfterInsertedRange));
 | 
			
		||||
			
 | 
			
		||||
			if (elementEffectiveImmediatelyAfterInsertedRange != null) {
 | 
			
		||||
				// We might have added one right back
 | 
			
		||||
				ranges++;
 | 
			
		||||
			}			
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
package ru.windcorp.progressia.common.util;
 | 
			
		||||
 | 
			
		||||
public interface FloatRangeMap<E> extends Iterable<E> {
 | 
			
		||||
	
 | 
			
		||||
	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;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -15,11 +15,13 @@
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
package ru.windcorp.progressia.common.util;
 | 
			
		||||
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
 | 
			
		||||
import glm.Glm;
 | 
			
		||||
import glm.mat._3.Mat3;
 | 
			
		||||
import glm.mat._4.Mat4;
 | 
			
		||||
import glm.vec._2.Vec2;
 | 
			
		||||
import glm.vec._2.d.Vec2d;
 | 
			
		||||
@@ -36,8 +38,50 @@ public class VectorUtil {
 | 
			
		||||
	public static enum Axis {
 | 
			
		||||
		X, Y, Z, W;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public static enum SignedAxis {
 | 
			
		||||
		POS_X(Axis.X, +1),
 | 
			
		||||
		NEG_X(Axis.X, -1),
 | 
			
		||||
		POS_Y(Axis.Y, +1),
 | 
			
		||||
		NEG_Y(Axis.Y, -1),
 | 
			
		||||
		POS_Z(Axis.Z, +1),
 | 
			
		||||
		NEG_Z(Axis.Z, -1),
 | 
			
		||||
		POS_W(Axis.W, +1),
 | 
			
		||||
		NEG_W(Axis.W, -1);
 | 
			
		||||
		
 | 
			
		||||
		private final Axis axis;
 | 
			
		||||
		private final boolean isPositive;
 | 
			
		||||
		
 | 
			
		||||
		private SignedAxis(Axis axis, int sign) {
 | 
			
		||||
			this.axis = axis;
 | 
			
		||||
			this.isPositive = (sign == +1 ? true : false);
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		/**
 | 
			
		||||
		 * @return the axis
 | 
			
		||||
		 */
 | 
			
		||||
		public Axis getAxis() {
 | 
			
		||||
			return axis;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		public boolean isPositive() {
 | 
			
		||||
			return isPositive;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		public int getSign() {
 | 
			
		||||
			return isPositive ? +1 : -1;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static void iterateCuboid(int x0, int y0, int z0, int x1, int y1, int z1, Consumer<? super Vec3i> action) {
 | 
			
		||||
	public static void iterateCuboid(
 | 
			
		||||
		int x0,
 | 
			
		||||
		int y0,
 | 
			
		||||
		int z0,
 | 
			
		||||
		int x1,
 | 
			
		||||
		int y1,
 | 
			
		||||
		int z1,
 | 
			
		||||
		Consumer<? super Vec3i> action
 | 
			
		||||
	) {
 | 
			
		||||
		Vec3i cursor = Vectors.grab3i();
 | 
			
		||||
 | 
			
		||||
		for (int x = x0; x < x1; ++x) {
 | 
			
		||||
@@ -52,12 +96,23 @@ public class VectorUtil {
 | 
			
		||||
		Vectors.release(cursor);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static void iterateCuboid(Vec3i vMin, Vec3i vMax, Consumer<? super Vec3i> action) {
 | 
			
		||||
	public static void iterateCuboid(
 | 
			
		||||
		Vec3i vMin,
 | 
			
		||||
		Vec3i vMax,
 | 
			
		||||
		Consumer<? super Vec3i> action
 | 
			
		||||
	) {
 | 
			
		||||
		iterateCuboid(vMin.x, vMin.y, vMin.z, vMax.x, vMax.y, vMax.z, action);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static void iterateCuboidAround(int cx, int cy, int cz, int dx, int dy, int dz,
 | 
			
		||||
			Consumer<? super Vec3i> action) {
 | 
			
		||||
	public static void iterateCuboidAround(
 | 
			
		||||
		int cx,
 | 
			
		||||
		int cy,
 | 
			
		||||
		int cz,
 | 
			
		||||
		int dx,
 | 
			
		||||
		int dy,
 | 
			
		||||
		int dz,
 | 
			
		||||
		Consumer<? super Vec3i> action
 | 
			
		||||
	) {
 | 
			
		||||
		if (dx < 0)
 | 
			
		||||
			throw new IllegalArgumentException("dx " + dx + " is negative");
 | 
			
		||||
		if (dy < 0)
 | 
			
		||||
@@ -79,19 +134,37 @@ public class VectorUtil {
 | 
			
		||||
		iterateCuboid(cx - dx, cy - dy, cz - dz, cx + dx + 1, cy + dy + 1, cz + dz + 1, action);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static void iterateCuboidAround(Vec3i center, Vec3i diameters, Consumer<? super Vec3i> action) {
 | 
			
		||||
	public static void iterateCuboidAround(
 | 
			
		||||
		Vec3i center,
 | 
			
		||||
		Vec3i diameters,
 | 
			
		||||
		Consumer<? super Vec3i> action
 | 
			
		||||
	) {
 | 
			
		||||
		iterateCuboidAround(center.x, center.y, center.z, diameters.x, diameters.y, diameters.z, action);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static void iterateCuboidAround(int cx, int cy, int cz, int diameter, Consumer<? super Vec3i> action) {
 | 
			
		||||
	public static void iterateCuboidAround(
 | 
			
		||||
		int cx,
 | 
			
		||||
		int cy,
 | 
			
		||||
		int cz,
 | 
			
		||||
		int diameter,
 | 
			
		||||
		Consumer<? super Vec3i> action
 | 
			
		||||
	) {
 | 
			
		||||
		iterateCuboidAround(cx, cy, cz, diameter, diameter, diameter, action);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static void iterateCuboidAround(Vec3i center, int diameter, Consumer<? super Vec3i> action) {
 | 
			
		||||
	public static void iterateCuboidAround(
 | 
			
		||||
		Vec3i center,
 | 
			
		||||
		int diameter,
 | 
			
		||||
		Consumer<? super Vec3i> action
 | 
			
		||||
	) {
 | 
			
		||||
		iterateCuboidAround(center.x, center.y, center.z, diameter, action);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static void applyMat4(Vec3 in, Mat4 mat, Vec3 out) {
 | 
			
		||||
	public static Vec3 applyMat4(Vec3 in, Mat4 mat, Vec3 out) {
 | 
			
		||||
		if (out == null) {
 | 
			
		||||
			out = new Vec3();
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		Vec4 vec4 = Vectors.grab4();
 | 
			
		||||
		vec4.set(in, 1f);
 | 
			
		||||
 | 
			
		||||
@@ -99,27 +172,206 @@ public class VectorUtil {
 | 
			
		||||
 | 
			
		||||
		out.set(vec4.x, vec4.y, vec4.z);
 | 
			
		||||
		Vectors.release(vec4);
 | 
			
		||||
		
 | 
			
		||||
		return out;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static void applyMat4(Vec3 inOut, Mat4 mat) {
 | 
			
		||||
		Vec4 vec4 = Vectors.grab4();
 | 
			
		||||
		vec4.set(inOut, 1f);
 | 
			
		||||
 | 
			
		||||
		mat.mul(vec4);
 | 
			
		||||
 | 
			
		||||
		inOut.set(vec4.x, vec4.y, vec4.z);
 | 
			
		||||
	public static Vec3 applyMat4(Vec3 inOut, Mat4 mat) {
 | 
			
		||||
		return applyMat4(inOut, mat, inOut);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public static Vec3 rotate(Vec3 in, Vec3 axis, float angle, Vec3 out) {
 | 
			
		||||
		if (out == null) {
 | 
			
		||||
			out = new Vec3();
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		Mat3 mat = Matrices.grab3();
 | 
			
		||||
		
 | 
			
		||||
		mat.identity().rotate(angle, axis);
 | 
			
		||||
		mat.mul(in, out);
 | 
			
		||||
		
 | 
			
		||||
		Matrices.release(mat);
 | 
			
		||||
		
 | 
			
		||||
		return out;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public static Vec3 rotate(Vec3 inOut, Vec3 axis, float angle) {
 | 
			
		||||
		return rotate(inOut, axis, angle, inOut);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public static double getAngle(Vec3 from, Vec3 to, Vec3 normal) {
 | 
			
		||||
		Vec3 left = Vectors.grab3();
 | 
			
		||||
		
 | 
			
		||||
		left.set(normal).cross(from);
 | 
			
		||||
		double sign = Math.signum(left.dot(to));
 | 
			
		||||
		
 | 
			
		||||
		double result = (float) Math.acos(Glm.clamp(from.dot(to), -1, +1)) * sign;
 | 
			
		||||
		
 | 
			
		||||
		Vectors.release(left);
 | 
			
		||||
		return result;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public static Vec3 projectOnSurface(Vec3 in, Vec3 normal, Vec3 out) {
 | 
			
		||||
		if (in == out) {
 | 
			
		||||
			return projectOnSurface(in, normal);
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		if (out == null) {
 | 
			
		||||
			out = new Vec3();
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		out.set(normal).mul(-normal.dot(in)).add(in);
 | 
			
		||||
		
 | 
			
		||||
		return out;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public static Vec3 projectOnSurface(Vec3 inOut, Vec3 normal) {
 | 
			
		||||
		Vec3 buffer = Vectors.grab3();
 | 
			
		||||
		
 | 
			
		||||
		projectOnSurface(inOut, normal, buffer);
 | 
			
		||||
		inOut.set(buffer);
 | 
			
		||||
		
 | 
			
		||||
		Vectors.release(buffer);
 | 
			
		||||
		
 | 
			
		||||
		return inOut;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public static Vec3 projectOnVector(Vec3 in, Vec3 vector, Vec3 out) {
 | 
			
		||||
		if (out == null) {
 | 
			
		||||
			out = new Vec3();
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		float dot = vector.dot(in);
 | 
			
		||||
		out.set(vector).mul(dot);
 | 
			
		||||
		
 | 
			
		||||
		return out;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public static Vec3 projectOnVector(Vec3 inOut, Vec3 vector) {
 | 
			
		||||
		return projectOnVector(inOut, vector);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static Vec3 linearCombination(Vec3 va, float ka, Vec3 vb, float kb, Vec3 output) {
 | 
			
		||||
		output.set(va.x * ka + vb.x * kb, va.y * ka + vb.y * kb, va.z * ka + vb.z * kb);
 | 
			
		||||
	public static Vec3 linearCombination(
 | 
			
		||||
		Vec3 va,
 | 
			
		||||
		float ka,
 | 
			
		||||
		Vec3 vb,
 | 
			
		||||
		float kb,
 | 
			
		||||
		Vec3 output
 | 
			
		||||
	) {
 | 
			
		||||
		if (output == null) {
 | 
			
		||||
			output = new Vec3();
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		output.set(
 | 
			
		||||
			va.x * ka + vb.x * kb,
 | 
			
		||||
			va.y * ka + vb.y * kb,
 | 
			
		||||
			va.z * ka + vb.z * kb
 | 
			
		||||
		);
 | 
			
		||||
		return output;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static Vec3 linearCombination(Vec3 va, float ka, Vec3 vb, float kb, Vec3 vc, float kc, Vec3 output) {
 | 
			
		||||
		output.set(va.x * ka + vb.x * kb + vc.x * kc, va.y * ka + vb.y * kb + vc.y * kc,
 | 
			
		||||
				va.z * ka + vb.z * kb + vc.z * kc);
 | 
			
		||||
	public static Vec3 linearCombination(
 | 
			
		||||
		Vec3 va,
 | 
			
		||||
		float ka,
 | 
			
		||||
		Vec3 vb,
 | 
			
		||||
		float kb,
 | 
			
		||||
		Vec3 vc,
 | 
			
		||||
		float kc,
 | 
			
		||||
		Vec3 output
 | 
			
		||||
	) {
 | 
			
		||||
		if (output == null) {
 | 
			
		||||
			output = new Vec3();
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		output.set(
 | 
			
		||||
			va.x * ka + vb.x * kb + vc.x * kc,
 | 
			
		||||
			va.y * ka + vb.y * kb + vc.y * kc,
 | 
			
		||||
			va.z * ka + vb.z * kb + vc.z * kc
 | 
			
		||||
		);
 | 
			
		||||
		return output;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public static Vec3i sort(Vec3i input, Vec3i output) {
 | 
			
		||||
		if (output == null) {
 | 
			
		||||
			output = new Vec3i();
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		int ax = input.x, ay = input.y, az = input.z;
 | 
			
		||||
 | 
			
		||||
		if (ax > ay) {
 | 
			
		||||
			if (ax > az) {
 | 
			
		||||
				output.x = ax;
 | 
			
		||||
				output.y = ay > az ? ay : az;
 | 
			
		||||
				output.z = ay > az ? az : ay;
 | 
			
		||||
			} else {
 | 
			
		||||
				output.x = az;
 | 
			
		||||
				output.y = ax;
 | 
			
		||||
				output.z = ay;
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			if (ay > az) {
 | 
			
		||||
				output.x = ay;
 | 
			
		||||
				output.y = ax > az ? ax : az;
 | 
			
		||||
				output.z = ax > az ? az : ax;
 | 
			
		||||
			} else {
 | 
			
		||||
				output.x = az;
 | 
			
		||||
				output.y = ay;
 | 
			
		||||
				output.z = ax;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		return output;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public static Vec3 sort(Vec3 input, Vec3 output) {
 | 
			
		||||
		if (output == null) {
 | 
			
		||||
			output = new Vec3();
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		float ax = input.x, ay = input.y, az = input.z;
 | 
			
		||||
 | 
			
		||||
		if (ax > ay) {
 | 
			
		||||
			if (ax > az) {
 | 
			
		||||
				output.x = ax;
 | 
			
		||||
				output.y = ay > az ? ay : az;
 | 
			
		||||
				output.z = ay > az ? az : ay;
 | 
			
		||||
			} else {
 | 
			
		||||
				output.x = az;
 | 
			
		||||
				output.y = ax;
 | 
			
		||||
				output.z = ay;
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			if (ay > az) {
 | 
			
		||||
				output.x = ay;
 | 
			
		||||
				output.y = ax > az ? ax : az;
 | 
			
		||||
				output.z = ax > az ? az : ax;
 | 
			
		||||
			} else {
 | 
			
		||||
				output.x = az;
 | 
			
		||||
				output.y = ay;
 | 
			
		||||
				output.z = ax;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		return output;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public static Vec3i sortAfterAbs(Vec3i input, Vec3i output) {
 | 
			
		||||
		if (output == null) {
 | 
			
		||||
			output = new Vec3i();
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		input.abs(output);
 | 
			
		||||
		return sort(output, output);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public static Vec3 sortAfterAbs(Vec3 input, Vec3 output) {
 | 
			
		||||
		if (output == null) {
 | 
			
		||||
			output = new Vec3();
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		input.abs(output);
 | 
			
		||||
		return sort(output, output);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static float get(Vec2 v, Axis a) {
 | 
			
		||||
		switch (a) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
package ru.windcorp.progressia.common.util.noise.discrete;
 | 
			
		||||
 | 
			
		||||
public interface DiscreteNoise<T> {
 | 
			
		||||
	
 | 
			
		||||
	T get(double x, double y);
 | 
			
		||||
	T get(double x, double y, double z);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
package ru.windcorp.progressia.common.util.noise.discrete;
 | 
			
		||||
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import com.google.common.collect.ImmutableList;
 | 
			
		||||
 | 
			
		||||
public class WorleyProceduralNoise<T> implements DiscreteNoise<T> {
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	 * 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<T> {
 | 
			
		||||
		private final T value;
 | 
			
		||||
		private final double chance;
 | 
			
		||||
 | 
			
		||||
		public Entry(T value, double chance) {
 | 
			
		||||
			this.value = value;
 | 
			
		||||
			this.chance = chance;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static class Builder<T> {
 | 
			
		||||
 | 
			
		||||
		com.google.common.collect.ImmutableList.Builder<Entry<T>> builder = ImmutableList.builder();
 | 
			
		||||
 | 
			
		||||
		public Builder<T> add(T value, double chance) {
 | 
			
		||||
			builder.add(new Entry<>(value, chance));
 | 
			
		||||
			return this;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public WorleyProceduralNoise<T> build(long seed) {
 | 
			
		||||
			return new WorleyProceduralNoise<>(this, seed);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static <T> Builder<T> builder() {
 | 
			
		||||
		return new Builder<>();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private final Entry<?>[] entries;
 | 
			
		||||
	private final long seed;
 | 
			
		||||
 | 
			
		||||
	public WorleyProceduralNoise(Builder<T> builder, long seed) {
 | 
			
		||||
		this(builder.builder.build(), seed);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public WorleyProceduralNoise(Collection<? extends Entry<? extends T>> entries, long seed) {
 | 
			
		||||
		this.entries = new Entry<?>[entries.size()];
 | 
			
		||||
 | 
			
		||||
		double chancesSum = 0;
 | 
			
		||||
		for (Entry<? extends T> entry : entries) {
 | 
			
		||||
			chancesSum += entry.chance;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		int i = 0;
 | 
			
		||||
		for (Entry<? extends T> entry : entries) {
 | 
			
		||||
			this.entries[i] = new Entry<T>(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;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
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<ChunkData, BlockData, TileData, TileDataStack> {
 | 
			
		||||
 | 
			
		||||
	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<ChunkDataListener> 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<Vec3i> action) {
 | 
			
		||||
		VectorUtil.iterateCuboid(0, 0, 0, BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK, action);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void forEachTileStack(Consumer<TileDataStack> 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<TileDataStack, TileData> action) {
 | 
			
		||||
		forEachTileStack(stack -> stack.forEach(tileData -> action.accept(stack, tileData)));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public WorldData getWorld() {
 | 
			
		||||
		return world;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public Collection<ChunkDataListener> 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<BlockData, TileData, TileDataStack, TileDataReference, ChunkData> {
 | 
			
		||||
	
 | 
			
		||||
	// currently empty
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,81 +15,78 @@
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
package ru.windcorp.progressia.common.world;
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ public class ChunkDataListeners {
 | 
			
		||||
	public static WorldDataListener createAdder(Supplier<ChunkDataListener> listenerSupplier) {
 | 
			
		||||
		return new WorldDataListener() {
 | 
			
		||||
			@Override
 | 
			
		||||
			public void getChunkListeners(WorldData world, Vec3i chunk, Consumer<ChunkDataListener> chunkListenerSink) {
 | 
			
		||||
			public void getChunkListeners(DefaultWorldData world, Vec3i chunk, Consumer<ChunkDataListener> chunkListenerSink) {
 | 
			
		||||
				chunkListenerSink.accept(listenerSupplier.get());
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
 
 | 
			
		||||
@@ -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<BlockData, TileData, TileDataStackRO, TileDataReferenceRO, ChunkDataRO> {
 | 
			
		||||
 | 
			
		||||
	// currently empty
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
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<ChunkDataListener> 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<ChunkDataListener> 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<TileData> 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;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
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<DefaultChunkData> chunksByPos = new LongBasedChunkMap<>(
 | 
			
		||||
		TCollections.synchronizedMap(new TLongObjectHashMap<>())
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	private final Collection<DefaultChunkData> chunks = Collections.unmodifiableCollection(chunksByPos.values());
 | 
			
		||||
 | 
			
		||||
	private final TLongObjectMap<EntityData> entitiesById = TCollections.synchronizedMap(new TLongObjectHashMap<>());
 | 
			
		||||
 | 
			
		||||
	private final Collection<EntityData> entities = Collections.unmodifiableCollection(entitiesById.valueCollection());
 | 
			
		||||
 | 
			
		||||
	private GravityModel gravityModel = null;
 | 
			
		||||
 | 
			
		||||
	private float time = 0;
 | 
			
		||||
 | 
			
		||||
	private final Collection<WorldDataListener> 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<DefaultChunkData> getChunks() {
 | 
			
		||||
		return chunks;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public ChunkSet getLoadedChunks() {
 | 
			
		||||
		return chunksByPos.keys();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public Collection<EntityData> getEntities() {
 | 
			
		||||
		return entities;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void forEachEntity(Consumer<? super EntityData> action) {
 | 
			
		||||
		synchronized (entitiesById) { // TODO HORRIBLY MUTILATE THE CORPSE OF
 | 
			
		||||
										// TROVE4J so that
 | 
			
		||||
										// gnu.trove.impl.sync.SynchronizedCollection.forEach
 | 
			
		||||
										// is synchronized
 | 
			
		||||
			getEntities().forEach(action);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public TLongSet getLoadedEntities() {
 | 
			
		||||
		return entitiesById.keySet();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void addChunkListeners(DefaultChunkData chunk) {
 | 
			
		||||
		getListeners().forEach(l -> l.getChunkListeners(this, chunk.getPosition(), chunk::addListener));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public synchronized void addChunk(DefaultChunkData chunk) {
 | 
			
		||||
		addChunkListeners(chunk);
 | 
			
		||||
 | 
			
		||||
		DefaultChunkData previous = chunksByPos.get(chunk);
 | 
			
		||||
		if (previous != null) {
 | 
			
		||||
			throw new IllegalArgumentException(
 | 
			
		||||
				String.format(
 | 
			
		||||
					"Chunk at (%d; %d; %d) already exists",
 | 
			
		||||
					chunk.getPosition().x,
 | 
			
		||||
					chunk.getPosition().y,
 | 
			
		||||
					chunk.getPosition().z
 | 
			
		||||
				)
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		chunksByPos.put(chunk, chunk);
 | 
			
		||||
 | 
			
		||||
		chunk.onLoaded();
 | 
			
		||||
		getListeners().forEach(l -> l.onChunkLoaded(this, chunk));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public synchronized void removeChunk(DefaultChunkData chunk) {
 | 
			
		||||
		getListeners().forEach(l -> l.beforeChunkUnloaded(this, chunk));
 | 
			
		||||
		chunk.beforeUnloaded();
 | 
			
		||||
 | 
			
		||||
		chunksByPos.remove(chunk);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void setBlock(Vec3i blockInWorld, BlockData block, boolean notify) {
 | 
			
		||||
		DefaultChunkData chunk = getChunkByBlock(blockInWorld);
 | 
			
		||||
		if (chunk == null)
 | 
			
		||||
			throw new IllegalCoordinatesException(
 | 
			
		||||
				"Coordinates "
 | 
			
		||||
					+ "(" + blockInWorld.x + "; " + blockInWorld.y + "; " + blockInWorld.z + ") "
 | 
			
		||||
					+ "do not belong to a loaded chunk"
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
		chunk.setBlock(Coordinates.convertInWorldToInChunk(blockInWorld, null), block, notify);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public TileDataStack getTiles(Vec3i blockInWorld, BlockFace face) {
 | 
			
		||||
		return WorldData.super.getTiles(blockInWorld, face);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public EntityData getEntity(long entityId) {
 | 
			
		||||
		return entitiesById.get(entityId);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void addEntity(EntityData entity) {
 | 
			
		||||
		Objects.requireNonNull(entity, "entity");
 | 
			
		||||
 | 
			
		||||
		EntityData previous = entitiesById.putIfAbsent(entity.getEntityId(), entity);
 | 
			
		||||
 | 
			
		||||
		if (previous != null) {
 | 
			
		||||
			String message = "Cannot add entity " + entity + ": ";
 | 
			
		||||
 | 
			
		||||
			if (previous == entity) {
 | 
			
		||||
				message += "already present";
 | 
			
		||||
			} else {
 | 
			
		||||
				message += "entity with the same EntityID already present (" + previous + ")";
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			throw new IllegalStateException(message);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		getListeners().forEach(l -> l.onEntityAdded(this, entity));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void removeEntity(long entityId) {
 | 
			
		||||
		synchronized (entitiesById) {
 | 
			
		||||
			EntityData entity = entitiesById.get(entityId);
 | 
			
		||||
 | 
			
		||||
			if (entity == null) {
 | 
			
		||||
				throw new IllegalArgumentException(
 | 
			
		||||
					"Entity with EntityID " + EntityData.formatEntityId(entityId) + " not present"
 | 
			
		||||
				);
 | 
			
		||||
			} else {
 | 
			
		||||
				removeEntity(entity);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void removeEntity(EntityData entity) {
 | 
			
		||||
		Objects.requireNonNull(entity, "entity");
 | 
			
		||||
 | 
			
		||||
		getListeners().forEach(l -> l.beforeEntityRemoved(this, entity));
 | 
			
		||||
		entitiesById.remove(entity.getEntityId());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public <SE extends StatefulObject & EntityGeneric> void changeEntity(SE entity, StateChange<SE> 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<WorldDataListener> getListeners() {
 | 
			
		||||
		return listeners;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void addListener(WorldDataListener e) {
 | 
			
		||||
		listeners.add(e);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void removeListener(WorldDataListener o) {
 | 
			
		||||
		listeners.remove(o);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
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.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * A gravity model may be queried for the vector of gravitational acceleration
 | 
			
		||||
 * that should affect an object. This vector is, generally speaking, a function
 | 
			
		||||
 * of space: gravity in two different locations may vary. Gravity may also be a
 | 
			
		||||
 * zero vector.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * The vector of gravitational acceleration defines the up direction. Up vector
 | 
			
		||||
 * is defined as the additive inverse of the normalized gravitational
 | 
			
		||||
 * acceleration vector or {@code (0; 0; 0)} if there is no gravity.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Separately from the gravitational acceleration and the up vectors, a
 | 
			
		||||
 * <em>discrete up</em> vector field is specified by a gravity model. This field
 | 
			
		||||
 * is defined for each chunk uniquely and may only take the value of one of the
 | 
			
		||||
 * six {@linkplain AbsFace absolute directions}. This vector specifies the
 | 
			
		||||
 * rotation of blocks, tiles and other objects that may not have a
 | 
			
		||||
 * non-axis-aligned direction. Discrete up vector must be specified even for
 | 
			
		||||
 * chunks that have a zero or an ambiguous up direction. Although discrete up
 | 
			
		||||
 * direction is not technically linked to the up direction, is it expected by
 | 
			
		||||
 * the players that they generally align.
 | 
			
		||||
 * 
 | 
			
		||||
 * @author javapony
 | 
			
		||||
 */
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
package ru.windcorp.progressia.common.world;
 | 
			
		||||
 | 
			
		||||
import ru.windcorp.progressia.common.util.namespaces.NamespacedFactoryRegistry;
 | 
			
		||||
 | 
			
		||||
public class GravityModelRegistry extends NamespacedFactoryRegistry<GravityModel> {
 | 
			
		||||
	
 | 
			
		||||
	public static final GravityModelRegistry INSTANCE = new GravityModelRegistry();
 | 
			
		||||
	
 | 
			
		||||
	public static GravityModelRegistry getInstance() {
 | 
			
		||||
		return INSTANCE;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -26,6 +26,6 @@ public abstract class PacketAffectWorld extends Packet {
 | 
			
		||||
		super(id);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public abstract void apply(WorldData world);
 | 
			
		||||
	public abstract void apply(DefaultWorldData world);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
			}
 | 
			
		||||
 
 | 
			
		||||
@@ -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) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
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);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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<BlockData, TileData, TileDataStack, TileDataReference, ChunkData> {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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<BlockData, TileData, TileDataStackRO, TileDataReferenceRO, ChunkDataRO> {
 | 
			
		||||
 | 
			
		||||
	// currently empty
 | 
			
		||||
	
 | 
			
		||||
}
 | 
			
		||||
@@ -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<BlockData, TileData, TileDataStack, TileDataReference, ChunkData> {
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	default boolean isFull() {
 | 
			
		||||
		return TileDataStackRO.super.isFull();
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	/*
 | 
			
		||||
	 * Method specialization
 | 
			
		||||
	 */
 | 
			
		||||
	
 | 
			
		||||
	@Override
 | 
			
		||||
	TileDataReference getReference(int index);
 | 
			
		||||
	
 | 
			
		||||
	@Override
 | 
			
		||||
	ChunkData getChunk();
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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<BlockData, TileData, TileDataStackRO, TileDataReferenceRO, ChunkDataRO> {
 | 
			
		||||
	
 | 
			
		||||
	// currently empty
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
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<BlockData, TileData, TileDataStack, ChunkData, EntityData> {
 | 
			
		||||
 | 
			
		||||
	private final ChunkMap<ChunkData> chunksByPos = new LongBasedChunkMap<>(
 | 
			
		||||
			TCollections.synchronizedMap(new TLongObjectHashMap<>()));
 | 
			
		||||
 | 
			
		||||
	private final Collection<ChunkData> chunks = Collections.unmodifiableCollection(chunksByPos.values());
 | 
			
		||||
 | 
			
		||||
	private final TLongObjectMap<EntityData> entitiesById = TCollections.synchronizedMap(new TLongObjectHashMap<>());
 | 
			
		||||
 | 
			
		||||
	private final Collection<EntityData> entities = Collections.unmodifiableCollection(entitiesById.valueCollection());
 | 
			
		||||
 | 
			
		||||
	private float time = 0;
 | 
			
		||||
 | 
			
		||||
	private final Collection<WorldDataListener> listeners = Collections.synchronizedCollection(new ArrayList<>());
 | 
			
		||||
 | 
			
		||||
	public WorldData() {
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
public interface WorldData
 | 
			
		||||
	extends WorldDataRO, WorldGenericWO<BlockData, TileData, TileDataStack, TileDataReference, ChunkData, EntityData> {
 | 
			
		||||
 | 
			
		||||
	@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<ChunkData> getChunks() {
 | 
			
		||||
		return chunks;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public ChunkSet getLoadedChunks() {
 | 
			
		||||
		return chunksByPos.keys();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ChunkData getChunk(Vec3i pos);
 | 
			
		||||
	
 | 
			
		||||
	@Override
 | 
			
		||||
	public Collection<EntityData> getEntities() {
 | 
			
		||||
		return entities;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Collection<? extends ChunkData> getChunks();
 | 
			
		||||
	
 | 
			
		||||
	// TODO: rename WGRO.forEachChunk -> forEachChunkRO and define WGWO.forEachChunk
 | 
			
		||||
	
 | 
			
		||||
	@Override
 | 
			
		||||
	public void forEachEntity(Consumer<? super EntityData> action) {
 | 
			
		||||
		synchronized (entitiesById) { // TODO HORRIBLY MUTILATE THE CORPSE OF
 | 
			
		||||
										// TROVE4J so that
 | 
			
		||||
										// gnu.trove.impl.sync.SynchronizedCollection.forEach
 | 
			
		||||
										// is synchronized
 | 
			
		||||
			getEntities().forEach(action);
 | 
			
		||||
		}
 | 
			
		||||
	default ChunkData getChunkByBlock(Vec3i blockInWorld) {
 | 
			
		||||
		return (ChunkData) WorldDataRO.super.getChunkByBlock(blockInWorld);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public TLongSet getLoadedEntities() {
 | 
			
		||||
		return entitiesById.keySet();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void addChunkListeners(ChunkData chunk) {
 | 
			
		||||
		getListeners().forEach(l -> l.getChunkListeners(this, chunk.getPosition(), chunk::addListener));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public synchronized void addChunk(ChunkData chunk) {
 | 
			
		||||
		addChunkListeners(chunk);
 | 
			
		||||
 | 
			
		||||
		ChunkData previous = chunksByPos.get(chunk);
 | 
			
		||||
		if (previous != null) {
 | 
			
		||||
			throw new IllegalArgumentException(String.format("Chunk at (%d; %d; %d) already exists",
 | 
			
		||||
					chunk.getPosition().x, chunk.getPosition().y, chunk.getPosition().z));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		chunksByPos.put(chunk, chunk);
 | 
			
		||||
 | 
			
		||||
		chunk.onLoaded();
 | 
			
		||||
		getListeners().forEach(l -> l.onChunkLoaded(this, chunk));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public synchronized void removeChunk(ChunkData chunk) {
 | 
			
		||||
		getListeners().forEach(l -> l.beforeChunkUnloaded(this, chunk));
 | 
			
		||||
		chunk.beforeUnloaded();
 | 
			
		||||
 | 
			
		||||
		chunksByPos.remove(chunk);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void setBlock(Vec3i blockInWorld, BlockData block, boolean notify) {
 | 
			
		||||
		ChunkData chunk = getChunkByBlock(blockInWorld);
 | 
			
		||||
		if (chunk == null)
 | 
			
		||||
			throw new IllegalCoordinatesException("Coordinates " + "(" + blockInWorld.x + "; " + blockInWorld.y + "; "
 | 
			
		||||
					+ blockInWorld.z + ") " + "do not belong to a loaded chunk");
 | 
			
		||||
 | 
			
		||||
		chunk.setBlock(Coordinates.convertInWorldToInChunk(blockInWorld, null), block, notify);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public EntityData getEntity(long entityId) {
 | 
			
		||||
		return entitiesById.get(entityId);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void addEntity(EntityData entity) {
 | 
			
		||||
		Objects.requireNonNull(entity, "entity");
 | 
			
		||||
 | 
			
		||||
		EntityData previous = entitiesById.putIfAbsent(entity.getEntityId(), entity);
 | 
			
		||||
 | 
			
		||||
		if (previous != null) {
 | 
			
		||||
			String message = "Cannot add entity " + entity + ": ";
 | 
			
		||||
 | 
			
		||||
			if (previous == entity) {
 | 
			
		||||
				message += "already present";
 | 
			
		||||
			} else {
 | 
			
		||||
				message += "entity with the same EntityID already present (" + previous + ")";
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			throw new IllegalStateException(message);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		getListeners().forEach(l -> l.onEntityAdded(this, entity));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void removeEntity(long entityId) {
 | 
			
		||||
		synchronized (entitiesById) {
 | 
			
		||||
			EntityData entity = entitiesById.get(entityId);
 | 
			
		||||
 | 
			
		||||
			if (entity == null) {
 | 
			
		||||
				throw new IllegalArgumentException(
 | 
			
		||||
						"Entity with EntityID " + EntityData.formatEntityId(entityId) + " not present");
 | 
			
		||||
			} else {
 | 
			
		||||
				removeEntity(entity);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void removeEntity(EntityData entity) {
 | 
			
		||||
		Objects.requireNonNull(entity, "entity");
 | 
			
		||||
 | 
			
		||||
		getListeners().forEach(l -> l.beforeEntityRemoved(this, entity));
 | 
			
		||||
		entitiesById.remove(entity.getEntityId());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public float getTime() {
 | 
			
		||||
		return time;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void advanceTime(float change) {
 | 
			
		||||
		this.time += change;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public CollisionModel getCollisionModelOfBlock(Vec3i blockInWorld) {
 | 
			
		||||
		ChunkData chunk = getChunkByBlock(blockInWorld);
 | 
			
		||||
		if (chunk == null)
 | 
			
		||||
			return null;
 | 
			
		||||
 | 
			
		||||
		BlockData block = chunk.getBlock(Coordinates.convertInWorldToInChunk(blockInWorld, null));
 | 
			
		||||
		if (block == null)
 | 
			
		||||
			return null;
 | 
			
		||||
		return block.getCollisionModel();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public Collection<WorldDataListener> 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);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
package ru.windcorp.progressia.common.world;
 | 
			
		||||
 | 
			
		||||
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<ChunkDataListener> chunkListenerSink) {
 | 
			
		||||
	default void getChunkListeners(DefaultWorldData world, Vec3i chunk, Consumer<ChunkDataListener> 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) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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<BlockData, TileData, TileDataStackRO, TileDataReferenceRO, ChunkDataRO, EntityData> {
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Returns in-world time since creation. World time is zero before and
 | 
			
		||||
	 * during first tick.
 | 
			
		||||
	 * <p>
 | 
			
		||||
	 * 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();
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
@@ -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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
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<BlockFace> ALL_FACES = ImmutableList.of(TOP, BOTTOM, NORTH, SOUTH, WEST, EAST);
 | 
			
		||||
 | 
			
		||||
	static {
 | 
			
		||||
		link(TOP, BOTTOM);
 | 
			
		||||
		link(NORTH, SOUTH);
 | 
			
		||||
		link(WEST, EAST);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static final ImmutableList<BlockFace> PRIMARY_FACES = ALL_FACES.stream().filter(BlockFace::isPrimary)
 | 
			
		||||
			.collect(ImmutableList.toImmutableList());
 | 
			
		||||
 | 
			
		||||
	private static final ImmutableList<BlockFace> 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<BlockFace> getFaces() {
 | 
			
		||||
		return ALL_FACES;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static ImmutableList<BlockFace> getPrimaryFaces() {
 | 
			
		||||
		return PRIMARY_FACES;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static ImmutableList<BlockFace> getSecondaryFaces() {
 | 
			
		||||
		return SECONDARY_FACES;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static void link(BlockFace a, BlockFace b) {
 | 
			
		||||
		a.counterFace = b;
 | 
			
		||||
		b.counterFace = a;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static <E> ImmutableMap<BlockFace, E> mapToFaces(E top, E bottom, E north, E south, E east, E west) {
 | 
			
		||||
		return ImmutableMap.<BlockFace, E>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();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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);
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
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<BlockData, TileData, EntityData>,
 | 
			
		||||
	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);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
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<BlockData, TileData, EntityData>,
 | 
			
		||||
	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);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
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
 | 
			
		||||
 * <em>relevant</em> and <em>implied</em> should be understood to refer to the
 | 
			
		||||
 * aforementioned location.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * 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.
 | 
			
		||||
 * <h2 id="validity">Context Validity</h2>
 | 
			
		||||
 * 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.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * 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.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * In practice, context objects are typically highly volatile. They are <em>not
 | 
			
		||||
 * thread-safe</em> and are often pooled and reused.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * <h2 id="subcontexting">Subcontexting</h2>
 | 
			
		||||
 * Context objects allow <em>subcontexting</em>. 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.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Modification methods are usually named <em>{@code pushXXX}</em>. 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.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Although various push methods declare differing result types, the same object
 | 
			
		||||
 * is always returned:
 | 
			
		||||
 * 
 | 
			
		||||
 * <pre>
 | 
			
		||||
 * someContext.pushXXX() == someContext
 | 
			
		||||
 * </pre>
 | 
			
		||||
 * 
 | 
			
		||||
 * Therefore invoking {@link #pop()} is valid using both the original reference
 | 
			
		||||
 * and the obtained reference.
 | 
			
		||||
 * <h3>Subcontexting example</h3>
 | 
			
		||||
 * 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
 | 
			
		||||
 * 
 | 
			
		||||
 * <pre>
 | 
			
		||||
 * TileStackDataContext b = a.push(RelFace.TOP);
 | 
			
		||||
 * processTileStack(b);
 | 
			
		||||
 * b.pop();
 | 
			
		||||
 * </pre>
 | 
			
		||||
 * 
 | 
			
		||||
 * One can improve readability by eliminating the temporary variable:
 | 
			
		||||
 * 
 | 
			
		||||
 * <pre>
 | 
			
		||||
 * processTileStack(a.push(RelFace.TOP));
 | 
			
		||||
 * a.pop();
 | 
			
		||||
 * </pre>
 | 
			
		||||
 * 
 | 
			
		||||
 * 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.
 | 
			
		||||
	 * <p>
 | 
			
		||||
	 * 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.
 | 
			
		||||
	 * <p>
 | 
			
		||||
	 * 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.
 | 
			
		||||
	 * <p>
 | 
			
		||||
	 * 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.
 | 
			
		||||
	 * <p>
 | 
			
		||||
	 * Context objects may be modified temporarily with various push methods
 | 
			
		||||
	 * (see <a href="#subcontexting">subcontexting</a>). 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.
 | 
			
		||||
	 * <p>
 | 
			
		||||
	 * This method may be invoked using either the original reference or the
 | 
			
		||||
	 * reference provided by push method.
 | 
			
		||||
	 * <p>
 | 
			
		||||
	 * This method fails with an {@link IllegalStateException} when there are no
 | 
			
		||||
	 * modifications to revert.
 | 
			
		||||
	 */
 | 
			
		||||
	void pop();
 | 
			
		||||
	
 | 
			
		||||
	default <T> 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;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user