Merge branch 'master' into falling-block
This commit is contained in:
commit
efff41a6db
@ -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.EntityAnchor;
|
||||||
import ru.windcorp.progressia.client.graphics.world.LocalPlayer;
|
import ru.windcorp.progressia.client.graphics.world.LocalPlayer;
|
||||||
import ru.windcorp.progressia.client.world.WorldRender;
|
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;
|
import ru.windcorp.progressia.common.world.entity.EntityData;
|
||||||
|
|
||||||
public class Client {
|
public class Client {
|
||||||
@ -36,7 +36,7 @@ public class Client {
|
|||||||
|
|
||||||
private final ServerCommsChannel comms;
|
private final ServerCommsChannel comms;
|
||||||
|
|
||||||
public Client(WorldData world, ServerCommsChannel comms) {
|
public Client(DefaultWorldData world, ServerCommsChannel comms) {
|
||||||
this.world = new WorldRender(world, this);
|
this.world = new WorldRender(world, this);
|
||||||
this.comms = comms;
|
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.comms.localhost.LocalServerCommsChannel;
|
||||||
import ru.windcorp.progressia.client.graphics.GUI;
|
import ru.windcorp.progressia.client.graphics.GUI;
|
||||||
import ru.windcorp.progressia.client.graphics.world.LayerWorld;
|
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.server.ServerState;
|
||||||
import ru.windcorp.progressia.test.LayerAbout;
|
import ru.windcorp.progressia.test.LayerAbout;
|
||||||
import ru.windcorp.progressia.test.LayerTestUI;
|
import ru.windcorp.progressia.test.LayerTestUI;
|
||||||
@ -41,7 +41,7 @@ public class ClientState {
|
|||||||
|
|
||||||
public static void connectToLocalServer() {
|
public static void connectToLocalServer() {
|
||||||
|
|
||||||
WorldData world = new WorldData();
|
DefaultWorldData world = new DefaultWorldData();
|
||||||
|
|
||||||
LocalServerCommsChannel channel = new LocalServerCommsChannel(ServerState.getInstance());
|
LocalServerCommsChannel channel = new LocalServerCommsChannel(ServerState.getInstance());
|
||||||
|
|
||||||
|
@ -15,20 +15,32 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.client.graphics;
|
package ru.windcorp.progressia.client.graphics;
|
||||||
|
|
||||||
import glm.vec._4.Vec4;
|
import glm.vec._4.Vec4;
|
||||||
|
|
||||||
public class Colors {
|
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_RED = toVector(0xFFFF0000),
|
||||||
DEBUG_CYAN = toVector(0xFF00FFFF), DEBUG_MAGENTA = toVector(0xFFFF00FF),
|
DEBUG_GREEN = toVector(0xFF00FF00),
|
||||||
DEBUG_YELLOW = toVector(0xFFFFFF00);
|
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) {
|
public static Vec4 toVector(int argb) {
|
||||||
return toVector(argb, new Vec4());
|
return toVector(argb, new Vec4());
|
||||||
|
@ -24,6 +24,7 @@ import java.util.List;
|
|||||||
|
|
||||||
import com.google.common.eventbus.Subscribe;
|
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.CursorEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.input.FrameResizeEvent;
|
import ru.windcorp.progressia.client.graphics.input.FrameResizeEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||||
@ -57,15 +58,24 @@ public class GUI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void addBottomLayer(Layer layer) {
|
public static void addBottomLayer(Layer layer) {
|
||||||
modify(layers -> layers.add(layer));
|
modify(layers -> {
|
||||||
|
layers.add(layer);
|
||||||
|
layer.onAdded();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void addTopLayer(Layer layer) {
|
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) {
|
public static void removeLayer(Layer layer) {
|
||||||
modify(layers -> layers.remove(layer));
|
modify(layers -> {
|
||||||
|
layers.remove(layer);
|
||||||
|
layer.onRemoved();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void modify(LayerStackModification mod) {
|
private static void modify(LayerStackModification mod) {
|
||||||
@ -78,12 +88,33 @@ public class GUI {
|
|||||||
|
|
||||||
public static void render() {
|
public static void render() {
|
||||||
synchronized (LAYERS) {
|
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) {
|
for (int i = LAYERS.size() - 1; i >= 0; --i) {
|
||||||
LAYERS.get(i).render();
|
LAYERS.get(i).render();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,15 +30,52 @@ public abstract class Layer {
|
|||||||
private boolean hasInitialized = false;
|
private boolean hasInitialized = false;
|
||||||
|
|
||||||
private final AtomicBoolean isValid = new AtomicBoolean(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) {
|
public Layer(String name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Layer " + name;
|
return "Layer " + name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CursorPolicy getCursorPolicy() {
|
||||||
|
return cursorPolicy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCursorPolicy(CursorPolicy cursorPolicy) {
|
||||||
|
this.cursorPolicy = cursorPolicy;
|
||||||
|
}
|
||||||
|
|
||||||
void render() {
|
void render() {
|
||||||
GraphicsInterface.startNextLayer();
|
GraphicsInterface.startNextLayer();
|
||||||
@ -78,5 +115,13 @@ public abstract class Layer {
|
|||||||
protected int getHeight() {
|
protected int getHeight() {
|
||||||
return GraphicsInterface.getFrameHeight();
|
return GraphicsInterface.getFrameHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void onAdded() {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onRemoved() {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -179,4 +179,18 @@ public class GraphicsBackend {
|
|||||||
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
|
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
|
||||||
return vidmode.refreshRate();
|
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());
|
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);
|
GraphicsBackend.setWindowHandle(handle);
|
||||||
|
|
||||||
glfwSetInputMode(handle, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
|
|
||||||
|
|
||||||
glfwMakeContextCurrent(handle);
|
glfwMakeContextCurrent(handle);
|
||||||
glfwSwapInterval(0); // TODO: remove after config system is added
|
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
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.client.graphics.flat;
|
package ru.windcorp.progressia.client.graphics.flat;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -29,8 +29,8 @@ import glm.vec._3.Vec3;
|
|||||||
import glm.vec._4.Vec4;
|
import glm.vec._4.Vec4;
|
||||||
import ru.windcorp.progressia.client.graphics.Colors;
|
import ru.windcorp.progressia.client.graphics.Colors;
|
||||||
import ru.windcorp.progressia.client.graphics.backend.Usage;
|
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.Faces;
|
import ru.windcorp.progressia.client.graphics.model.ShapeParts;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Shape;
|
import ru.windcorp.progressia.client.graphics.model.Shape;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||||
@ -45,7 +45,11 @@ public class RenderTarget {
|
|||||||
private final Mat4 transform;
|
private final Mat4 transform;
|
||||||
private final Renderable renderable;
|
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) {
|
for (TransformedMask mask : masks) {
|
||||||
this.masks.pushMask(mask);
|
this.masks.pushMask(mask);
|
||||||
}
|
}
|
||||||
@ -80,7 +84,7 @@ public class RenderTarget {
|
|||||||
|
|
||||||
private final Deque<TransformedMask> maskStack = new LinkedList<>();
|
private final Deque<TransformedMask> maskStack = new LinkedList<>();
|
||||||
private final Deque<Mat4> transformStack = 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;
|
private int depth = 0;
|
||||||
|
|
||||||
@ -90,19 +94,33 @@ public class RenderTarget {
|
|||||||
|
|
||||||
protected void assembleCurrentClipFromFaces() {
|
protected void assembleCurrentClipFromFaces() {
|
||||||
if (!currentClipFaces.isEmpty()) {
|
if (!currentClipFaces.isEmpty()) {
|
||||||
Face[] faces = currentClipFaces.toArray(new Face[currentClipFaces.size()]);
|
ShapePart[] faces = currentClipFaces.toArray(
|
||||||
|
new ShapePart[currentClipFaces.size()]
|
||||||
|
);
|
||||||
currentClipFaces.clear();
|
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() {
|
public Clip[] assemble() {
|
||||||
assembleCurrentClipFromFaces();
|
assembleCurrentClipFromFaces();
|
||||||
|
|
||||||
Clip[] result = assembled.toArray(new Clip[assembled.size()]);
|
Clip[] result = assembled.toArray(
|
||||||
|
new Clip[assembled.size()]
|
||||||
|
);
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
|
|
||||||
@ -124,11 +142,21 @@ public class RenderTarget {
|
|||||||
|
|
||||||
pushTransform(new Mat4().identity().translate(startX, startY, 0));
|
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) {
|
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) {
|
public void pushMaskStartSize(int x, int y, int width, int height) {
|
||||||
@ -161,58 +189,139 @@ public class RenderTarget {
|
|||||||
|
|
||||||
public void addCustomRenderer(Renderable renderable) {
|
public void addCustomRenderer(Renderable renderable) {
|
||||||
assembleCurrentClipFromFaces();
|
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);
|
currentClipFaces.add(face);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void drawTexture(int x, int y, int width, int height, Vec4 color, Texture texture) {
|
public void drawTexture(
|
||||||
addFaceToCurrentClip(createRectagleFace(x, y, width, height, color, texture));
|
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);
|
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);
|
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);
|
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));
|
fill(x, y, width, height, Colors.toVector(color));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void fill(Vec4 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) {
|
public void fill(int color) {
|
||||||
fill(Colors.toVector(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--;
|
float depth = this.depth--;
|
||||||
|
|
||||||
return Faces.createRectangle(FlatRenderProgram.getDefault(), texture, color, new Vec3(x, y, depth),
|
return ShapeParts.createRectangle(
|
||||||
new Vec3(width, 0, 0), new Vec3(0, height, 0), false);
|
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);
|
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) {
|
public Shape createRectagle(
|
||||||
return new Shape(Usage.STATIC, FlatRenderProgram.getDefault(),
|
int x,
|
||||||
createRectagleFace(x, y, width, height, color, texture));
|
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);
|
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
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.client.graphics.font;
|
package ru.windcorp.progressia.client.graphics.font;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -33,8 +33,8 @@ import gnu.trove.stack.TIntStack;
|
|||||||
import gnu.trove.stack.array.TIntArrayStack;
|
import gnu.trove.stack.array.TIntArrayStack;
|
||||||
import ru.windcorp.progressia.client.graphics.Colors;
|
import ru.windcorp.progressia.client.graphics.Colors;
|
||||||
import ru.windcorp.progressia.client.graphics.backend.Usage;
|
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.Faces;
|
import ru.windcorp.progressia.client.graphics.model.ShapeParts;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Shape;
|
import ru.windcorp.progressia.client.graphics.model.Shape;
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
|
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
|
||||||
@ -105,7 +105,13 @@ public abstract class SpriteTypeface extends Typeface {
|
|||||||
public abstract ShapeRenderProgram getProgram();
|
public abstract ShapeRenderProgram getProgram();
|
||||||
|
|
||||||
@Override
|
@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)
|
if (output == null)
|
||||||
output = new Vec2i();
|
output = new Vec2i();
|
||||||
|
|
||||||
@ -135,8 +141,19 @@ public abstract class SpriteTypeface extends Typeface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Shape createCharShape(char c) {
|
private Shape createCharShape(char c) {
|
||||||
return new Shape(Usage.STATIC, getProgram(), Faces.createRectangle(getProgram(), getTexture(c), Colors.WHITE,
|
return new Shape(
|
||||||
Vectors.ZERO_3, new Vec3(getWidth(c), 0, 0), new Vec3(0, getHeight(), 0), false));
|
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 {
|
private class DynamicText implements Renderable, Drawer {
|
||||||
@ -147,8 +164,19 @@ public abstract class SpriteTypeface extends Typeface {
|
|||||||
private final float maxWidth;
|
private final float maxWidth;
|
||||||
private final Vec4 color;
|
private final Vec4 color;
|
||||||
|
|
||||||
private final Renderable unitLine = new Shape(Usage.STATIC, getProgram(), Faces.createRectangle(getProgram(),
|
private final Renderable unitLine = new Shape(
|
||||||
null, Vectors.UNIT_4, Vectors.ZERO_3, new Vec3(1, 0, 0), new Vec3(0, 1, 0), false));
|
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 class DynamicWorkspace extends Workspace {
|
||||||
private ShapeRenderHelper renderer;
|
private ShapeRenderHelper renderer;
|
||||||
@ -162,7 +190,13 @@ public abstract class SpriteTypeface extends Typeface {
|
|||||||
|
|
||||||
private final DynamicWorkspace workspace = new DynamicWorkspace();
|
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.supplier = supplier;
|
||||||
this.style = style;
|
this.style = style;
|
||||||
this.align = align;
|
this.align = align;
|
||||||
@ -223,7 +257,7 @@ public abstract class SpriteTypeface extends Typeface {
|
|||||||
|
|
||||||
private class SDWorkspace extends SpriteTypeface.Workspace {
|
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 origin = new Vec3();
|
||||||
private final Vec3 width = new Vec3();
|
private final Vec3 width = new Vec3();
|
||||||
@ -263,12 +297,25 @@ public abstract class SpriteTypeface extends Typeface {
|
|||||||
workspace.width.sub(workspace.origin);
|
workspace.width.sub(workspace.origin);
|
||||||
workspace.height.sub(workspace.origin);
|
workspace.height.sub(workspace.origin);
|
||||||
|
|
||||||
workspace.faces.add(Faces.createRectangle(getProgram(), texture, color, workspace.origin, workspace.width,
|
workspace.faces.add(
|
||||||
workspace.height, false));
|
ShapeParts.createRectangle(
|
||||||
|
getProgram(),
|
||||||
|
texture,
|
||||||
|
color,
|
||||||
|
workspace.origin,
|
||||||
|
workspace.width,
|
||||||
|
workspace.height,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Renderable assemble() {
|
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
|
@Override
|
||||||
public Renderable assembleDynamic(Supplier<CharSequence> supplier, int style, float align, float maxWidth,
|
public Renderable assembleDynamic(
|
||||||
Vec4 color) {
|
Supplier<CharSequence> supplier,
|
||||||
|
int style,
|
||||||
|
float align,
|
||||||
|
float maxWidth,
|
||||||
|
Vec4 color
|
||||||
|
) {
|
||||||
return new DynamicText(supplier, style, align, maxWidth, 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);
|
void drawRectangle(Vec2 size, Vec4 color, Mat4 transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void draw(CharSequence text, Drawer drawer, Workspace workspace, int style, float align, float maxWidth,
|
protected void draw(
|
||||||
Vec4 color) {
|
CharSequence text,
|
||||||
|
Drawer drawer,
|
||||||
|
Workspace workspace,
|
||||||
|
int style,
|
||||||
|
float align,
|
||||||
|
float maxWidth,
|
||||||
|
Vec4 color
|
||||||
|
) {
|
||||||
workspace.text = text;
|
workspace.text = text;
|
||||||
workspace.toIndex = text.length();
|
workspace.toIndex = text.length();
|
||||||
workspace.align = align;
|
workspace.align = align;
|
||||||
@ -430,7 +489,12 @@ public abstract class SpriteTypeface extends Typeface {
|
|||||||
return w.align * (w.totalSize.x - w.currentWidth);
|
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) {
|
private void drawLine(Drawer drawer, Workspace workspace) {
|
||||||
int style = workspace.styles.peek();
|
int style = workspace.styles.peek();
|
||||||
@ -527,8 +591,11 @@ public abstract class SpriteTypeface extends Typeface {
|
|||||||
workspace.styles.pop();
|
workspace.styles.pop();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("Style contains unknown flags " + Integer.toBinaryString(
|
throw new IllegalArgumentException(
|
||||||
(style & ~(Style.BOLD | Style.ITALIC | Style.SHADOW | Style.STRIKETHRU | Style.UNDERLINED))));
|
"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;
|
package ru.windcorp.progressia.client.graphics.gui;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.EnumMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
import com.google.common.eventbus.EventBus;
|
import com.google.common.eventbus.EventBus;
|
||||||
|
import com.google.common.eventbus.Subscribe;
|
||||||
|
|
||||||
import glm.vec._2.i.Vec2i;
|
import glm.vec._2.i.Vec2i;
|
||||||
import ru.windcorp.progressia.client.graphics.backend.InputTracker;
|
import ru.windcorp.progressia.client.graphics.backend.InputTracker;
|
||||||
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
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.ChildAddedEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.gui.event.ChildRemovedEvent;
|
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.FocusEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.gui.event.HoverEvent;
|
import ru.windcorp.progressia.client.graphics.gui.event.HoverEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.gui.event.ParentChangedEvent;
|
import ru.windcorp.progressia.client.graphics.gui.event.ParentChangedEvent;
|
||||||
@ -61,6 +66,8 @@ public class Component extends Named {
|
|||||||
|
|
||||||
private Object layoutHint = null;
|
private Object layoutHint = null;
|
||||||
private Layout layout = null;
|
private Layout layout = null;
|
||||||
|
|
||||||
|
private boolean isEnabled = true;
|
||||||
|
|
||||||
private boolean isFocusable = false;
|
private boolean isFocusable = false;
|
||||||
private boolean isFocused = false;
|
private boolean isFocused = false;
|
||||||
@ -285,9 +292,30 @@ public class Component extends Named {
|
|||||||
return this;
|
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() {
|
public boolean isFocusable() {
|
||||||
return 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) {
|
public Component setFocusable(boolean focusable) {
|
||||||
this.isFocusable = focusable;
|
this.isFocusable = focusable;
|
||||||
@ -337,7 +365,7 @@ public class Component extends Named {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (component.isFocusable()) {
|
if (component.canGainFocusNow()) {
|
||||||
setFocused(false);
|
setFocused(false);
|
||||||
component.setFocused(true);
|
component.setFocused(true);
|
||||||
return;
|
return;
|
||||||
@ -379,7 +407,7 @@ public class Component extends Named {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (component.isFocusable()) {
|
if (component.canGainFocusNow()) {
|
||||||
setFocused(false);
|
setFocused(false);
|
||||||
component.setFocused(true);
|
component.setFocused(true);
|
||||||
return;
|
return;
|
||||||
@ -432,13 +460,52 @@ public class Component extends Named {
|
|||||||
|
|
||||||
return null;
|
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() {
|
public boolean isHovered() {
|
||||||
return isHovered;
|
return isHovered;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setHovered(boolean isHovered) {
|
protected void setHovered(boolean isHovered) {
|
||||||
if (this.isHovered != isHovered) {
|
if (this.isHovered != isHovered && isEnabled()) {
|
||||||
this.isHovered = isHovered;
|
this.isHovered = isHovered;
|
||||||
|
|
||||||
if (!isHovered && !getChildren().isEmpty()) {
|
if (!isHovered && !getChildren().isEmpty()) {
|
||||||
@ -499,7 +566,7 @@ public class Component extends Named {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void handleInput(Input input) {
|
protected void handleInput(Input input) {
|
||||||
if (inputBus != null) {
|
if (inputBus != null && isEnabled()) {
|
||||||
inputBus.dispatch(input);
|
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() {
|
protected void handleReassemblyRequest() {
|
||||||
// To be overridden
|
// To be overridden
|
||||||
}
|
}
|
||||||
@ -634,6 +712,135 @@ public class Component extends Named {
|
|||||||
protected void assembleChildren(RenderTarget target) {
|
protected void assembleChildren(RenderTarget target) {
|
||||||
getChildren().forEach(child -> child.assemble(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.
|
// * 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
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* 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 {
|
public Group(String name, Layout layout) {
|
||||||
|
super(name);
|
||||||
TileData get();
|
setLayout(layout);
|
||||||
|
|
||||||
int getIndex();
|
|
||||||
|
|
||||||
TileDataStack getStack();
|
|
||||||
|
|
||||||
default boolean isValid() {
|
|
||||||
return get() != null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -15,7 +15,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.client.graphics.gui;
|
package ru.windcorp.progressia.client.graphics.gui;
|
||||||
|
|
||||||
import glm.mat._4.Mat4;
|
import glm.mat._4.Mat4;
|
||||||
@ -82,6 +82,11 @@ public class Label extends Component {
|
|||||||
public Font getFont() {
|
public Font getFont() {
|
||||||
return font;
|
return font;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setFont(Font font) {
|
||||||
|
this.font = font;
|
||||||
|
requestReassembly();
|
||||||
|
}
|
||||||
|
|
||||||
public String getCurrentText() {
|
public String getCurrentText() {
|
||||||
return currentText;
|
return currentText;
|
||||||
@ -95,13 +100,9 @@ public class Label extends Component {
|
|||||||
protected void assembleSelf(RenderTarget target) {
|
protected void assembleSelf(RenderTarget target) {
|
||||||
float startX = getX() + font.getAlign() * (getWidth() - currentSize.x);
|
float startX = getX() + font.getAlign() * (getWidth() - currentSize.x);
|
||||||
|
|
||||||
target.pushTransform(new Mat4().identity().translate(startX, getY(), -1000) // TODO
|
target.pushTransform(
|
||||||
// wtf
|
new Mat4().identity().translate(startX, getY(), 0).scale(2)
|
||||||
// is
|
);
|
||||||
// this
|
|
||||||
// magic
|
|
||||||
// <---
|
|
||||||
.scale(2));
|
|
||||||
|
|
||||||
target.addCustomRenderer(font.assemble(currentText, maxWidth));
|
target.addCustomRenderer(font.assemble(currentText, maxWidth));
|
||||||
|
|
||||||
|
@ -15,14 +15,66 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.client.graphics.gui;
|
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) {
|
public Panel(String name, Layout layout) {
|
||||||
super(name);
|
this(name, layout, Colors.WHITE, Colors.LIGHT_GRAY);
|
||||||
setLayout(layout);
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.client.graphics.gui.layout;
|
package ru.windcorp.progressia.client.graphics.gui.layout;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@ -97,14 +97,28 @@ public class LayoutGrid implements Layout {
|
|||||||
void setBounds(int column, int row, Component child, Component parent) {
|
void setBounds(int column, int row, Component child, Component parent) {
|
||||||
if (!isSummed)
|
if (!isSummed)
|
||||||
throw new IllegalStateException("Not summed yet");
|
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)
|
width,
|
||||||
: (parent.getWidth() - margin - columns[column])),
|
height
|
||||||
|
);
|
||||||
(row != (rows.length - 1) ? (rows[row + 1] - rows[row] - gap)
|
|
||||||
: (parent.getHeight() - margin - rows[row])));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,10 +143,9 @@ public class LayoutGrid implements Layout {
|
|||||||
GridDimensions grid = calculateGrid(c);
|
GridDimensions grid = calculateGrid(c);
|
||||||
grid.sum();
|
grid.sum();
|
||||||
|
|
||||||
int[] coords;
|
|
||||||
for (Component child : c.getChildren()) {
|
for (Component child : c.getChildren()) {
|
||||||
coords = (int[]) child.getLayoutHint();
|
Vec2i coords = (Vec2i) child.getLayoutHint();
|
||||||
grid.setBounds(coords[0], coords[1], child, c);
|
grid.setBounds(coords.x, coords.y, child, c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -146,11 +159,10 @@ public class LayoutGrid implements Layout {
|
|||||||
|
|
||||||
private GridDimensions calculateGrid(Component parent) {
|
private GridDimensions calculateGrid(Component parent) {
|
||||||
GridDimensions result = new GridDimensions();
|
GridDimensions result = new GridDimensions();
|
||||||
int[] coords;
|
|
||||||
|
|
||||||
for (Component child : parent.getChildren()) {
|
for (Component child : parent.getChildren()) {
|
||||||
coords = (int[]) child.getLayoutHint();
|
Vec2i coords = (Vec2i) child.getLayoutHint();
|
||||||
result.add(coords[0], coords[1], child.getPreferredSize());
|
result.add(coords.x, coords.y, child.getPreferredSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
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
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.client.graphics.model;
|
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 com.google.common.collect.ImmutableMap;
|
||||||
|
|
||||||
import glm.vec._3.Vec3;
|
import glm.vec._3.Vec3;
|
||||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
||||||
|
|
||||||
class BlockFaceVectors {
|
class BlockFaceVectors {
|
||||||
|
|
||||||
private static BlockFaceVectors createInner(BlockFaceVectors outer) {
|
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 width = outer.getWidth(face);
|
||||||
Vec3 height = outer.getHeight(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));
|
widthBuilder.put(face, new Vec3(width));
|
||||||
heightBuilder.put(face, new Vec3(height));
|
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 OUTER;
|
||||||
private static final BlockFaceVectors INNER;
|
private static final BlockFaceVectors INNER;
|
||||||
|
|
||||||
static {
|
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(POS_Z, 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(NEG_Z, 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_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(),
|
.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(POS_Z, 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(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(POS_Z, new Vec3(+1, 0, 0))
|
||||||
.put(SOUTH, new Vec3(0, 0, +1)).put(WEST, new Vec3(0, 0, +1)).put(EAST, new Vec3(0, 0, +1))
|
.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);
|
INNER = createInner(OUTER);
|
||||||
}
|
}
|
||||||
@ -80,26 +100,29 @@ class BlockFaceVectors {
|
|||||||
return inner ? INNER : OUTER;
|
return inner ? INNER : OUTER;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final ImmutableMap<BlockFace, Vec3> origins;
|
private final ImmutableMap<AbsFace, Vec3> origins;
|
||||||
private final ImmutableMap<BlockFace, Vec3> widths;
|
private final ImmutableMap<AbsFace, Vec3> widths;
|
||||||
private final ImmutableMap<BlockFace, Vec3> heights;
|
private final ImmutableMap<AbsFace, Vec3> heights;
|
||||||
|
|
||||||
public BlockFaceVectors(ImmutableMap<BlockFace, Vec3> origins, ImmutableMap<BlockFace, Vec3> widths,
|
public BlockFaceVectors(
|
||||||
ImmutableMap<BlockFace, Vec3> heights) {
|
ImmutableMap<AbsFace, Vec3> origins,
|
||||||
|
ImmutableMap<AbsFace, Vec3> widths,
|
||||||
|
ImmutableMap<AbsFace, Vec3> heights
|
||||||
|
) {
|
||||||
this.origins = origins;
|
this.origins = origins;
|
||||||
this.widths = widths;
|
this.widths = widths;
|
||||||
this.heights = heights;
|
this.heights = heights;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vec3 getOrigin(BlockFace face) {
|
public Vec3 getOrigin(AbsFace face) {
|
||||||
return origins.get(face);
|
return origins.get(face);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vec3 getWidth(BlockFace face) {
|
public Vec3 getWidth(AbsFace face) {
|
||||||
return widths.get(face);
|
return widths.get(face);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vec3 getHeight(BlockFace face) {
|
public Vec3 getHeight(AbsFace face) {
|
||||||
return heights.get(face);
|
return heights.get(face);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,10 +30,10 @@ import ru.windcorp.progressia.client.graphics.backend.VertexBufferObject;
|
|||||||
public class Shape implements Renderable {
|
public class Shape implements Renderable {
|
||||||
|
|
||||||
private final ShapeRenderProgram program;
|
private final ShapeRenderProgram program;
|
||||||
private final Face[] faces;
|
private final ShapePart[] parts;
|
||||||
private final Usage usage;
|
private final Usage usage;
|
||||||
|
|
||||||
private FaceGroup[] groups;
|
private ShapePartGroup[] groups;
|
||||||
|
|
||||||
private ByteBuffer vertices;
|
private ByteBuffer vertices;
|
||||||
private ShortBuffer indices;
|
private ShortBuffer indices;
|
||||||
@ -45,33 +45,33 @@ public class Shape implements Renderable {
|
|||||||
private VertexBufferObject verticesVbo;
|
private VertexBufferObject verticesVbo;
|
||||||
private VertexBufferObject indicesVbo;
|
private VertexBufferObject indicesVbo;
|
||||||
|
|
||||||
public Shape(Usage usage, ShapeRenderProgram program, Face... faces) {
|
public Shape(Usage usage, ShapeRenderProgram program, ShapePart... parts) {
|
||||||
this.program = program;
|
this.program = program;
|
||||||
this.faces = faces;
|
this.parts = parts;
|
||||||
this.usage = usage;
|
this.usage = usage;
|
||||||
|
|
||||||
configureFaces();
|
configureParts();
|
||||||
program.preprocess(this);
|
program.preprocess(this);
|
||||||
|
|
||||||
assembleBuffers();
|
assembleBuffers();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void configureFaces() {
|
private void configureParts() {
|
||||||
for (Face face : faces) {
|
for (ShapePart part : parts) {
|
||||||
face.setShape(this);
|
part.setShape(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assembleBuffers() {
|
private void assembleBuffers() {
|
||||||
// TODO optimize: only update faces that requested it
|
// TODO optimize: only update faces that requested it
|
||||||
|
|
||||||
sortFaces();
|
sortParts();
|
||||||
resizeBuffers();
|
resizeBuffers();
|
||||||
|
|
||||||
for (Face face : faces) {
|
for (ShapePart part : parts) {
|
||||||
assembleVertices(face);
|
assembleVertices(part);
|
||||||
assembleIndices(face);
|
assembleIndices(part);
|
||||||
face.resetUpdateFlags();
|
part.resetUpdateFlags();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.vertices.flip();
|
this.vertices.flip();
|
||||||
@ -85,110 +85,110 @@ public class Shape implements Renderable {
|
|||||||
|
|
||||||
private void resizeBuffers() {
|
private void resizeBuffers() {
|
||||||
int verticesRequired = 0, indicesRequired = 0;
|
int verticesRequired = 0, indicesRequired = 0;
|
||||||
for (Face face : faces) {
|
for (ShapePart part : parts) {
|
||||||
verticesRequired += face.getVertices().remaining();
|
verticesRequired += part.getVertices().remaining();
|
||||||
indicesRequired += face.getIndices().remaining();
|
indicesRequired += part.getIndices().remaining();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.vertices == null || vertices.capacity() < verticesRequired) {
|
if (vertices == null || vertices.capacity() < verticesRequired) {
|
||||||
this.vertices = BufferUtils.createByteBuffer(verticesRequired);
|
this.vertices = BufferUtils.createByteBuffer(verticesRequired);
|
||||||
} else {
|
} 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);
|
this.indices = BufferUtils.createShortBuffer(indicesRequired);
|
||||||
} else {
|
} else {
|
||||||
this.indices.position(0).limit(indicesRequired);
|
indices.position(0).limit(indicesRequired);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assembleVertices(Face face) {
|
private void assembleVertices(ShapePart part) {
|
||||||
face.locationOfVertices = this.vertices.position();
|
part.locationOfVertices = this.vertices.position();
|
||||||
|
|
||||||
insertVertices(face);
|
insertVertices(part);
|
||||||
linkVerticesWith(face);
|
linkVerticesWith(part);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void insertVertices(Face face) {
|
private void insertVertices(ShapePart part) {
|
||||||
ByteBuffer faceVertices = face.getVertices();
|
ByteBuffer partVertices = part.getVertices();
|
||||||
|
|
||||||
faceVertices.mark();
|
partVertices.mark();
|
||||||
this.vertices.put(faceVertices);
|
this.vertices.put(partVertices);
|
||||||
faceVertices.reset();
|
partVertices.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void linkVerticesWith(Face face) {
|
private void linkVerticesWith(ShapePart part) {
|
||||||
int limit = vertices.limit();
|
int limit = vertices.limit();
|
||||||
int position = vertices.position();
|
int position = vertices.position();
|
||||||
|
|
||||||
vertices.limit(position).position(face.getLocationOfVertices());
|
vertices.limit(position).position(part.getLocationOfVertices());
|
||||||
face.vertices = vertices.slice();
|
part.vertices = vertices.slice();
|
||||||
|
|
||||||
vertices.position(position).limit(limit);
|
vertices.position(position).limit(limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assembleIndices(Face face) {
|
private void assembleIndices(ShapePart part) {
|
||||||
short vertexOffset = (short) (face.getLocationOfVertices() / program.getBytesPerVertex());
|
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) {
|
if (partIndices == null) {
|
||||||
for (int i = 0; i < face.getVertexCount(); ++i) {
|
for (int i = 0; i < part.getVertexCount(); ++i) {
|
||||||
this.indices.put((short) (vertexOffset + i));
|
this.indices.put((short) (vertexOffset + i));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (int i = faceIndices.position(); i < faceIndices.limit(); ++i) {
|
for (int i = partIndices.position(); i < partIndices.limit(); ++i) {
|
||||||
short faceIndex = faceIndices.get(i);
|
short partIndex = partIndices.get(i);
|
||||||
faceIndex += vertexOffset;
|
partIndex += vertexOffset;
|
||||||
this.indices.put(faceIndex);
|
this.indices.put(partIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sortFaces() {
|
private void sortParts() {
|
||||||
Arrays.sort(faces);
|
Arrays.sort(parts);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assembleGroups() {
|
private void assembleGroups() {
|
||||||
int unique = countUniqueFaces();
|
int unique = countUniqueParts();
|
||||||
this.groups = new FaceGroup[unique];
|
this.groups = new ShapePartGroup[unique];
|
||||||
|
|
||||||
if (faces.length == 0)
|
if (parts.length == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int previousHandle = faces[0].getSortingIndex();
|
int previousHandle = parts[0].getSortingIndex();
|
||||||
int start = 0;
|
int start = 0;
|
||||||
int groupIndex = 0;
|
int groupIndex = 0;
|
||||||
|
|
||||||
for (int i = 1; i < faces.length; ++i) {
|
for (int i = 1; i < parts.length; ++i) {
|
||||||
if (previousHandle != faces[i].getSortingIndex()) {
|
if (previousHandle != parts[i].getSortingIndex()) {
|
||||||
|
|
||||||
groups[groupIndex] = new FaceGroup(faces, start, i);
|
groups[groupIndex] = new ShapePartGroup(parts, start, i);
|
||||||
start = i;
|
start = i;
|
||||||
groupIndex++;
|
groupIndex++;
|
||||||
|
|
||||||
previousHandle = faces[i].getSortingIndex();
|
previousHandle = parts[i].getSortingIndex();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert groupIndex == groups.length - 1;
|
assert groupIndex == groups.length - 1;
|
||||||
groups[groupIndex] = new FaceGroup(faces, start, faces.length);
|
groups[groupIndex] = new ShapePartGroup(parts, start, parts.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int countUniqueFaces() {
|
private int countUniqueParts() {
|
||||||
if (faces.length == 0)
|
if (parts.length == 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
int result = 1;
|
int result = 1;
|
||||||
int previousHandle = faces[0].getSortingIndex();
|
int previousHandle = parts[0].getSortingIndex();
|
||||||
|
|
||||||
for (int i = 1; i < faces.length; ++i) {
|
for (int i = 1; i < parts.length; ++i) {
|
||||||
if (previousHandle != faces[i].getSortingIndex()) {
|
if (previousHandle != parts[i].getSortingIndex()) {
|
||||||
result++;
|
result++;
|
||||||
previousHandle = faces[i].getSortingIndex();
|
previousHandle = parts[i].getSortingIndex();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,11 +238,11 @@ public class Shape implements Renderable {
|
|||||||
return program;
|
return program;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Face[] getFaces() {
|
public ShapePart[] getParts() {
|
||||||
return faces;
|
return parts;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FaceGroup[] getGroups() {
|
public ShapePartGroup[] getGroups() {
|
||||||
return groups;
|
return groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.client.graphics.model;
|
package ru.windcorp.progressia.client.graphics.model;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
@ -24,7 +24,7 @@ import java.util.Objects;
|
|||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
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;
|
private static final ShortBuffer GENERATE_SUCCESSIVE_LATER = null;
|
||||||
|
|
||||||
@ -40,13 +40,20 @@ public class Face implements Comparable<Face> {
|
|||||||
private ShortBuffer userIndices;
|
private ShortBuffer userIndices;
|
||||||
private boolean userIndicesUpdated = true;
|
private boolean userIndicesUpdated = true;
|
||||||
|
|
||||||
public Face(Texture texture, ByteBuffer vertices, ShortBuffer indices) {
|
public ShapePart(
|
||||||
|
Texture texture,
|
||||||
|
ByteBuffer vertices,
|
||||||
|
ShortBuffer indices
|
||||||
|
) {
|
||||||
setTexture(texture);
|
setTexture(texture);
|
||||||
setVertices(vertices);
|
setVertices(vertices);
|
||||||
setIndices(indices);
|
setIndices(indices);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Face(Texture texture, ByteBuffer vertices) {
|
public ShapePart(
|
||||||
|
Texture texture,
|
||||||
|
ByteBuffer vertices
|
||||||
|
) {
|
||||||
this(texture, vertices, null);
|
this(texture, vertices, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,16 +66,22 @@ public class Face implements Comparable<Face> {
|
|||||||
|
|
||||||
private void checkVertices() {
|
private void checkVertices() {
|
||||||
if (vertices.remaining() % getBytesPerVertex() != 0) {
|
if (vertices.remaining() % getBytesPerVertex() != 0) {
|
||||||
throw new IllegalArgumentException("Invalid vertex buffer: " + (vertices.remaining() % getBytesPerVertex())
|
throw new IllegalArgumentException(
|
||||||
+ " extra bytes after last vertex");
|
"Invalid vertex buffer: " +
|
||||||
|
(vertices.remaining() % getBytesPerVertex()) +
|
||||||
|
" extra bytes after last vertex"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkIndices() {
|
private void checkIndices() {
|
||||||
if (userIndices != GENERATE_SUCCESSIVE_LATER) {
|
if (userIndices != GENERATE_SUCCESSIVE_LATER) {
|
||||||
if (userIndices.remaining() % 3 != 0) {
|
if (userIndices.remaining() % 3 != 0) {
|
||||||
throw new IllegalArgumentException("Invalid vertex indices: " + (userIndices.remaining() % 3)
|
throw new IllegalArgumentException(
|
||||||
+ " extra indices after last triangle");
|
"Invalid vertex indices: " +
|
||||||
|
(userIndices.remaining() % 3) +
|
||||||
|
" extra indices after last triangle"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
userIndices.mark();
|
userIndices.mark();
|
||||||
@ -78,15 +91,21 @@ public class Face implements Comparable<Face> {
|
|||||||
short index = userIndices.get();
|
short index = userIndices.get();
|
||||||
if (index < 0 || index >= vertexCount) {
|
if (index < 0 || index >= vertexCount) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Invalid vertex index " + index + " (" + vertexCount + " vertices available)");
|
"Invalid vertex index " + index +
|
||||||
|
" (" + vertexCount + " vertices available)"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
userIndices.reset();
|
userIndices.reset();
|
||||||
} else {
|
} else {
|
||||||
if (getVertexCount() % 3 != 0) {
|
if (getVertexCount() % 3 != 0) {
|
||||||
throw new IllegalArgumentException("Invalid vertices: " + (getVertexCount() % 3)
|
throw new IllegalArgumentException(
|
||||||
+ " extra indices after last triangle " + "(indices are automatic)");
|
"Invalid vertices: " +
|
||||||
|
(getVertexCount() % 3) +
|
||||||
|
" extra indices after last triangle " +
|
||||||
|
"(indices are automatic)"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -136,7 +155,7 @@ public class Face implements Comparable<Face> {
|
|||||||
return vertices;
|
return vertices;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Face setVertices(ByteBuffer vertices) {
|
public ShapePart setVertices(ByteBuffer vertices) {
|
||||||
this.vertices = Objects.requireNonNull(vertices, "vertices");
|
this.vertices = Objects.requireNonNull(vertices, "vertices");
|
||||||
markForVertexUpdate();
|
markForVertexUpdate();
|
||||||
return this;
|
return this;
|
||||||
@ -183,7 +202,7 @@ public class Face implements Comparable<Face> {
|
|||||||
return userIndices.remaining();
|
return userIndices.remaining();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Face setIndices(ShortBuffer indices) {
|
public ShapePart setIndices(ShortBuffer indices) {
|
||||||
if (indices == null) {
|
if (indices == null) {
|
||||||
indices = GENERATE_SUCCESSIVE_LATER;
|
indices = GENERATE_SUCCESSIVE_LATER;
|
||||||
}
|
}
|
||||||
@ -226,7 +245,7 @@ public class Face implements Comparable<Face> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(Face o) {
|
public int compareTo(ShapePart o) {
|
||||||
return Integer.compare(getSortingIndex(), o.getSortingIndex());
|
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.Texture;
|
||||||
import ru.windcorp.progressia.client.graphics.texture.TexturePrimitive;
|
import ru.windcorp.progressia.client.graphics.texture.TexturePrimitive;
|
||||||
|
|
||||||
public class FaceGroup {
|
public class ShapePartGroup {
|
||||||
|
|
||||||
private final TexturePrimitive texture;
|
private final TexturePrimitive texture;
|
||||||
private final int indexCount;
|
private final int indexCount;
|
||||||
private final int byteOffsetOfIndices;
|
private final int byteOffsetOfIndices;
|
||||||
|
|
||||||
FaceGroup(Face[] faces, int start, int end) {
|
ShapePartGroup(ShapePart[] faces, int start, int end) {
|
||||||
|
|
||||||
Texture t = faces[start].getTexture();
|
Texture t = faces[start].getTexture();
|
||||||
this.texture = t == null ? null : t.getSprite().getPrimitive();
|
this.texture = t == null ? null : t.getSprite().getPrimitive();
|
||||||
@ -36,7 +36,7 @@ public class FaceGroup {
|
|||||||
int indexCount = 0;
|
int indexCount = 0;
|
||||||
|
|
||||||
for (int i = start; i < end; ++i) {
|
for (int i = start; i < end; ++i) {
|
||||||
Face face = faces[i];
|
ShapePart face = faces[i];
|
||||||
|
|
||||||
assert this.texture == null ? (face.getTexture() == null)
|
assert this.texture == null ? (face.getTexture() == null)
|
||||||
: (face.getTexture().getSprite().getPrimitive() == this.texture);
|
: (face.getTexture().getSprite().getPrimitive() == this.texture);
|
@ -15,7 +15,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.client.graphics.model;
|
package ru.windcorp.progressia.client.graphics.model;
|
||||||
|
|
||||||
import java.nio.ShortBuffer;
|
import java.nio.ShortBuffer;
|
||||||
@ -25,37 +25,93 @@ import glm.vec._3.Vec3;
|
|||||||
import glm.vec._4.Vec4;
|
import glm.vec._4.Vec4;
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram.VertexBuilder;
|
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram.VertexBuilder;
|
||||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
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,
|
public static ShapePart createRectangle(
|
||||||
Vec3 width, Vec3 height, boolean flip) {
|
ShapeRenderProgram program,
|
||||||
|
Texture texture,
|
||||||
|
Vec4 colorMultiplier,
|
||||||
|
Vec3 origin,
|
||||||
|
Vec3 width,
|
||||||
|
Vec3 height,
|
||||||
|
boolean flip
|
||||||
|
) {
|
||||||
VertexBuilder builder = program.getVertexBuilder();
|
VertexBuilder builder = program.getVertexBuilder();
|
||||||
|
|
||||||
builder.addVertex(origin, colorMultiplier, new Vec2(0, 0))
|
builder.addVertex(
|
||||||
.addVertex(origin.add_(height), colorMultiplier, new Vec2(0, 1))
|
origin,
|
||||||
.addVertex(origin.add_(width), colorMultiplier, new Vec2(1, 0))
|
colorMultiplier,
|
||||||
.addVertex(origin.add_(width).add(height), colorMultiplier, new Vec2(1, 1));
|
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 buffer = flip ? ShortBuffer.wrap(
|
||||||
: ShortBuffer.wrap(new short[] { 3, 1, 0, 2, 3, 0 });
|
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,
|
public static ShapePart createBlockFace(
|
||||||
Vec3 blockCenter, BlockFace face, boolean inner) {
|
ShapeRenderProgram program,
|
||||||
|
Texture texture,
|
||||||
|
Vec4 colorMultiplier,
|
||||||
|
Vec3 blockCenter,
|
||||||
|
AbsFace face,
|
||||||
|
boolean inner
|
||||||
|
) {
|
||||||
BlockFaceVectors vectors = BlockFaceVectors.get(inner);
|
BlockFaceVectors vectors = BlockFaceVectors.get(inner);
|
||||||
|
|
||||||
Vec3 origin = blockCenter.add_(vectors.getOrigin(face));
|
Vec3 origin = blockCenter.add_(vectors.getOrigin(face));
|
||||||
Vec3 width = vectors.getWidth(face);
|
Vec3 width = vectors.getWidth(face);
|
||||||
Vec3 height = vectors.getHeight(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 {
|
try {
|
||||||
enableAttributes();
|
enableAttributes();
|
||||||
for (FaceGroup group : shape.getGroups()) {
|
for (ShapePartGroup group : shape.getGroups()) {
|
||||||
renderFaceGroup(group);
|
renderFaceGroup(group);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@ -145,7 +145,7 @@ public class ShapeRenderProgram extends Program {
|
|||||||
indices.bind(BindTarget.ELEMENT_ARRAY);
|
indices.bind(BindTarget.ELEMENT_ARRAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void renderFaceGroup(FaceGroup group) {
|
protected void renderFaceGroup(ShapePartGroup group) {
|
||||||
TexturePrimitive texture = group.getTexture();
|
TexturePrimitive texture = group.getTexture();
|
||||||
|
|
||||||
if (texture != null) {
|
if (texture != null) {
|
||||||
@ -165,12 +165,12 @@ public class ShapeRenderProgram extends Program {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void preprocess(Shape shape) {
|
public void preprocess(Shape shape) {
|
||||||
for (Face face : shape.getFaces()) {
|
for (ShapePart face : shape.getParts()) {
|
||||||
applySprites(face);
|
applySprites(face);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applySprites(Face face) {
|
private void applySprites(ShapePart face) {
|
||||||
if (face.getTexture() == null)
|
if (face.getTexture() == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.client.graphics.model;
|
package ru.windcorp.progressia.client.graphics.model;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -24,42 +24,102 @@ import glm.vec._3.Vec3;
|
|||||||
import glm.vec._4.Vec4;
|
import glm.vec._4.Vec4;
|
||||||
import ru.windcorp.progressia.client.graphics.backend.Usage;
|
import ru.windcorp.progressia.client.graphics.backend.Usage;
|
||||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
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 class Shapes {
|
||||||
|
|
||||||
public static Shape createParallelepiped(
|
public static Shape createParallelepiped(
|
||||||
// Try saying that 10 times fast
|
// Try saying that 10 times fast
|
||||||
ShapeRenderProgram program,
|
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 topTexture,
|
||||||
Texture westTexture,
|
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),
|
ShapePart top = ShapeParts.createRectangle(
|
||||||
width.negate_(), depth, flip);
|
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,
|
ShapePart north = ShapeParts.createRectangle(
|
||||||
flip);
|
program,
|
||||||
|
northTexture,
|
||||||
|
colorMultiplier,
|
||||||
|
origin.add_(depth),
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
flip
|
||||||
|
);
|
||||||
|
|
||||||
Face south = Faces.createRectangle(program, southTexture, colorMultiplier, origin.add_(width), width.negate_(),
|
ShapePart south = ShapeParts.createRectangle(
|
||||||
height, flip);
|
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),
|
ShapePart west = ShapeParts.createRectangle(
|
||||||
depth.negate_(), height, flip);
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
@ -85,8 +145,15 @@ public class Shapes {
|
|||||||
|
|
||||||
private boolean flip = false;
|
private boolean flip = false;
|
||||||
|
|
||||||
public PppBuilder(ShapeRenderProgram program, Texture top, Texture bottom, Texture north, Texture south,
|
public PppBuilder(
|
||||||
Texture east, Texture west) {
|
ShapeRenderProgram program,
|
||||||
|
Texture top,
|
||||||
|
Texture bottom,
|
||||||
|
Texture north,
|
||||||
|
Texture south,
|
||||||
|
Texture east,
|
||||||
|
Texture west
|
||||||
|
) {
|
||||||
this.program = program;
|
this.program = program;
|
||||||
this.topTexture = top;
|
this.topTexture = top;
|
||||||
this.bottomTexture = bottom;
|
this.bottomTexture = bottom;
|
||||||
@ -96,10 +163,19 @@ public class Shapes {
|
|||||||
this.westTexture = west;
|
this.westTexture = west;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PppBuilder(ShapeRenderProgram program, Map<BlockFace, Texture> textureMap) {
|
public PppBuilder(
|
||||||
this(program, textureMap.get(BlockFace.TOP), textureMap.get(BlockFace.BOTTOM),
|
ShapeRenderProgram program,
|
||||||
textureMap.get(BlockFace.NORTH), textureMap.get(BlockFace.SOUTH), textureMap.get(BlockFace.EAST),
|
Map<AbsFace, Texture> textureMap
|
||||||
textureMap.get(BlockFace.WEST));
|
) {
|
||||||
|
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) {
|
public PppBuilder(ShapeRenderProgram program, Texture texture) {
|
||||||
@ -190,8 +266,21 @@ public class Shapes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Shape create() {
|
public Shape create() {
|
||||||
return createParallelepiped(program, origin, width, height, depth, colorMultiplier, topTexture,
|
return createParallelepiped(
|
||||||
bottomTexture, northTexture, southTexture, eastTexture, westTexture, flip);
|
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
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.client.graphics.texture;
|
package ru.windcorp.progressia.client.graphics.texture;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import glm.vec._2.Vec2;
|
import glm.vec._2.Vec2;
|
||||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
||||||
|
|
||||||
public class ComplexTexture {
|
public class ComplexTexture {
|
||||||
|
|
||||||
@ -30,27 +30,67 @@ public class ComplexTexture {
|
|||||||
private final float assumedWidth;
|
private final float assumedWidth;
|
||||||
private final float assumedHeight;
|
private final float assumedHeight;
|
||||||
|
|
||||||
public ComplexTexture(TexturePrimitive primitive, int abstractWidth, int abstractHeight) {
|
public ComplexTexture(
|
||||||
|
TexturePrimitive primitive,
|
||||||
|
int abstractWidth,
|
||||||
|
int abstractHeight
|
||||||
|
) {
|
||||||
this.primitive = primitive;
|
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) {
|
public Texture get(int x, int y, int width, int height) {
|
||||||
return new SimpleTexture(new Sprite(primitive, new Vec2(x / assumedWidth, y / assumedHeight),
|
return new SimpleTexture(
|
||||||
new Vec2(width / assumedWidth, height / assumedHeight)));
|
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) {
|
public Map<AbsFace, Texture> getCuboidTextures(
|
||||||
return BlockFace.mapToFaces(get(x + depth + width, y + height + depth, -width, -depth),
|
int x,
|
||||||
get(x + depth + width + width, y + height + depth, -width, -depth), get(x + depth, y, width, height),
|
int y,
|
||||||
get(x + depth + width + depth, y, width, height), get(x, y, depth, height),
|
int width,
|
||||||
get(x + depth + width, y, depth, height));
|
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);
|
return getCuboidTextures(x, y, size, size, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.client.graphics.world;
|
package ru.windcorp.progressia.client.graphics.world;
|
||||||
|
|
||||||
import static java.lang.Math.*;
|
import static java.lang.Math.*;
|
||||||
@ -29,6 +29,9 @@ import glm.mat._4.Mat4;
|
|||||||
import glm.vec._3.Vec3;
|
import glm.vec._3.Vec3;
|
||||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||||
import ru.windcorp.progressia.client.graphics.world.Camera.Anchor.Mode;
|
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 {
|
public class Camera {
|
||||||
|
|
||||||
@ -42,7 +45,10 @@ public class Camera {
|
|||||||
|
|
||||||
void applyCameraRotation(Mat4 output);
|
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() {
|
return new Mode() {
|
||||||
@Override
|
@Override
|
||||||
public void getCameraOffset(Vec3 output) {
|
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();
|
Vec3 getLookingAt(Vec3 output);
|
||||||
|
|
||||||
float getCameraPitch();
|
Vec3 getUpVector(Vec3 output);
|
||||||
|
|
||||||
Collection<Mode> getCameraModes();
|
Collection<Mode> getCameraModes();
|
||||||
|
|
||||||
@ -81,14 +87,11 @@ public class Camera {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
private final Vec3 lastAnchorPosition = new Vec3();
|
private final Vec3 lastAnchorPosition = new Vec3();
|
||||||
private float lastAnchorYaw;
|
private final Vec3 lastAnchorLookingAt = new Vec3();
|
||||||
private float lastAnchorPitch;
|
private final Vec3 lastAnchorUpVector = new Vec3();
|
||||||
|
|
||||||
private final Mat4 lastCameraMatrix = new Mat4();
|
private final Mat4 lastCameraMatrix = new Mat4();
|
||||||
|
|
||||||
private final Vec3 lastAnchorLookingAt = new Vec3();
|
|
||||||
private final Vec3 lastAnchorUp = new Vec3();
|
|
||||||
|
|
||||||
{
|
{
|
||||||
invalidateCache();
|
invalidateCache();
|
||||||
}
|
}
|
||||||
@ -105,6 +108,9 @@ public class Camera {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
public void apply(WorldRenderHelper helper) {
|
public void apply(WorldRenderHelper helper) {
|
||||||
|
if (NPedModel.flag) {
|
||||||
|
// System.out.println("Camera.apply()");
|
||||||
|
}
|
||||||
applyPerspective(helper);
|
applyPerspective(helper);
|
||||||
rotateCoordinateSystem(helper);
|
rotateCoordinateSystem(helper);
|
||||||
|
|
||||||
@ -118,8 +124,13 @@ public class Camera {
|
|||||||
private void applyPerspective(WorldRenderHelper helper) {
|
private void applyPerspective(WorldRenderHelper helper) {
|
||||||
Mat4 previous = helper.getViewTransform();
|
Mat4 previous = helper.getViewTransform();
|
||||||
|
|
||||||
Glm.perspective(computeFovY(), GraphicsInterface.getAspectRatio(), 0.01f, 150.0f, helper.pushViewTransform())
|
Glm.perspective(
|
||||||
.mul(previous);
|
computeFovY(),
|
||||||
|
GraphicsInterface.getAspectRatio(),
|
||||||
|
0.01f,
|
||||||
|
150.0f,
|
||||||
|
helper.pushViewTransform()
|
||||||
|
).mul(previous);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void rotateCoordinateSystem(WorldRenderHelper helper) {
|
private void rotateCoordinateSystem(WorldRenderHelper helper) {
|
||||||
@ -141,17 +152,34 @@ public class Camera {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void applyDirection(WorldRenderHelper helper) {
|
private void applyDirection(WorldRenderHelper helper) {
|
||||||
float pitch = anchor.getCameraPitch();
|
anchor.getLookingAt(lastAnchorLookingAt);
|
||||||
float yaw = anchor.getCameraYaw();
|
anchor.getUpVector(lastAnchorUpVector);
|
||||||
|
|
||||||
helper.pushViewTransform().rotateY(-pitch).rotateZ(-yaw);
|
lookAt(helper.pushViewTransform());
|
||||||
|
}
|
||||||
|
|
||||||
this.lastAnchorYaw = yaw;
|
private void lookAt(Mat4 result) {
|
||||||
this.lastAnchorPitch = pitch;
|
Vec3 f = this.lastAnchorLookingAt;
|
||||||
|
Vec3 s = Vectors.grab3();
|
||||||
this.lastAnchorLookingAt.set(cos(pitch) * cos(yaw), cos(pitch) * sin(yaw), sin(pitch));
|
Vec3 u = Vectors.grab3();
|
||||||
this.lastAnchorUp.set(cos(pitch + PI_F / 2) * cos(yaw), cos(pitch + PI_F / 2) * sin(yaw),
|
|
||||||
sin(pitch + PI_F / 2));
|
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) {
|
private void applyPosition(WorldRenderHelper helper) {
|
||||||
@ -177,7 +205,11 @@ public class Camera {
|
|||||||
if (widthOverHeight >= 1) {
|
if (widthOverHeight >= 1) {
|
||||||
return fieldOfView;
|
return fieldOfView;
|
||||||
} else {
|
} 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()) {
|
if (modesCollection.isEmpty()) {
|
||||||
throw new IllegalArgumentException(
|
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;
|
this.anchor = anchor;
|
||||||
@ -224,14 +258,28 @@ public class Camera {
|
|||||||
|
|
||||||
private void invalidateCache() {
|
private void invalidateCache() {
|
||||||
this.lastAnchorPosition.set(Float.NaN);
|
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,
|
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,
|
||||||
|
Float.NaN,
|
||||||
|
Float.NaN,
|
||||||
|
Float.NaN,
|
||||||
|
Float.NaN,
|
||||||
|
Float.NaN,
|
||||||
|
Float.NaN,
|
||||||
|
Float.NaN,
|
||||||
|
Float.NaN,
|
||||||
|
Float.NaN
|
||||||
|
);
|
||||||
|
|
||||||
this.lastAnchorLookingAt.set(Float.NaN);
|
this.lastAnchorLookingAt.set(Float.NaN);
|
||||||
this.lastAnchorUp.set(Float.NaN);
|
this.lastAnchorUpVector.set(Float.NaN);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Anchor.Mode getMode() {
|
public Anchor.Mode getMode() {
|
||||||
@ -250,14 +298,6 @@ public class Camera {
|
|||||||
return currentModeIndex;
|
return currentModeIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getLastAnchorYaw() {
|
|
||||||
return lastAnchorYaw;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float getLastAnchorPitch() {
|
|
||||||
return lastAnchorPitch;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Vec3 getLastAnchorPosition() {
|
public Vec3 getLastAnchorPosition() {
|
||||||
return lastAnchorPosition;
|
return lastAnchorPosition;
|
||||||
}
|
}
|
||||||
@ -271,7 +311,7 @@ public class Camera {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Vec3 getLastAnchorUp() {
|
public Vec3 getLastAnchorUp() {
|
||||||
return lastAnchorUp;
|
return lastAnchorUpVector;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -52,24 +52,32 @@ public class EntityAnchor implements Anchor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void getCameraPosition(Vec3 output) {
|
public Vec3 getCameraPosition(Vec3 output) {
|
||||||
|
if (output == null) output = new Vec3();
|
||||||
model.getViewPoint(output);
|
model.getViewPoint(output);
|
||||||
output.add(entity.getPosition());
|
output.add(model.getPosition());
|
||||||
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void getCameraVelocity(Vec3 output) {
|
public Vec3 getCameraVelocity(Vec3 output) {
|
||||||
|
if (output == null) output = new Vec3();
|
||||||
output.set(entity.getVelocity());
|
output.set(entity.getVelocity());
|
||||||
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getCameraYaw() {
|
public Vec3 getLookingAt(Vec3 output) {
|
||||||
return entity.getYaw();
|
if (output == null) output = new Vec3();
|
||||||
|
model.getLookingAt(output);
|
||||||
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getCameraPitch() {
|
public Vec3 getUpVector(Vec3 output) {
|
||||||
return entity.getPitch();
|
if (output == null) output = new Vec3();
|
||||||
|
model.getUpVector(output);
|
||||||
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -41,6 +41,8 @@ import ru.windcorp.progressia.common.Units;
|
|||||||
import ru.windcorp.progressia.common.collision.Collideable;
|
import ru.windcorp.progressia.common.collision.Collideable;
|
||||||
import ru.windcorp.progressia.common.collision.colliders.Collider;
|
import ru.windcorp.progressia.common.collision.colliders.Collider;
|
||||||
import ru.windcorp.progressia.common.util.FloatMathUtil;
|
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.common.world.entity.EntityData;
|
||||||
import ru.windcorp.progressia.test.CollisionModelRenderer;
|
import ru.windcorp.progressia.test.CollisionModelRenderer;
|
||||||
import ru.windcorp.progressia.test.TestPlayerControls;
|
import ru.windcorp.progressia.test.TestPlayerControls;
|
||||||
@ -57,6 +59,8 @@ public class LayerWorld extends Layer {
|
|||||||
super("World");
|
super("World");
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.inputBasedControls = new InputBasedControls(client);
|
this.inputBasedControls = new InputBasedControls(client);
|
||||||
|
|
||||||
|
setCursorPolicy(CursorPolicy.FORBID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -181,16 +185,25 @@ public class LayerWorld extends Layer {
|
|||||||
entity.getVelocity().mul((float) Math.exp(-FRICTION_COEFF / entity.getCollisionMass() * tickLength));
|
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) {
|
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()) {
|
if (ClientState.getInstance().getLocalPlayer().getEntity() == entity && tmp_testControls.isFlying()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final float gravitationalAcceleration = tmp_testControls.useMinecraftGravity() ? MC_g : IRL_g;
|
Vec3 gravitationalAcceleration = Vectors.grab3();
|
||||||
entity.getVelocity().add(0, 0, -gravitationalAcceleration * tickLength);
|
gm.getGravity(entity.getPosition(), gravitationalAcceleration);
|
||||||
|
|
||||||
|
gravitationalAcceleration.mul(tickLength);
|
||||||
|
entity.getVelocity().add(gravitationalAcceleration);
|
||||||
|
|
||||||
|
Vectors.release(gravitationalAcceleration);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -23,13 +23,13 @@ import glm.vec._3.Vec3;
|
|||||||
import glm.vec._3.i.Vec3i;
|
import glm.vec._3.i.Vec3i;
|
||||||
import ru.windcorp.progressia.client.world.WorldRender;
|
import ru.windcorp.progressia.client.world.WorldRender;
|
||||||
import ru.windcorp.progressia.common.world.BlockRay;
|
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.entity.EntityData;
|
||||||
|
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
||||||
|
|
||||||
public class Selection {
|
public class Selection {
|
||||||
|
|
||||||
private final Vec3i block = new Vec3i();
|
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 Vec2 pointOnSurface = new Vec2(0.5f, 0.5f);
|
||||||
private final Vec3 point = new Vec3();
|
private final Vec3 point = new Vec3();
|
||||||
|
|
||||||
@ -38,10 +38,9 @@ public class Selection {
|
|||||||
private BlockRay ray = new BlockRay();
|
private BlockRay ray = new BlockRay();
|
||||||
|
|
||||||
public void update(WorldRender world, EntityData player) {
|
public void update(WorldRender world, EntityData player) {
|
||||||
Vec3 direction = new Vec3();
|
|
||||||
Vec3 start = new Vec3();
|
Vec3 start = new Vec3();
|
||||||
|
Vec3 direction = player.getLookingAt();
|
||||||
player.getLookingAtVector(direction);
|
|
||||||
world.getEntityRenderable(player).getViewPoint(start);
|
world.getEntityRenderable(player).getViewPoint(start);
|
||||||
start.add(player.getPosition());
|
start.add(player.getPosition());
|
||||||
|
|
||||||
@ -71,7 +70,7 @@ public class Selection {
|
|||||||
return exists ? point : null;
|
return exists ? point : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlockFace getSurface() {
|
public AbsFace getSurface() {
|
||||||
return exists ? surface : null;
|
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.VertexBufferObject;
|
||||||
import ru.windcorp.progressia.client.graphics.backend.shaders.attributes.*;
|
import ru.windcorp.progressia.client.graphics.backend.shaders.attributes.*;
|
||||||
import ru.windcorp.progressia.client.graphics.backend.shaders.uniforms.*;
|
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.Shape;
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
|
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
|
||||||
@ -120,12 +120,12 @@ public class WorldRenderProgram extends ShapeRenderProgram {
|
|||||||
public void preprocess(Shape shape) {
|
public void preprocess(Shape shape) {
|
||||||
super.preprocess(shape);
|
super.preprocess(shape);
|
||||||
|
|
||||||
for (Face face : shape.getFaces()) {
|
for (ShapePart face : shape.getParts()) {
|
||||||
computeNormals(face);
|
computeNormals(face);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void computeNormals(Face face) {
|
private void computeNormals(ShapePart face) {
|
||||||
Vec3 a = Vectors.grab3();
|
Vec3 a = Vectors.grab3();
|
||||||
Vec3 b = Vectors.grab3();
|
Vec3 b = Vectors.grab3();
|
||||||
Vec3 c = Vectors.grab3();
|
Vec3 c = Vectors.grab3();
|
||||||
@ -160,7 +160,7 @@ public class WorldRenderProgram extends ShapeRenderProgram {
|
|||||||
normal.normalize();
|
normal.normalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadVertexPosition(Face face, int index, Vec3 result) {
|
private void loadVertexPosition(ShapePart face, int index, Vec3 result) {
|
||||||
ByteBuffer vertices = face.getVertices();
|
ByteBuffer vertices = face.getVertices();
|
||||||
int offset = vertices.position() + index * getBytesPerVertex();
|
int offset = vertices.position() + index * getBytesPerVertex();
|
||||||
|
|
||||||
@ -168,7 +168,7 @@ public class WorldRenderProgram extends ShapeRenderProgram {
|
|||||||
vertices.getFloat(offset + 2 * Float.BYTES));
|
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();
|
ByteBuffer vertices = face.getVertices();
|
||||||
int offset = vertices.position() + index * getBytesPerVertex()
|
int offset = vertices.position() + index * getBytesPerVertex()
|
||||||
+ (3 * Float.BYTES + 4 * Float.BYTES + 2 * Float.BYTES);
|
+ (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
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.client.world;
|
package ru.windcorp.progressia.client.world;
|
||||||
|
|
||||||
import java.util.Collections;
|
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.BlockRender;
|
||||||
import ru.windcorp.progressia.client.world.block.BlockRenderRegistry;
|
import ru.windcorp.progressia.client.world.block.BlockRenderRegistry;
|
||||||
import ru.windcorp.progressia.client.world.tile.TileRender;
|
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.TileRenderRegistry;
|
||||||
import ru.windcorp.progressia.client.world.tile.TileRenderStack;
|
import ru.windcorp.progressia.client.world.tile.TileRenderStack;
|
||||||
import ru.windcorp.progressia.common.world.ChunkData;
|
import ru.windcorp.progressia.common.world.DefaultChunkData;
|
||||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
import ru.windcorp.progressia.common.world.TileDataReference;
|
||||||
import ru.windcorp.progressia.common.world.generic.GenericChunk;
|
import ru.windcorp.progressia.common.world.TileDataStack;
|
||||||
import ru.windcorp.progressia.common.world.tile.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 WorldRender world;
|
||||||
private final ChunkData data;
|
private final DefaultChunkData data;
|
||||||
|
|
||||||
private final ChunkRenderModel model;
|
private final ChunkRenderModel model;
|
||||||
|
|
||||||
private final Map<TileDataStack, TileRenderStackImpl> tileRenderLists = Collections
|
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.world = world;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.model = new ChunkRenderModel(this);
|
this.model = new ChunkRenderModel(this);
|
||||||
@ -54,10 +59,17 @@ public class ChunkRender implements GenericChunk<ChunkRender, BlockRender, TileR
|
|||||||
public Vec3i getPosition() {
|
public Vec3i getPosition() {
|
||||||
return getData().getPosition();
|
return getData().getPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AbsFace getUp() {
|
||||||
|
return getData().getUp();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BlockRender getBlock(Vec3i posInChunk) {
|
public BlockRender getBlock(Vec3i posInChunk) {
|
||||||
return BlockRenderRegistry.getInstance().get(getData().getBlock(posInChunk).getId());
|
return BlockRenderRegistry.getInstance().get(
|
||||||
|
getData().getBlock(posInChunk).getId()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -71,18 +83,21 @@ public class ChunkRender implements GenericChunk<ChunkRender, BlockRender, TileR
|
|||||||
}
|
}
|
||||||
|
|
||||||
private TileRenderStack getTileStackWrapper(TileDataStack tileDataList) {
|
private TileRenderStack getTileStackWrapper(TileDataStack tileDataList) {
|
||||||
return tileRenderLists.computeIfAbsent(tileDataList, TileRenderStackImpl::new);
|
return tileRenderLists.computeIfAbsent(
|
||||||
|
tileDataList,
|
||||||
|
TileRenderStackImpl::new
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public WorldRender getWorld() {
|
public WorldRender getWorld() {
|
||||||
return world;
|
return world;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChunkData getData() {
|
public DefaultChunkData getData() {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void markForUpdate() {
|
public void markForUpdate() {
|
||||||
getWorld().markChunkForUpdate(getPosition());
|
getWorld().markChunkForUpdate(getPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,6 +110,28 @@ public class ChunkRender implements GenericChunk<ChunkRender, BlockRender, TileR
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class TileRenderStackImpl extends TileRenderStack {
|
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;
|
private final TileDataStack parent;
|
||||||
|
|
||||||
@ -113,9 +150,24 @@ public class ChunkRender implements GenericChunk<ChunkRender, BlockRender, TileR
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BlockFace getFace() {
|
public RelFace getFace() {
|
||||||
return parent.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
|
@Override
|
||||||
public TileRender get(int index) {
|
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.TileRender;
|
||||||
import ru.windcorp.progressia.client.world.tile.TileRenderNone;
|
import ru.windcorp.progressia.client.world.tile.TileRenderNone;
|
||||||
import ru.windcorp.progressia.client.world.tile.TileRenderStack;
|
import ru.windcorp.progressia.client.world.tile.TileRenderStack;
|
||||||
import ru.windcorp.progressia.common.world.ChunkData;
|
import ru.windcorp.progressia.common.world.DefaultChunkData;
|
||||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
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 {
|
public class ChunkRenderModel implements Renderable {
|
||||||
|
|
||||||
private final ChunkRender chunk;
|
private final ChunkRender chunk;
|
||||||
|
|
||||||
private final Collection<ChunkRenderOptimizer> optimizers = new ArrayList<>();
|
private final Collection<ChunkRenderOptimizer> optimizers = new ArrayList<>();
|
||||||
private Model model = null;
|
private Model model = null;
|
||||||
|
|
||||||
@ -51,42 +53,48 @@ public class ChunkRenderModel implements Renderable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(ShapeRenderHelper renderer) {
|
public void render(ShapeRenderHelper renderer) {
|
||||||
if (model == null)
|
if (model == null) return;
|
||||||
return;
|
|
||||||
|
float offset = DefaultChunkData.BLOCKS_PER_CHUNK / 2 - 0.5f;
|
||||||
renderer.pushTransform().translate(chunk.getX() * ChunkData.BLOCKS_PER_CHUNK,
|
|
||||||
chunk.getY() * ChunkData.BLOCKS_PER_CHUNK, chunk.getZ() * ChunkData.BLOCKS_PER_CHUNK);
|
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);
|
model.render(renderer);
|
||||||
|
|
||||||
renderer.popTransform();
|
renderer.popTransform();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update() {
|
public void update() {
|
||||||
setupCROs();
|
setupCROs();
|
||||||
|
|
||||||
StaticModel.Builder sink = StaticModel.builder();
|
StaticModel.Builder sink = StaticModel.builder();
|
||||||
|
|
||||||
optimizers.forEach(ChunkRenderOptimizer::startRender);
|
optimizers.forEach(ChunkRenderOptimizer::startRender);
|
||||||
|
|
||||||
chunk.forEachBiC(blockInChunk -> {
|
GenericChunks.forEachBiC(relBlockInChunk -> {
|
||||||
processBlockAndTiles(blockInChunk, sink);
|
processBlockAndTiles(relBlockInChunk, sink);
|
||||||
});
|
});
|
||||||
|
|
||||||
for (ChunkRenderOptimizer optimizer : optimizers) {
|
for (ChunkRenderOptimizer optimizer : optimizers) {
|
||||||
Renderable renderable = optimizer.endRender();
|
Renderable renderable = optimizer.endRender();
|
||||||
if (renderable != null) {
|
if (renderable != null) {
|
||||||
sink.addPart(renderable);
|
sink.addPart(renderable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.model = sink.build();
|
this.model = sink.build();
|
||||||
this.optimizers.clear();
|
this.optimizers.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupCROs() {
|
private void setupCROs() {
|
||||||
Set<String> ids = ChunkRenderOptimizerRegistry.getInstance().keySet();
|
Set<String> ids = ChunkRenderOptimizerRegistry.getInstance().keySet();
|
||||||
|
|
||||||
for (String id : ids) {
|
for (String id : ids) {
|
||||||
ChunkRenderOptimizer optimizer = ChunkRenderOptimizerRegistry.getInstance().create(id);
|
ChunkRenderOptimizer optimizer = ChunkRenderOptimizerRegistry.getInstance().create(id);
|
||||||
optimizer.setup(chunk);
|
optimizer.setup(chunk);
|
||||||
@ -94,61 +102,65 @@ public class ChunkRenderModel implements Renderable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processBlockAndTiles(Vec3i blockInChunk, Builder sink) {
|
private void processBlockAndTiles(Vec3i relBlockInChunk, Builder sink) {
|
||||||
processBlock(blockInChunk, sink);
|
processBlock(relBlockInChunk, sink);
|
||||||
|
|
||||||
for (BlockFace face : BlockFace.getFaces()) {
|
for (RelFace face : RelFace.getFaces()) {
|
||||||
processTileStack(blockInChunk, face, sink);
|
processTileStack(relBlockInChunk, face, sink);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processBlock(Vec3i blockInChunk, Builder sink) {
|
private void processBlock(Vec3i relBlockInChunk, Builder sink) {
|
||||||
BlockRender block = chunk.getBlock(blockInChunk);
|
BlockRender block = chunk.getBlockRel(relBlockInChunk);
|
||||||
|
|
||||||
if (block instanceof BlockRenderNone) {
|
if (block instanceof BlockRenderNone) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (block.needsOwnRenderable()) {
|
if (block.needsOwnRenderable()) {
|
||||||
sink.addPart(block.createRenderable(chunk.getData(), blockInChunk),
|
sink.addPart(
|
||||||
new Mat4().identity().translate(blockInChunk.x, blockInChunk.y, blockInChunk.z));
|
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) {
|
for (ChunkRenderOptimizer optimizer : optimizers) {
|
||||||
optimizer.addBlock(block, blockInChunk);
|
optimizer.addBlock(block, relBlockInChunk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processTileStack(Vec3i blockInChunk, BlockFace face, Builder sink) {
|
private void processTileStack(Vec3i relBlockInChunk, RelFace face, Builder sink) {
|
||||||
TileRenderStack trs = chunk.getTilesOrNull(blockInChunk, face);
|
TileRenderStack trs = chunk.getTilesOrNullRel(relBlockInChunk, face);
|
||||||
|
|
||||||
if (trs == null || trs.isEmpty()) {
|
if (trs == null || trs.isEmpty()) {
|
||||||
return;
|
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) {
|
if (tile instanceof TileRenderNone) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tile.needsOwnRenderable()) {
|
if (tile.needsOwnRenderable()) {
|
||||||
sink.addPart(tile.createRenderable(chunk.getData(), blockInChunk, face),
|
sink.addPart(
|
||||||
new Mat4().identity().translate(blockInChunk.x, blockInChunk.y, blockInChunk.z));
|
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) {
|
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
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.client.world;
|
package ru.windcorp.progressia.client.world;
|
||||||
|
|
||||||
import glm.vec._3.i.Vec3i;
|
import glm.vec._3.i.Vec3i;
|
||||||
import ru.windcorp.progressia.common.util.VectorUtil;
|
import ru.windcorp.progressia.common.util.VectorUtil;
|
||||||
import ru.windcorp.progressia.common.util.Vectors;
|
import ru.windcorp.progressia.common.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.ChunkDataListener;
|
||||||
import ru.windcorp.progressia.common.world.block.BlockData;
|
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;
|
import ru.windcorp.progressia.common.world.tile.TileData;
|
||||||
|
|
||||||
class ChunkUpdateListener implements ChunkDataListener {
|
class ChunkUpdateListener implements ChunkDataListener {
|
||||||
@ -36,58 +37,63 @@ class ChunkUpdateListener implements ChunkDataListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onChunkChanged(ChunkData chunk) {
|
public void onChunkChanged(DefaultChunkData chunk) {
|
||||||
world.getChunk(chunk).markForUpdate();
|
world.getChunk(chunk).markForUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onChunkLoaded(ChunkData chunk) {
|
public void onChunkLoaded(DefaultChunkData chunk) {
|
||||||
Vec3i cursor = new Vec3i();
|
Vec3i cursor = new Vec3i();
|
||||||
for (BlockFace face : BlockFace.getFaces()) {
|
for (AbsFace face : AbsFace.getFaces()) {
|
||||||
cursor.set(chunk.getX(), chunk.getY(), chunk.getZ());
|
cursor.set(chunk.getX(), chunk.getY(), chunk.getZ());
|
||||||
cursor.add(face.getVector());
|
cursor.add(face.getVector());
|
||||||
world.markChunkForUpdate(cursor);
|
world.markChunkForUpdate(cursor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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);
|
onLocationChanged(chunk, blockInChunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void onLocationChanged(DefaultChunkData chunk, Vec3i blockInChunk) {
|
||||||
public void onChunkTilesChanged(ChunkData chunk, Vec3i blockInChunk, BlockFace face, TileData tile,
|
|
||||||
boolean wasAdded) {
|
|
||||||
onLocationChanged(chunk, blockInChunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onLocationChanged(ChunkData chunk, Vec3i blockInChunk) {
|
|
||||||
Vec3i chunkPos = Vectors.grab3i().set(chunk.getX(), chunk.getY(), chunk.getZ());
|
Vec3i chunkPos = Vectors.grab3i().set(chunk.getX(), chunk.getY(), chunk.getZ());
|
||||||
|
|
||||||
checkCoordinate(blockInChunk, chunkPos, VectorUtil.Axis.X);
|
checkCoordinate(blockInChunk, chunkPos, VectorUtil.Axis.X);
|
||||||
checkCoordinate(blockInChunk, chunkPos, VectorUtil.Axis.Y);
|
checkCoordinate(blockInChunk, chunkPos, VectorUtil.Axis.Y);
|
||||||
checkCoordinate(blockInChunk, chunkPos, VectorUtil.Axis.Z);
|
checkCoordinate(blockInChunk, chunkPos, VectorUtil.Axis.Z);
|
||||||
|
|
||||||
Vectors.release(chunkPos);
|
Vectors.release(chunkPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkCoordinate(Vec3i blockInChunk, Vec3i chunkPos, VectorUtil.Axis axis) {
|
private void checkCoordinate(Vec3i blockInChunk, Vec3i chunkPos, VectorUtil.Axis axis) {
|
||||||
int block = VectorUtil.get(blockInChunk, axis);
|
int block = VectorUtil.get(blockInChunk, axis);
|
||||||
int diff = 0;
|
int diff = 0;
|
||||||
|
|
||||||
if (block == 0) {
|
if (block == 0) {
|
||||||
diff = -1;
|
diff = -1;
|
||||||
} else if (block == ChunkData.BLOCKS_PER_CHUNK - 1) {
|
} else if (block == DefaultChunkData.BLOCKS_PER_CHUNK - 1) {
|
||||||
diff = +1;
|
diff = +1;
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int previousChunkPos = VectorUtil.get(chunkPos, axis);
|
int previousChunkPos = VectorUtil.get(chunkPos, axis);
|
||||||
VectorUtil.set(chunkPos, axis, previousChunkPos + diff);
|
VectorUtil.set(chunkPos, axis, previousChunkPos + diff);
|
||||||
|
|
||||||
world.markChunkForUpdate(chunkPos);
|
world.markChunkForUpdate(chunkPos);
|
||||||
|
|
||||||
VectorUtil.set(chunkPos, axis, previousChunkPos);
|
VectorUtil.set(chunkPos, axis, previousChunkPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.client.world;
|
package ru.windcorp.progressia.client.world;
|
||||||
|
|
||||||
import java.util.Collection;
|
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.EntityRenderRegistry;
|
||||||
import ru.windcorp.progressia.client.world.entity.EntityRenderable;
|
import ru.windcorp.progressia.client.world.entity.EntityRenderable;
|
||||||
import ru.windcorp.progressia.client.world.tile.TileRender;
|
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.client.world.tile.TileRenderStack;
|
||||||
import ru.windcorp.progressia.common.util.VectorUtil;
|
import ru.windcorp.progressia.common.util.VectorUtil;
|
||||||
import ru.windcorp.progressia.common.util.Vectors;
|
import ru.windcorp.progressia.common.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.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.WorldDataListener;
|
||||||
import ru.windcorp.progressia.common.world.entity.EntityData;
|
import ru.windcorp.progressia.common.world.entity.EntityData;
|
||||||
import ru.windcorp.progressia.common.world.generic.ChunkSet;
|
import ru.windcorp.progressia.common.world.generic.ChunkSet;
|
||||||
import ru.windcorp.progressia.common.world.generic.ChunkSets;
|
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
|
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 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 Map<EntityData, EntityRenderable> entityModels = Collections.synchronizedMap(new WeakHashMap<>());
|
||||||
|
|
||||||
private final ChunkSet chunksToUpdate = ChunkSets.newSyncHashSet();
|
private final ChunkSet chunksToUpdate = ChunkSets.newSyncHashSet();
|
||||||
|
|
||||||
public WorldRender(WorldData data, Client client) {
|
public WorldRender(DefaultWorldData data, Client client) {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
|
||||||
data.addListener(ChunkDataListeners.createAdder(new ChunkUpdateListener(this)));
|
data.addListener(ChunkDataListeners.createAdder(new ChunkUpdateListener(this)));
|
||||||
data.addListener(new WorldDataListener() {
|
data.addListener(new WorldDataListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onChunkLoaded(WorldData world, ChunkData chunk) {
|
public void onChunkLoaded(DefaultWorldData world, DefaultChunkData chunk) {
|
||||||
addChunk(chunk);
|
addChunk(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void beforeChunkUnloaded(WorldData world, ChunkData chunk) {
|
public void beforeChunkUnloaded(DefaultWorldData world, DefaultChunkData chunk) {
|
||||||
removeChunk(chunk);
|
removeChunk(chunk);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addChunk(ChunkData chunk) {
|
protected void addChunk(DefaultChunkData chunk) {
|
||||||
chunks.put(chunk, new ChunkRender(WorldRender.this, chunk));
|
chunks.put(chunk, new ChunkRender(WorldRender.this, chunk));
|
||||||
markChunkForUpdate(chunk.getPosition());
|
markChunkForUpdate(chunk.getPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void removeChunk(ChunkData chunk) {
|
protected void removeChunk(DefaultChunkData chunk) {
|
||||||
chunks.remove(chunk);
|
chunks.remove(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
public WorldData getData() {
|
public DefaultWorldData getData() {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +93,7 @@ public class WorldRender
|
|||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChunkRender getChunk(ChunkData chunkData) {
|
public ChunkRender getChunk(DefaultChunkData chunkData) {
|
||||||
return chunks.get(chunkData);
|
return chunks.get(chunkData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,6 +111,13 @@ public class WorldRender
|
|||||||
public Collection<EntityRenderable> getEntities() {
|
public Collection<EntityRenderable> getEntities() {
|
||||||
return entityModels.values();
|
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) {
|
public void render(ShapeRenderHelper renderer) {
|
||||||
updateChunks();
|
updateChunks();
|
||||||
@ -207,11 +215,15 @@ public class WorldRender
|
|||||||
}
|
}
|
||||||
|
|
||||||
public EntityRenderable getEntityRenderable(EntityData entity) {
|
public EntityRenderable getEntityRenderable(EntityData entity) {
|
||||||
return entityModels.computeIfAbsent(entity, WorldRender::createEntityRenderable);
|
return entityModels.computeIfAbsent(
|
||||||
|
entity,
|
||||||
|
WorldRender::createEntityRenderable
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static EntityRenderable createEntityRenderable(EntityData entity) {
|
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) {
|
public void markChunkForUpdate(Vec3i chunkPos) {
|
||||||
|
@ -15,27 +15,22 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.client.world.block;
|
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.util.namespaces.Namespaced;
|
||||||
import ru.windcorp.progressia.common.world.ChunkData;
|
import ru.windcorp.progressia.common.world.DefaultChunkData;
|
||||||
import ru.windcorp.progressia.common.world.generic.GenericBlock;
|
import ru.windcorp.progressia.common.world.generic.BlockGeneric;
|
||||||
import glm.vec._3.i.Vec3i;
|
import glm.vec._3.i.Vec3i;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
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) {
|
public BlockRender(String id) {
|
||||||
super(id);
|
super(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void render(ShapeRenderHelper renderer) {
|
public Renderable createRenderable(DefaultChunkData chunk, Vec3i relBlockInChunk) {
|
||||||
throw new UnsupportedOperationException("BlockRender.render() not implemented in " + this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ package ru.windcorp.progressia.client.world.block;
|
|||||||
import glm.vec._3.i.Vec3i;
|
import glm.vec._3.i.Vec3i;
|
||||||
import ru.windcorp.progressia.client.graphics.model.EmptyModel;
|
import ru.windcorp.progressia.client.graphics.model.EmptyModel;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
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 {
|
public class BlockRenderNone extends BlockRender {
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ public class BlockRenderNone extends BlockRender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk) {
|
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk) {
|
||||||
return EmptyModel.getInstance();
|
return EmptyModel.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,25 +15,60 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.client.world.block;
|
package ru.windcorp.progressia.client.world.block;
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
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 class BlockRenderOpaqueCube extends BlockRenderTexturedCube {
|
||||||
|
|
||||||
public BlockRenderOpaqueCube(String id, Texture topTexture, Texture bottomTexture, Texture northTexture,
|
public BlockRenderOpaqueCube(
|
||||||
Texture southTexture, Texture eastTexture, Texture westTexture) {
|
String id,
|
||||||
super(id, topTexture, bottomTexture, northTexture, southTexture, eastTexture, westTexture);
|
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) {
|
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
|
@Override
|
||||||
public boolean isOpaque(BlockFace face) {
|
public boolean isOpaque(RelFace face) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,12 +15,11 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.client.world.block;
|
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.Map;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@ -29,69 +28,86 @@ import glm.vec._3.i.Vec3i;
|
|||||||
import glm.vec._4.Vec4;
|
import glm.vec._4.Vec4;
|
||||||
import ru.windcorp.progressia.client.graphics.Colors;
|
import ru.windcorp.progressia.client.graphics.Colors;
|
||||||
import ru.windcorp.progressia.client.graphics.backend.Usage;
|
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.Faces;
|
import ru.windcorp.progressia.client.graphics.model.ShapeParts;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Shape;
|
import ru.windcorp.progressia.client.graphics.model.Shape;
|
||||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||||
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
|
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
|
||||||
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSurface.BlockOptimizedSurface;
|
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSurface.BlockOptimizedSurface;
|
||||||
import ru.windcorp.progressia.common.util.Vectors;
|
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.block.BlockFace;
|
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,
|
public BlockRenderTexturedCube(
|
||||||
Texture southTexture, Texture eastTexture, Texture westTexture) {
|
String id,
|
||||||
|
Texture topTexture,
|
||||||
|
Texture bottomTexture,
|
||||||
|
Texture northTexture,
|
||||||
|
Texture southTexture,
|
||||||
|
Texture westTexture,
|
||||||
|
Texture eastTexture
|
||||||
|
) {
|
||||||
super(id);
|
super(id);
|
||||||
|
this.textures = RelFace.mapToFaces(topTexture, bottomTexture, northTexture, southTexture, westTexture, eastTexture);
|
||||||
textures.put(TOP, topTexture);
|
|
||||||
textures.put(BOTTOM, bottomTexture);
|
|
||||||
textures.put(NORTH, northTexture);
|
|
||||||
textures.put(SOUTH, southTexture);
|
|
||||||
textures.put(EAST, eastTexture);
|
|
||||||
textures.put(WEST, westTexture);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Texture getTexture(BlockFace blockFace) {
|
public Texture getTexture(RelFace blockFace) {
|
||||||
return textures.get(blockFace);
|
return textures.get(blockFace);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vec4 getColorMultiplier(BlockFace blockFace) {
|
public Vec4 getColorMultiplier(RelFace blockFace) {
|
||||||
return Colors.WHITE;
|
return Colors.WHITE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void getFaces(ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace, boolean inner,
|
public final void getShapeParts(
|
||||||
Consumer<Face> output, Vec3 offset) {
|
DefaultChunkData chunk, Vec3i blockInChunk, RelFace blockFace,
|
||||||
|
boolean inner,
|
||||||
|
Consumer<ShapePart> output,
|
||||||
|
Vec3 offset
|
||||||
|
) {
|
||||||
output.accept(createFace(chunk, blockInChunk, blockFace, inner, offset));
|
output.accept(createFace(chunk, blockInChunk, blockFace, inner, offset));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Face createFace(ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace, boolean inner, Vec3 offset) {
|
private ShapePart createFace(
|
||||||
return Faces.createBlockFace(WorldRenderProgram.getDefault(), getTexture(blockFace),
|
DefaultChunkData chunk, Vec3i blockInChunk, RelFace blockFace,
|
||||||
getColorMultiplier(blockFace), offset, blockFace, inner);
|
boolean inner,
|
||||||
|
Vec3 offset
|
||||||
|
) {
|
||||||
|
return ShapeParts.createBlockFace(
|
||||||
|
WorldRenderProgram.getDefault(),
|
||||||
|
getTexture(blockFace),
|
||||||
|
getColorMultiplier(blockFace),
|
||||||
|
offset,
|
||||||
|
blockFace.resolve(AbsFace.POS_Z),
|
||||||
|
inner
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk) {
|
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk) {
|
||||||
boolean opaque = isBlockOpaque();
|
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) {
|
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) {
|
if (!opaque) {
|
||||||
for (int i = 0; i < BLOCK_FACE_COUNT; ++i) {
|
for (int i = 0; i < BLOCK_FACE_COUNT; ++i) {
|
||||||
faces[i + BLOCK_FACE_COUNT] = createFace(chunk, blockInChunk, BlockFace.getFaces().get(i), true,
|
faces[i + BLOCK_FACE_COUNT] = createFace(chunk, blockInChunk, RelFace.getFaces().get(i), true, Vectors.ZERO_3);
|
||||||
Vectors.ZERO_3);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Shape(Usage.STATIC, WorldRenderProgram.getDefault(), faces);
|
return new Shape(Usage.STATIC, WorldRenderProgram.getDefault(), faces);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,25 +15,60 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.client.world.block;
|
package ru.windcorp.progressia.client.world.block;
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
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 class BlockRenderTransparentCube extends BlockRenderTexturedCube {
|
||||||
|
|
||||||
public BlockRenderTransparentCube(String id, Texture topTexture, Texture bottomTexture, Texture northTexture,
|
public BlockRenderTransparentCube(
|
||||||
Texture southTexture, Texture eastTexture, Texture westTexture) {
|
String id,
|
||||||
super(id, topTexture, bottomTexture, northTexture, southTexture, eastTexture, westTexture);
|
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) {
|
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
|
@Override
|
||||||
public boolean isOpaque(BlockFace face) {
|
public boolean isOpaque(RelFace face) {
|
||||||
return false;
|
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.block.BlockRender;
|
||||||
import ru.windcorp.progressia.client.world.tile.TileRender;
|
import ru.windcorp.progressia.client.world.tile.TileRender;
|
||||||
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
|
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
|
* 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
|
* tiles. An example of a CRO is {@link ChunkRenderOptimizerSurface}: it removes
|
||||||
* block surfaces and tiles that it knows cannot be seen, thus significantly
|
* block surfaces and tiles that it knows cannot be seen, thus significantly
|
||||||
* reducing total polygon count.
|
* reducing total polygon count.
|
||||||
* <h3>CRO lifecycle</h3> A CRO instance is created by
|
* <p>
|
||||||
* {@link ChunkRenderOptimizerRegistry}. It may then be used to work on multiple
|
* As with everything related to rendering chunks, CROs are interacted with
|
||||||
* chunks sequentially. Each chunk is processed in the following way:
|
* 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>
|
* <ol>
|
||||||
* <li>{@link #setup(ChunkRender)} is invoked to provide the {@link ChunkRender}
|
* <li>{@link #setup(ChunkRender)} is invoked to provide the {@link ChunkRender}
|
||||||
* instance.</li>
|
* instance.</li>
|
||||||
* <li>{@link #startRender()} is invoked. The CRO must reset its state.</li>
|
* <li>{@link #startRender()} is invoked. The CRO must reset its state.</li>
|
||||||
* <li>{@link #addBlock(BlockRender, Vec3i)} and
|
* <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
|
* tile that this CRO should optimize. {@code addTile} specifies tiles in order
|
||||||
* of ascension within a tile stack.</li>
|
* of ascension within a tile stack.</li>
|
||||||
* <li>{@link #endRender()} is invoked. The CRO may perform any pending
|
* <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.
|
* Creates a new CRO instance with the specified ID.
|
||||||
*
|
*
|
||||||
* @param id
|
* @param id the ID of this CRO
|
||||||
* the ID of this CRO
|
|
||||||
*/
|
*/
|
||||||
public ChunkRenderOptimizer(String id) {
|
public ChunkRenderOptimizer(String id) {
|
||||||
super(id);
|
super(id);
|
||||||
@ -74,8 +80,7 @@ public abstract class ChunkRenderOptimizer extends Namespaced {
|
|||||||
* specify the chunk. When overriding, {@code super.setup(chunk)} must be
|
* specify the chunk. When overriding, {@code super.setup(chunk)} must be
|
||||||
* invoked.
|
* invoked.
|
||||||
*
|
*
|
||||||
* @param chunk
|
* @param chunk the chunk that will be processed next
|
||||||
* the chunk that will be processed next
|
|
||||||
*/
|
*/
|
||||||
public void setup(ChunkRender chunk) {
|
public void setup(ChunkRender chunk) {
|
||||||
this.chunk = 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
|
* method is only invoked once per block. This method is not necessarily
|
||||||
* invoked for each block.
|
* invoked for each block.
|
||||||
*
|
*
|
||||||
* @param block
|
* @param block a {@link BlockRender} instance describing the
|
||||||
* a {@link BlockRender} instance describing the block. It
|
* block.
|
||||||
* corresponds to {@code getChunk().getBlock(blockInChunk)}.
|
* It corresponds to
|
||||||
* @param blockInChunk
|
* {@code getChunk().getBlock(blockInChunk)}.
|
||||||
* the position of the block
|
* @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
|
* 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,
|
* invoked for each tile. When multiple tiles in a tile stack are requested,
|
||||||
* this method is invoked for lower tiles first.
|
* this method is invoked for lower tiles first.
|
||||||
*
|
*
|
||||||
* @param tile
|
* @param tile a {@link BlockRender} instance describing the tile
|
||||||
* a {@link BlockRender} instance describing the tile
|
* @param relBlockInChunk the relative position of the block that the tile
|
||||||
* @param blockInChunk
|
* belongs to
|
||||||
* the position of the block that the tile belongs to
|
* @param blockFace the face 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
|
* Requests that the CRO assembles and outputs its model. This method may
|
||||||
|
@ -18,9 +18,9 @@
|
|||||||
|
|
||||||
package ru.windcorp.progressia.client.world.cro;
|
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.DefaultChunkData.BLOCKS_PER_CHUNK;
|
||||||
import static ru.windcorp.progressia.common.world.block.BlockFace.BLOCK_FACE_COUNT;
|
import static ru.windcorp.progressia.common.world.generic.TileGenericStackRO.TILES_PER_FACE;
|
||||||
import static ru.windcorp.progressia.common.world.generic.GenericTileStack.TILES_PER_FACE;
|
import static ru.windcorp.progressia.common.world.rels.AbsFace.BLOCK_FACE_COUNT;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@ -29,7 +29,7 @@ import java.util.function.Consumer;
|
|||||||
import glm.vec._3.Vec3;
|
import glm.vec._3.Vec3;
|
||||||
import glm.vec._3.i.Vec3i;
|
import glm.vec._3.i.Vec3i;
|
||||||
import ru.windcorp.progressia.client.graphics.backend.Usage;
|
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.Renderable;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Shape;
|
import ru.windcorp.progressia.client.graphics.model.Shape;
|
||||||
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
|
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.block.BlockRender;
|
||||||
import ru.windcorp.progressia.client.world.tile.TileRender;
|
import ru.windcorp.progressia.client.world.tile.TileRender;
|
||||||
import ru.windcorp.progressia.common.util.Vectors;
|
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.block.BlockFace;
|
import ru.windcorp.progressia.common.world.generic.GenericChunks;
|
||||||
|
import ru.windcorp.progressia.common.world.rels.RelFace;
|
||||||
|
|
||||||
public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
|
public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
|
||||||
|
|
||||||
@ -52,38 +53,42 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
|
|||||||
private static interface OptimizedSurface {
|
private static interface OptimizedSurface {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates and outputs a set of faces that correspond to this surface.
|
* Creates and outputs a set of shape parts that correspond to this
|
||||||
* The coordinates of the face vertices must be in chunk coordinate
|
* surface. The coordinates of the face vertices must be in chunk
|
||||||
* system.
|
* coordinate system.
|
||||||
*
|
*
|
||||||
* @param chunk
|
* @param chunk the chunk that contains the requested face
|
||||||
* the chunk that contains the requested face
|
* @param relBlockInChunk the relative block in chunk
|
||||||
* @param blockInChunk
|
* @param blockFace the requested face
|
||||||
* the block in chunk
|
* @param inner whether this face should be visible from
|
||||||
* @param blockFace
|
* inside
|
||||||
* the requested face
|
* ({@code true}) or outside ({@code false})
|
||||||
* @param inner
|
* @param output a consumer that the created shape parts must
|
||||||
* whether this face should be visible from inside
|
* be
|
||||||
* ({@code true}) or outside ({@code false})
|
* given to
|
||||||
* @param output
|
* @param offset an additional offset that must be applied to
|
||||||
* a consumer that the created faces must be given to
|
* all
|
||||||
* @param offset
|
* vertices
|
||||||
* an additional offset that must be applied to all vertices
|
|
||||||
*/
|
*/
|
||||||
void getFaces(ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace, boolean inner, Consumer<Face> output,
|
void getShapeParts(
|
||||||
Vec3 offset /* kostyl 156% */
|
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
|
* Returns the opacity of the surface identified by the provided
|
||||||
* {@link BlockFace}. Opaque surfaces prevent surfaces behind them from
|
* {@link RelFace}.
|
||||||
* being included in chunk models.
|
* Opaque surfaces prevent surfaces behind them from being included in
|
||||||
|
* chunk models.
|
||||||
*
|
*
|
||||||
* @param blockFace
|
* @param blockFace the face to query
|
||||||
* the face to query
|
|
||||||
* @return {@code true} iff the surface is opaque.
|
* @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
|
* Returns the opacity of the block. Opaque blocks do not expect that
|
||||||
* the camera can be inside them. Opaque blocks prevent surfaces 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.
|
* @return {@code true} iff the block is opaque.
|
||||||
*/
|
*/
|
||||||
@ -157,29 +163,29 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addBlock(BlockRender block, Vec3i pos) {
|
public void addBlock(BlockRender block, Vec3i relBlockInChunk) {
|
||||||
if (!(block instanceof BlockOptimizedSurface))
|
if (!(block instanceof BlockOptimizedSurface))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
BlockOptimizedSurface bos = (BlockOptimizedSurface) block;
|
BlockOptimizedSurface bos = (BlockOptimizedSurface) block;
|
||||||
addBlock(pos, bos);
|
addBlock(relBlockInChunk, bos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addTile(TileRender tile, Vec3i pos, BlockFace face) {
|
public void addTile(TileRender tile, Vec3i relBlockInChunk, RelFace face) {
|
||||||
if (!(tile instanceof TileOptimizedSurface))
|
if (!(tile instanceof TileOptimizedSurface))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
TileOptimizedSurface tos = (TileOptimizedSurface) tile;
|
TileOptimizedSurface tos = (TileOptimizedSurface) tile;
|
||||||
addTile(pos, face, tos);
|
addTile(relBlockInChunk, face, tos);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addBlock(Vec3i pos, BlockOptimizedSurface block) {
|
private void addBlock(Vec3i relBlockInChunk, BlockOptimizedSurface block) {
|
||||||
getBlock(pos).block = block;
|
getBlock(relBlockInChunk).block = block;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addTile(Vec3i pos, BlockFace face, TileOptimizedSurface tile) {
|
private void addTile(Vec3i relBlockInChunk, RelFace face, TileOptimizedSurface tile) {
|
||||||
FaceInfo faceInfo = getFace(pos, face);
|
FaceInfo faceInfo = getFace(relBlockInChunk, face);
|
||||||
|
|
||||||
int index = faceInfo.tileCount;
|
int index = faceInfo.tileCount;
|
||||||
faceInfo.tileCount++;
|
faceInfo.tileCount++;
|
||||||
@ -195,119 +201,130 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected BlockInfo getBlock(Vec3i cursor) {
|
protected BlockInfo getBlock(Vec3i relBlockInChunk) {
|
||||||
return data[cursor.x][cursor.y][cursor.z];
|
return data[relBlockInChunk.x][relBlockInChunk.y][relBlockInChunk.z];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected FaceInfo getFace(Vec3i cursor, BlockFace face) {
|
protected FaceInfo getFace(Vec3i relBlockInChunk, RelFace face) {
|
||||||
return getBlock(cursor).faces[face.getId()];
|
return getBlock(relBlockInChunk).faces[face.getId()];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Renderable endRender() {
|
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<ShapePart> consumer = shapeParts::add;
|
||||||
Consumer<Face> consumer = shapeFaces::add;
|
|
||||||
|
|
||||||
for (cursor.x = 0; cursor.x < BLOCKS_PER_CHUNK; ++cursor.x) {
|
GenericChunks.forEachBiC(relBlockInChunk -> {
|
||||||
for (cursor.y = 0; cursor.y < BLOCKS_PER_CHUNK; ++cursor.y) {
|
processInnerFaces(relBlockInChunk, consumer);
|
||||||
for (cursor.z = 0; cursor.z < BLOCKS_PER_CHUNK; ++cursor.z) {
|
processOuterFaces(relBlockInChunk, consumer);
|
||||||
processInnerFaces(cursor, consumer);
|
});
|
||||||
processOuterFaces(cursor, consumer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shapeFaces.isEmpty()) {
|
if (shapeParts.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Shape(Usage.STATIC, WorldRenderProgram.getDefault(),
|
return new Shape(
|
||||||
shapeFaces.toArray(new Face[shapeFaces.size()]));
|
Usage.STATIC,
|
||||||
|
WorldRenderProgram.getDefault(),
|
||||||
|
shapeParts.toArray(new ShapePart[shapeParts.size()])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processOuterFaces(Vec3i blockInChunk, Consumer<Face> output) {
|
private void processOuterFaces(
|
||||||
for (BlockFace blockFace : BlockFace.getFaces()) {
|
Vec3i relBlockInChunk,
|
||||||
processOuterFace(blockInChunk, blockFace, output);
|
Consumer<ShapePart> output
|
||||||
|
) {
|
||||||
|
for (RelFace blockFace : RelFace.getFaces()) {
|
||||||
|
processOuterFace(relBlockInChunk, blockFace, output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processOuterFace(Vec3i blockInChunk, BlockFace blockFace, Consumer<Face> output) {
|
private void processOuterFace(Vec3i relBlockInChunk, RelFace blockFace, Consumer<ShapePart> output) {
|
||||||
if (!shouldRenderOuterFace(blockInChunk, blockFace))
|
if (!shouldRenderOuterFace(relBlockInChunk, blockFace))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
FaceInfo info = getFace(blockInChunk, blockFace);
|
FaceInfo info = getFace(relBlockInChunk, blockFace);
|
||||||
|
|
||||||
if (info.tileCount == 0 && info.block.block == null)
|
if (info.tileCount == 0 && info.block.block == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Vec3 faceOrigin = new Vec3(blockInChunk.x, blockInChunk.y, blockInChunk.z);
|
Vec3 faceOrigin = new Vec3(relBlockInChunk.x, relBlockInChunk.y, relBlockInChunk.z);
|
||||||
Vec3 offset = new Vec3(blockFace.getFloatVector()).mul(OVERLAY_OFFSET);
|
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);
|
OptimizedSurface surface = info.getSurface(layer);
|
||||||
if (surface == null)
|
if (surface == null)
|
||||||
continue; // layer may be BLOCK_LAYER, then block may be 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);
|
faceOrigin.add(offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processInnerFaces(Vec3i blockInChunk, Consumer<Face> output) {
|
private void processInnerFaces(Vec3i relBlockInChunk, Consumer<ShapePart> output) {
|
||||||
for (BlockFace blockFace : BlockFace.getFaces()) {
|
for (RelFace blockFace : RelFace.getFaces()) {
|
||||||
processInnerFace(blockInChunk, blockFace, output);
|
processInnerFace(relBlockInChunk, blockFace, output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processInnerFace(Vec3i blockInChunk, BlockFace blockFace, Consumer<Face> output) {
|
private void processInnerFace(Vec3i relBlockInChunk, RelFace blockFace, Consumer<ShapePart> output) {
|
||||||
if (!shouldRenderInnerFace(blockInChunk, blockFace))
|
if (!shouldRenderInnerFace(relBlockInChunk, blockFace))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
FaceInfo info = getFace(blockInChunk, blockFace);
|
FaceInfo info = getFace(relBlockInChunk, blockFace);
|
||||||
|
|
||||||
if (info.tileCount == 0 && info.block.block == null)
|
if (info.tileCount == 0 && info.block.block == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Vec3 faceOrigin = new Vec3(blockInChunk.x, blockInChunk.y, blockInChunk.z);
|
Vec3 faceOrigin = new Vec3(relBlockInChunk.x, relBlockInChunk.y, relBlockInChunk.z);
|
||||||
Vec3 offset = new Vec3(blockFace.getFloatVector()).mul(OVERLAY_OFFSET);
|
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);
|
OptimizedSurface surface = info.getSurface(layer);
|
||||||
if (surface == null)
|
if (surface == null)
|
||||||
continue; // layer may be BLOCK_LAYER, then block may be 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);
|
faceOrigin.add(offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldRenderOuterFace(Vec3i blockInChunk, BlockFace face) {
|
private boolean shouldRenderOuterFace(Vec3i relBlockInChunk, RelFace face) {
|
||||||
blockInChunk.add(face.getVector());
|
relBlockInChunk.add(face.getRelVector());
|
||||||
try {
|
try {
|
||||||
return shouldRenderWhenFacing(blockInChunk, face);
|
return shouldRenderWhenFacing(relBlockInChunk, face);
|
||||||
} finally {
|
} finally {
|
||||||
blockInChunk.sub(face.getVector());
|
relBlockInChunk.sub(face.getRelVector());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldRenderInnerFace(Vec3i blockInChunk, BlockFace face) {
|
private boolean shouldRenderInnerFace(Vec3i relBlockInChunk, RelFace face) {
|
||||||
return shouldRenderWhenFacing(blockInChunk, face);
|
return shouldRenderWhenFacing(relBlockInChunk, face);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldRenderWhenFacing(Vec3i blockInChunk, BlockFace face) {
|
private boolean shouldRenderWhenFacing(Vec3i relBlockInChunk, RelFace face) {
|
||||||
if (chunk.containsBiC(blockInChunk)) {
|
if (GenericChunks.containsBiC(relBlockInChunk)) {
|
||||||
return shouldRenderWhenFacingLocal(blockInChunk, face);
|
return shouldRenderWhenFacingLocal(relBlockInChunk, face);
|
||||||
} else {
|
} else {
|
||||||
return shouldRenderWhenFacingNeighbor(blockInChunk, face);
|
return shouldRenderWhenFacingNeighbor(relBlockInChunk, face);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldRenderWhenFacingLocal(Vec3i blockInChunk, BlockFace face) {
|
private boolean shouldRenderWhenFacingLocal(Vec3i relBlockInChunk, RelFace face) {
|
||||||
BlockOptimizedSurface block = getBlock(blockInChunk).block;
|
BlockOptimizedSurface block = getBlock(relBlockInChunk).block;
|
||||||
|
|
||||||
if (block == null) {
|
if (block == null) {
|
||||||
return true;
|
return true;
|
||||||
@ -319,33 +336,38 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldRenderWhenFacingNeighbor(Vec3i blockInLocalChunk, BlockFace face) {
|
private boolean shouldRenderWhenFacingNeighbor(Vec3i relBlockInLocalChunk, RelFace face) {
|
||||||
Vec3i blockInChunk = Vectors.grab3i().set(blockInLocalChunk.x, blockInLocalChunk.y, blockInLocalChunk.z);
|
Vec3i blockInChunk = Vectors.grab3i();
|
||||||
|
chunk.resolve(relBlockInLocalChunk, blockInChunk);
|
||||||
Vec3i chunkPos = Vectors.grab3i().set(chunk.getX(), chunk.getY(), chunk.getZ());
|
Vec3i chunkPos = Vectors.grab3i().set(chunk.getX(), chunk.getY(), chunk.getZ());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Determine blockInChunk and chunkPos
|
// Determine blockInChunk and chunkPos
|
||||||
if (blockInLocalChunk.x == -1) {
|
if (blockInChunk.x == -1) {
|
||||||
blockInChunk.x = BLOCKS_PER_CHUNK - 1;
|
blockInChunk.x = BLOCKS_PER_CHUNK - 1;
|
||||||
chunkPos.x -= 1;
|
chunkPos.x -= 1;
|
||||||
} else if (blockInLocalChunk.x == BLOCKS_PER_CHUNK) {
|
} else if (blockInChunk.x == BLOCKS_PER_CHUNK) {
|
||||||
blockInChunk.x = 0;
|
blockInChunk.x = 0;
|
||||||
chunkPos.x += 1;
|
chunkPos.x += 1;
|
||||||
} else if (blockInLocalChunk.y == -1) {
|
} else if (blockInChunk.y == -1) {
|
||||||
blockInChunk.y = BLOCKS_PER_CHUNK - 1;
|
blockInChunk.y = BLOCKS_PER_CHUNK - 1;
|
||||||
chunkPos.y -= 1;
|
chunkPos.y -= 1;
|
||||||
} else if (blockInLocalChunk.y == BLOCKS_PER_CHUNK) {
|
} else if (blockInChunk.y == BLOCKS_PER_CHUNK) {
|
||||||
blockInChunk.y = 0;
|
blockInChunk.y = 0;
|
||||||
chunkPos.y += 1;
|
chunkPos.y += 1;
|
||||||
} else if (blockInLocalChunk.z == -1) {
|
} else if (blockInChunk.z == -1) {
|
||||||
blockInChunk.z = BLOCKS_PER_CHUNK - 1;
|
blockInChunk.z = BLOCKS_PER_CHUNK - 1;
|
||||||
chunkPos.z -= 1;
|
chunkPos.z -= 1;
|
||||||
} else if (blockInLocalChunk.z == BLOCKS_PER_CHUNK) {
|
} else if (blockInChunk.z == BLOCKS_PER_CHUNK) {
|
||||||
blockInChunk.z = 0;
|
blockInChunk.z = 0;
|
||||||
chunkPos.z += 1;
|
chunkPos.z += 1;
|
||||||
} else {
|
} else {
|
||||||
throw new AssertionError("Requested incorrent neighbor (" + blockInLocalChunk.x + "; "
|
throw new AssertionError(
|
||||||
+ blockInLocalChunk.y + "; " + blockInLocalChunk.z + ")");
|
"Requested incorrent neighbor ("
|
||||||
|
+ relBlockInLocalChunk.x + "; "
|
||||||
|
+ relBlockInLocalChunk.y + "; "
|
||||||
|
+ relBlockInLocalChunk.z + ")"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ChunkRender chunk = this.chunk.getWorld().getChunk(chunkPos);
|
ChunkRender chunk = this.chunk.getWorld().getChunk(chunkPos);
|
||||||
@ -357,8 +379,11 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
BlockOptimizedSurface bos = (BlockOptimizedSurface) block;
|
BlockOptimizedSurface bos = (BlockOptimizedSurface) block;
|
||||||
if (!bos.isOpaque(face))
|
RelFace rotatedFace = face.rotate(this.chunk.getUp(), chunk.getUp());
|
||||||
|
|
||||||
|
if (!bos.isOpaque(rotatedFace)) {
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -19,18 +19,45 @@
|
|||||||
package ru.windcorp.progressia.client.world.entity;
|
package ru.windcorp.progressia.client.world.entity;
|
||||||
|
|
||||||
import glm.vec._3.Vec3;
|
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.Renderable;
|
||||||
|
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
||||||
import ru.windcorp.progressia.common.world.entity.EntityData;
|
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 final EntityData data;
|
||||||
|
|
||||||
|
private long stateComputedForFrame = -1;
|
||||||
|
|
||||||
public EntityRenderable(EntityData data) {
|
public EntityRenderable(EntityData data) {
|
||||||
this.data = 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() {
|
public EntityData getData() {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@ -44,8 +71,42 @@ public abstract class EntityRenderable implements Renderable, GenericEntity {
|
|||||||
public String getId() {
|
public String getId() {
|
||||||
return getData().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);
|
output.set(0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,6 +39,7 @@ public class HumanoidModel extends NPedModel {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void applyTransform(Mat4 mat, NPedModel model) {
|
protected void applyTransform(Mat4 mat, NPedModel model) {
|
||||||
|
super.applyTransform(mat, model);
|
||||||
float phase = model.getWalkingFrequency() * model.getWalkingParameter() + animationOffset;
|
float phase = model.getWalkingFrequency() * model.getWalkingParameter() + animationOffset;
|
||||||
float value = sin(phase);
|
float value = sin(phase);
|
||||||
float amplitude = getSwingAmplitude((HumanoidModel) model) * model.getVelocityParameter();
|
float amplitude = getSwingAmplitude((HumanoidModel) model) * model.getVelocityParameter();
|
||||||
|
@ -15,14 +15,11 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.client.world.entity;
|
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.pow;
|
||||||
import static java.lang.Math.toRadians;
|
import static java.lang.Math.toRadians;
|
||||||
import static ru.windcorp.progressia.common.util.FloatMathUtil.normalizeAngle;
|
|
||||||
|
|
||||||
import glm.Glm;
|
import glm.Glm;
|
||||||
import glm.mat._4.Mat4;
|
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.Renderable;
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
||||||
import ru.windcorp.progressia.common.Units;
|
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;
|
import ru.windcorp.progressia.common.world.entity.EntityData;
|
||||||
|
|
||||||
public abstract class NPedModel extends EntityRenderable {
|
public abstract class NPedModel extends EntityRenderable {
|
||||||
@ -47,30 +47,34 @@ public abstract class NPedModel extends EntityRenderable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void render(ShapeRenderHelper renderer, NPedModel model) {
|
protected void render(
|
||||||
renderer.pushTransform().translate(translation);
|
ShapeRenderHelper renderer,
|
||||||
|
NPedModel model
|
||||||
|
) {
|
||||||
applyTransform(renderer.pushTransform(), model);
|
applyTransform(renderer.pushTransform(), model);
|
||||||
renderable.render(renderer);
|
renderable.render(renderer);
|
||||||
renderer.popTransform();
|
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() {
|
public Vec3 getTranslation() {
|
||||||
return translation;
|
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 static class Body extends BodyPart {
|
||||||
public Body(Renderable renderable) {
|
public Body(Renderable renderable) {
|
||||||
super(renderable, null);
|
super(renderable, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void applyTransform(Mat4 mat, NPedModel model) {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Head extends BodyPart {
|
public static class Head extends BodyPart {
|
||||||
@ -79,7 +83,13 @@ public abstract class NPedModel extends EntityRenderable {
|
|||||||
|
|
||||||
private final Vec3 viewPoint;
|
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);
|
super(renderable, joint);
|
||||||
this.maxYaw = (float) toRadians(maxYawDegrees);
|
this.maxYaw = (float) toRadians(maxYawDegrees);
|
||||||
this.maxPitch = (float) toRadians(maxPitchDegrees);
|
this.maxPitch = (float) toRadians(maxPitchDegrees);
|
||||||
@ -88,7 +98,8 @@ public abstract class NPedModel extends EntityRenderable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void applyTransform(Mat4 mat, NPedModel model) {
|
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() {
|
public Vec3 getViewPoint() {
|
||||||
@ -96,6 +107,8 @@ public abstract class NPedModel extends EntityRenderable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean flag;
|
||||||
|
|
||||||
protected final Body body;
|
protected final Body body;
|
||||||
protected final Head head;
|
protected final Head head;
|
||||||
|
|
||||||
@ -119,7 +132,9 @@ public abstract class NPedModel extends EntityRenderable {
|
|||||||
|
|
||||||
private float walkingFrequency;
|
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 headYaw;
|
||||||
private float headPitch;
|
private float headPitch;
|
||||||
|
|
||||||
@ -129,63 +144,121 @@ public abstract class NPedModel extends EntityRenderable {
|
|||||||
this.head = head;
|
this.head = head;
|
||||||
this.scale = scale;
|
this.scale = scale;
|
||||||
|
|
||||||
evaluateAngles();
|
computeRotations();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(ShapeRenderHelper renderer) {
|
protected void doRender(ShapeRenderHelper renderer) {
|
||||||
renderer.pushTransform().scale(scale).rotateZ(bodyYaw);
|
renderer.pushTransform().scale(scale).mul(bodyTransform);
|
||||||
renderBodyParts(renderer);
|
renderBodyParts(renderer);
|
||||||
renderer.popTransform();
|
renderer.popTransform();
|
||||||
|
|
||||||
accountForVelocity();
|
|
||||||
evaluateAngles();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void renderBodyParts(ShapeRenderHelper renderer) {
|
protected void renderBodyParts(ShapeRenderHelper renderer) {
|
||||||
body.render(renderer, this);
|
body.render(renderer, this);
|
||||||
head.render(renderer, this);
|
head.render(renderer, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void evaluateAngles() {
|
@Override
|
||||||
float globalYaw = normalizeAngle(getData().getYaw());
|
protected void update() {
|
||||||
|
advanceTime();
|
||||||
if (Float.isNaN(bodyYaw)) {
|
computeRotations();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void accountForVelocity() {
|
private void computeRotations() {
|
||||||
Vec3 horizontal = new Vec3(getData().getVelocity());
|
if (!bodyLookingAt.any()) {
|
||||||
horizontal.z = 0;
|
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();
|
velocity = horizontal.length();
|
||||||
|
|
||||||
evaluateVelocityCoeff();
|
computeVelocityParameter();
|
||||||
|
|
||||||
// TODO switch to world time
|
|
||||||
walkingParameter += velocity * GraphicsInterface.getFrameLength() * 1000;
|
walkingParameter += velocity * GraphicsInterface.getFrameLength() * 1000;
|
||||||
|
|
||||||
bodyYaw += velocityParameter * normalizeAngle((float) (atan2(horizontal.y, horizontal.x) - bodyYaw))
|
rotateBodyWithMovement(horizontal);
|
||||||
* min(1, GraphicsInterface.getFrameLength() * 10);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void evaluateVelocityCoeff() {
|
private void computeVelocityParameter() {
|
||||||
if (velocity > maxEffectiveVelocity) {
|
if (velocity > maxEffectiveVelocity) {
|
||||||
velocityParameter = 1;
|
velocityParameter = 1;
|
||||||
} else {
|
} 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
|
@Override
|
||||||
public void getViewPoint(Vec3 output) {
|
protected void doGetViewPoint(Vec3 output) {
|
||||||
Mat4 m = new Mat4();
|
Mat4 m = new Mat4();
|
||||||
Vec4 v = new Vec4();
|
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);
|
v.set(head.getViewPoint(), 1);
|
||||||
m.mul(v);
|
m.mul(v);
|
||||||
@ -213,9 +313,9 @@ public abstract class NPedModel extends EntityRenderable {
|
|||||||
public Head getHead() {
|
public Head getHead() {
|
||||||
return head;
|
return head;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getBodyYaw() {
|
public Vec3 getBodyLookingAt() {
|
||||||
return bodyYaw;
|
return bodyLookingAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getHeadYaw() {
|
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
|
* 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
|
* effects that depend on speed.
|
||||||
* moving and 1 when it's moving "fast".
|
* This parameter is 0 when the entity is not moving and 1 when it's moving
|
||||||
|
* "fast".
|
||||||
*
|
*
|
||||||
* @return velocity parameter
|
* @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
|
* Returns a number that can be used to parameterize animation effects that
|
||||||
* depend on walking. This parameter increases when the entity moves (e.g.
|
* depend on walking.
|
||||||
* this can be total traveled distance).
|
* This parameter increases when the entity moves (e.g. this can be total
|
||||||
|
* traveled distance).
|
||||||
*
|
*
|
||||||
* @return walking parameter
|
* @return walking parameter
|
||||||
*/
|
*/
|
||||||
|
@ -40,6 +40,7 @@ public class QuadripedModel extends NPedModel {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void applyTransform(Mat4 mat, NPedModel model) {
|
protected void applyTransform(Mat4 mat, NPedModel model) {
|
||||||
|
super.applyTransform(mat, model);
|
||||||
float phase = model.getWalkingFrequency() * model.getWalkingParameter() + animationOffset;
|
float phase = model.getWalkingFrequency() * model.getWalkingParameter() + animationOffset;
|
||||||
float value = sin(phase);
|
float value = sin(phase);
|
||||||
float amplitude = ((QuadripedModel) model).getWalkingSwing() * model.getVelocityParameter();
|
float amplitude = ((QuadripedModel) model).getWalkingSwing() * model.getVelocityParameter();
|
||||||
|
@ -15,29 +15,24 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.client.world.tile;
|
package ru.windcorp.progressia.client.world.tile;
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
|
||||||
import glm.vec._3.i.Vec3i;
|
import glm.vec._3.i.Vec3i;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||||
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizer;
|
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizer;
|
||||||
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
|
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
|
||||||
import ru.windcorp.progressia.common.world.ChunkData;
|
import ru.windcorp.progressia.common.world.DefaultChunkData;
|
||||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
import ru.windcorp.progressia.common.world.generic.TileGeneric;
|
||||||
import ru.windcorp.progressia.common.world.generic.GenericTile;
|
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) {
|
public TileRender(String id) {
|
||||||
super(id);
|
super(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void render(ShapeRenderHelper renderer, BlockFace face) {
|
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk, RelFace face) {
|
||||||
throw new UnsupportedOperationException("TileRender.render() not implemented in " + this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk, BlockFace face) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,8 +20,8 @@ package ru.windcorp.progressia.client.world.tile;
|
|||||||
import glm.vec._3.i.Vec3i;
|
import glm.vec._3.i.Vec3i;
|
||||||
import ru.windcorp.progressia.client.graphics.model.EmptyModel;
|
import ru.windcorp.progressia.client.graphics.model.EmptyModel;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||||
import ru.windcorp.progressia.common.world.ChunkData;
|
import ru.windcorp.progressia.common.world.DefaultChunkData;
|
||||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
import ru.windcorp.progressia.common.world.rels.RelFace;
|
||||||
|
|
||||||
public class TileRenderNone extends TileRender {
|
public class TileRenderNone extends TileRender {
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ public class TileRenderNone extends TileRender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk, BlockFace face) {
|
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk, RelFace face) {
|
||||||
return EmptyModel.getInstance();
|
return EmptyModel.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
package ru.windcorp.progressia.client.world.tile;
|
package ru.windcorp.progressia.client.world.tile;
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
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 {
|
public class TileRenderOpaqueSurface extends TileRenderSurface {
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ public class TileRenderOpaqueSurface extends TileRenderSurface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isOpaque(BlockFace face) {
|
public boolean isOpaque(RelFace face) {
|
||||||
return true;
|
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
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.client.world.tile;
|
package ru.windcorp.progressia.client.world.tile;
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.world.ChunkRender;
|
import java.util.AbstractList;
|
||||||
import ru.windcorp.progressia.common.world.generic.GenericTileStack;
|
|
||||||
import ru.windcorp.progressia.common.world.tile.TileDataStack;
|
|
||||||
|
|
||||||
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();
|
public abstract TileDataStack getData();
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.client.world.tile;
|
package ru.windcorp.progressia.client.world.tile;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
@ -25,16 +25,17 @@ import glm.vec._3.i.Vec3i;
|
|||||||
import glm.vec._4.Vec4;
|
import glm.vec._4.Vec4;
|
||||||
import ru.windcorp.progressia.client.graphics.Colors;
|
import ru.windcorp.progressia.client.graphics.Colors;
|
||||||
import ru.windcorp.progressia.client.graphics.backend.Usage;
|
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.Faces;
|
import ru.windcorp.progressia.client.graphics.model.ShapeParts;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Shape;
|
import ru.windcorp.progressia.client.graphics.model.Shape;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||||
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
|
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
|
||||||
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSurface.TileOptimizedSurface;
|
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSurface.TileOptimizedSurface;
|
||||||
import ru.windcorp.progressia.common.util.Vectors;
|
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.block.BlockFace;
|
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
||||||
|
import ru.windcorp.progressia.common.world.rels.RelFace;
|
||||||
|
|
||||||
public abstract class TileRenderSurface extends TileRender implements TileOptimizedSurface {
|
public abstract class TileRenderSurface extends TileRender implements TileOptimizedSurface {
|
||||||
|
|
||||||
@ -44,36 +45,53 @@ public abstract class TileRenderSurface extends TileRender implements TileOptimi
|
|||||||
super(id);
|
super(id);
|
||||||
this.texture = texture;
|
this.texture = texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TileRenderSurface(String id) {
|
public TileRenderSurface(String id) {
|
||||||
this(id, null);
|
this(id, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Texture getTexture(BlockFace blockFace) {
|
public Texture getTexture(RelFace blockFace) {
|
||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vec4 getColorMultiplier(BlockFace blockFace) {
|
public Vec4 getColorMultiplier(RelFace blockFace) {
|
||||||
return Colors.WHITE;
|
return Colors.WHITE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void getFaces(ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace, boolean inner,
|
public final void getShapeParts(
|
||||||
Consumer<Face> output, Vec3 offset) {
|
DefaultChunkData chunk, Vec3i relBlockInChunk, RelFace blockFace,
|
||||||
output.accept(createFace(chunk, blockInChunk, blockFace, inner, offset));
|
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) {
|
private ShapePart createFace(
|
||||||
return Faces.createBlockFace(WorldRenderProgram.getDefault(), getTexture(blockFace),
|
DefaultChunkData chunk, Vec3i blockInChunk, RelFace blockFace,
|
||||||
getColorMultiplier(blockFace), offset, blockFace, inner);
|
boolean inner,
|
||||||
|
Vec3 offset
|
||||||
|
) {
|
||||||
|
return ShapeParts.createBlockFace(
|
||||||
|
WorldRenderProgram.getDefault(),
|
||||||
|
getTexture(blockFace),
|
||||||
|
getColorMultiplier(blockFace),
|
||||||
|
offset,
|
||||||
|
blockFace.resolve(AbsFace.POS_Z),
|
||||||
|
inner
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace) {
|
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk, RelFace blockFace) {
|
||||||
return new Shape(Usage.STATIC, WorldRenderProgram.getDefault(),
|
return new Shape(
|
||||||
|
Usage.STATIC,
|
||||||
createFace(chunk, blockInChunk, blockFace, false, Vectors.ZERO_3),
|
WorldRenderProgram.getDefault(),
|
||||||
createFace(chunk, blockInChunk, blockFace, true, Vectors.ZERO_3));
|
|
||||||
|
createFace(chunk, blockInChunk, blockFace, false, Vectors.ZERO_3),
|
||||||
|
createFace(chunk, blockInChunk, blockFace, true, Vectors.ZERO_3)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
package ru.windcorp.progressia.client.world.tile;
|
package ru.windcorp.progressia.client.world.tile;
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
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 {
|
public class TileRenderTransparentSurface extends TileRenderSurface {
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ public class TileRenderTransparentSurface extends TileRenderSurface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isOpaque(BlockFace face) {
|
public boolean isOpaque(RelFace face) {
|
||||||
return false;
|
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;
|
package ru.windcorp.progressia.common.collision;
|
||||||
|
|
||||||
import glm.vec._3.Vec3;
|
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 {
|
public interface AABBoid extends CollisionModel {
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ public interface AABBoid extends CollisionModel {
|
|||||||
|
|
||||||
void getSize(Vec3 output);
|
void getSize(Vec3 output);
|
||||||
|
|
||||||
default Wall getWall(BlockFace face) {
|
default Wall getWall(AbsFace face) {
|
||||||
return getWall(face.getId());
|
return getWall(face.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ package ru.windcorp.progressia.common.collision;
|
|||||||
|
|
||||||
import glm.vec._3.Vec3;
|
import glm.vec._3.Vec3;
|
||||||
import ru.windcorp.progressia.common.util.Vectors;
|
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 {
|
public class TranslatedAABB implements AABBoid {
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ public class TranslatedAABB implements AABBoid {
|
|||||||
private AABBoid parent;
|
private AABBoid parent;
|
||||||
private final Vec3 translation = new Vec3();
|
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) {
|
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.Vec3;
|
||||||
import glm.vec._3.i.Vec3i;
|
import glm.vec._3.i.Vec3i;
|
||||||
import ru.windcorp.progressia.common.util.LowOverheadCache;
|
import ru.windcorp.progressia.common.util.LowOverheadCache;
|
||||||
import ru.windcorp.progressia.common.world.WorldData;
|
import ru.windcorp.progressia.common.world.DefaultWorldData;
|
||||||
|
|
||||||
public class WorldCollisionHelper {
|
public class WorldCollisionHelper {
|
||||||
|
|
||||||
@ -79,7 +79,7 @@ public class WorldCollisionHelper {
|
|||||||
* @param maxTime
|
* @param maxTime
|
||||||
* maximum collision time
|
* 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.forEach(blockModelCache::release);
|
||||||
activeBlockModels.clear();
|
activeBlockModels.clear();
|
||||||
CollisionPathComputer.forEveryBlockInCollisionPath(collideable, maxTime,
|
CollisionPathComputer.forEveryBlockInCollisionPath(collideable, maxTime,
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.common.collision.colliders;
|
package ru.windcorp.progressia.common.collision.colliders;
|
||||||
|
|
||||||
import glm.mat._3.Mat3;
|
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.collision.colliders.Collider.Collision;
|
||||||
import ru.windcorp.progressia.common.util.Matrices;
|
import ru.windcorp.progressia.common.util.Matrices;
|
||||||
import ru.windcorp.progressia.common.util.Vectors;
|
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 {
|
class AABBoidCollider {
|
||||||
|
|
||||||
static Collider.Collision computeModelCollision(Collideable aBody, Collideable bBody, AABBoid aModel,
|
static Collider.Collision computeModelCollision(
|
||||||
AABBoid bModel, float tickLength, ColliderWorkspace workspace) {
|
Collideable aBody,
|
||||||
|
Collideable bBody,
|
||||||
|
AABBoid aModel,
|
||||||
|
AABBoid bModel,
|
||||||
|
float tickLength,
|
||||||
|
ColliderWorkspace workspace
|
||||||
|
) {
|
||||||
Collideable obstacleBody = bBody;
|
Collideable obstacleBody = bBody;
|
||||||
Collideable colliderBody = aBody;
|
Collideable colliderBody = aBody;
|
||||||
AABBoid obstacleModel = bModel;
|
AABBoid obstacleModel = bModel;
|
||||||
@ -44,11 +50,18 @@ class AABBoidCollider {
|
|||||||
computeCollisionVelocity(collisionVelocity, obstacleBody, colliderBody);
|
computeCollisionVelocity(collisionVelocity, obstacleBody, colliderBody);
|
||||||
|
|
||||||
// For every wall of collision space
|
// 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);
|
Wall wall = originCollisionSpace.getWall(i);
|
||||||
|
|
||||||
Collision collision = computeWallCollision(wall, colliderModel, collisionVelocity, tickLength, workspace,
|
Collision collision = computeWallCollision(
|
||||||
aBody, bBody);
|
wall,
|
||||||
|
colliderModel,
|
||||||
|
collisionVelocity,
|
||||||
|
tickLength,
|
||||||
|
workspace,
|
||||||
|
aBody,
|
||||||
|
bBody
|
||||||
|
);
|
||||||
|
|
||||||
// Update result
|
// Update result
|
||||||
if (collision != null) {
|
if (collision != null) {
|
||||||
@ -73,7 +86,11 @@ class AABBoidCollider {
|
|||||||
return result;
|
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 obstacleVelocity = Vectors.grab3();
|
||||||
Vec3 colliderVelocity = Vectors.grab3();
|
Vec3 colliderVelocity = Vectors.grab3();
|
||||||
|
|
||||||
@ -105,31 +122,60 @@ class AABBoidCollider {
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @formatter:off
|
||||||
/*
|
/*
|
||||||
* Here we determine whether a collision has actually happened, and if it
|
* Here we determine whether a collision has actually happened, and if it did, at what moment.
|
||||||
* did, at what moment. The basic idea is to compute the moment of collision
|
*
|
||||||
* and impact coordinates in wall coordinate space. Then, we can check
|
* The basic idea is to compute the moment of collision and impact coordinates in wall coordinate space.
|
||||||
* impact coordinates to determine if we actually hit the wall or flew by
|
* Then, we can check impact coordinates to determine if we actually hit the wall or flew by and then
|
||||||
* and then check time to make sure the collision is not too far in the
|
* check time to make sure the collision is not too far in the future and not in the past.
|
||||||
* 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.
|
* DETAILED EXPLANATION:
|
||||||
* 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 +
|
* Consider a surface defined by an origin r_wall and two noncollinear nonzero vectors w and h.
|
||||||
* v * t and ______ _ _ r_wall + w * x + h * y describe the same location
|
* Consider a line defined by an origin r_line and a nonzero vector v.
|
||||||
* (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,
|
* Then, a collision occurs if there exist x, y and t such that
|
||||||
* ______ _ ______ _ _ 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 + v * t
|
||||||
* = 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.
|
* and
|
||||||
* ⎣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
|
* r_wall + w * x + h * y
|
||||||
* matrix is not invertible or any of the conditions are not met, no
|
*
|
||||||
* collision happened. If all conditions are satisfied, then the moment of
|
* describe the same location (indeed, this corresponds to a collision at moment t0 + t
|
||||||
* impact is 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,
|
// @formatter:on
|
||||||
float tickLength, ColliderWorkspace workspace, Collideable aBody, Collideable bBody) {
|
private static Collision computeWallCollision(
|
||||||
|
Wall obstacleWall,
|
||||||
|
AABBoid colliderModel,
|
||||||
|
Vec3 collisionVelocity,
|
||||||
|
float tickLength,
|
||||||
|
ColliderWorkspace workspace,
|
||||||
|
Collideable aBody,
|
||||||
|
Collideable bBody
|
||||||
|
) {
|
||||||
Vec3 w = Vectors.grab3();
|
Vec3 w = Vectors.grab3();
|
||||||
Vec3 h = Vectors.grab3();
|
Vec3 h = Vectors.grab3();
|
||||||
Vec3 v = Vectors.grab3();
|
Vec3 v = Vectors.grab3();
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.common.collision.colliders;
|
package ru.windcorp.progressia.common.collision.colliders;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@ -27,7 +27,7 @@ import glm.vec._3.Vec3;
|
|||||||
import ru.windcorp.progressia.common.collision.*;
|
import ru.windcorp.progressia.common.collision.*;
|
||||||
import ru.windcorp.progressia.common.util.LowOverheadCache;
|
import ru.windcorp.progressia.common.util.LowOverheadCache;
|
||||||
import ru.windcorp.progressia.common.util.Vectors;
|
import ru.windcorp.progressia.common.util.Vectors;
|
||||||
import ru.windcorp.progressia.common.world.WorldData;
|
import ru.windcorp.progressia.common.world.DefaultWorldData;
|
||||||
|
|
||||||
public class Collider {
|
public class Collider {
|
||||||
|
|
||||||
@ -36,15 +36,19 @@ public class Collider {
|
|||||||
/**
|
/**
|
||||||
* Dear Princess Celestia,
|
* Dear Princess Celestia,
|
||||||
* <p>
|
* <p>
|
||||||
* When {@linkplain #advanceTime(Collection, Collision, WorldData, float)
|
* When {@linkplain #advanceTime(Collection, Collision, DefaultWorldData, float)
|
||||||
* advancing time}, time step for all entities <em>except</em> currently
|
* advancing time},
|
||||||
* colliding bodies is the current collisions's timestamp relative to now.
|
* time step for all entities <em>except</em> currently colliding bodies is
|
||||||
* However, currently colliding bodies (Collision.a and Collision.b) have a
|
* the current
|
||||||
* smaller time step. This is done to make sure they don't intersect due to
|
* collisions's timestamp relative to now. However, currently colliding
|
||||||
* rounding errors.
|
* 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>
|
* <p>
|
||||||
* Today I learned that bad code has nothing to do with friendship, although
|
* 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>
|
* <p>
|
||||||
* Your faithful student,<br />
|
* Your faithful student,<br />
|
||||||
* Kostyl.
|
* Kostyl.
|
||||||
@ -55,14 +59,21 @@ public class Collider {
|
|||||||
* 1f
|
* 1f
|
||||||
*/;
|
*/;
|
||||||
|
|
||||||
public static void performCollisions(List<? extends Collideable> colls, WorldData world, float tickLength,
|
public static void performCollisions(
|
||||||
ColliderWorkspace workspace) {
|
List<? extends Collideable> colls,
|
||||||
|
DefaultWorldData world,
|
||||||
|
float tickLength,
|
||||||
|
ColliderWorkspace workspace
|
||||||
|
) {
|
||||||
int collisionCount = 0;
|
int collisionCount = 0;
|
||||||
int maxCollisions = colls.size() * MAX_COLLISIONS_PER_ENTITY;
|
int maxCollisions = colls.size() * MAX_COLLISIONS_PER_ENTITY;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (collisionCount > maxCollisions) {
|
if (collisionCount > maxCollisions) {
|
||||||
LogManager.getLogger().warn("Attempted to handle more than {} collisions", maxCollisions);
|
LogManager.getLogger().warn(
|
||||||
|
"Attempted to handle more than {} collisions",
|
||||||
|
maxCollisions
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,8 +93,12 @@ public class Collider {
|
|||||||
advanceTime(colls, null, world, tickLength);
|
advanceTime(colls, null, world, tickLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Collision getFirstCollision(List<? extends Collideable> colls, float tickLength, WorldData world,
|
private static Collision getFirstCollision(
|
||||||
ColliderWorkspace workspace) {
|
List<? extends Collideable> colls,
|
||||||
|
float tickLength,
|
||||||
|
DefaultWorldData world,
|
||||||
|
ColliderWorkspace workspace
|
||||||
|
) {
|
||||||
Collision result = null;
|
Collision result = null;
|
||||||
Collideable worldColl = workspace.worldCollisionHelper.getCollideable();
|
Collideable worldColl = workspace.worldCollisionHelper.getCollideable();
|
||||||
|
|
||||||
@ -93,7 +108,10 @@ public class Collider {
|
|||||||
|
|
||||||
tuneWorldCollisionHelper(a, tickLength, world, workspace);
|
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) {
|
for (int j = i + 1; j < colls.size(); ++j) {
|
||||||
Collideable b = colls.get(j);
|
Collideable b = colls.get(j);
|
||||||
@ -105,42 +123,81 @@ public class Collider {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void tuneWorldCollisionHelper(Collideable coll, float tickLength, WorldData world,
|
private static void tuneWorldCollisionHelper(
|
||||||
ColliderWorkspace workspace) {
|
Collideable coll,
|
||||||
|
float tickLength,
|
||||||
|
DefaultWorldData world,
|
||||||
|
ColliderWorkspace workspace
|
||||||
|
) {
|
||||||
WorldCollisionHelper wch = workspace.worldCollisionHelper;
|
WorldCollisionHelper wch = workspace.worldCollisionHelper;
|
||||||
wch.tuneToCollideable(world, coll, tickLength);
|
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 aModel = a.getCollisionModel();
|
||||||
CollisionModel bModel = b.getCollisionModel();
|
CollisionModel bModel = b.getCollisionModel();
|
||||||
return getCollision(a, b, aModel, bModel, tickLength, workspace);
|
return getCollision(a, b, aModel, bModel, tickLength, workspace);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Collision getCollision(Collideable aBody, Collideable bBody, CollisionModel aModel, CollisionModel bModel,
|
static Collision getCollision(
|
||||||
float tickLength, ColliderWorkspace workspace) {
|
Collideable aBody,
|
||||||
|
Collideable bBody,
|
||||||
|
CollisionModel aModel,
|
||||||
|
CollisionModel bModel,
|
||||||
|
float tickLength,
|
||||||
|
ColliderWorkspace workspace
|
||||||
|
) {
|
||||||
if (aModel instanceof AABBoid && bModel instanceof AABBoid) {
|
if (aModel instanceof AABBoid && bModel instanceof AABBoid) {
|
||||||
return AABBoidCollider.computeModelCollision(aBody, bBody, (AABBoid) aModel, (AABBoid) bModel, tickLength,
|
return AABBoidCollider.computeModelCollision(
|
||||||
workspace);
|
aBody,
|
||||||
|
bBody,
|
||||||
|
(AABBoid) aModel,
|
||||||
|
(AABBoid) bModel,
|
||||||
|
tickLength,
|
||||||
|
workspace
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aModel instanceof CompoundCollisionModel) {
|
if (aModel instanceof CompoundCollisionModel) {
|
||||||
return AnythingWithCompoundCollider.computeModelCollision(aBody, bBody, (CompoundCollisionModel) aModel,
|
return AnythingWithCompoundCollider.computeModelCollision(
|
||||||
bModel, tickLength, workspace);
|
aBody,
|
||||||
|
bBody,
|
||||||
|
(CompoundCollisionModel) aModel,
|
||||||
|
bModel,
|
||||||
|
tickLength,
|
||||||
|
workspace
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bModel instanceof CompoundCollisionModel) {
|
if (bModel instanceof CompoundCollisionModel) {
|
||||||
return AnythingWithCompoundCollider.computeModelCollision(bBody, aBody, (CompoundCollisionModel) bModel,
|
return AnythingWithCompoundCollider.computeModelCollision(
|
||||||
aModel, tickLength, workspace);
|
bBody,
|
||||||
|
aBody,
|
||||||
|
(CompoundCollisionModel) bModel,
|
||||||
|
aModel,
|
||||||
|
tickLength,
|
||||||
|
workspace
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new UnsupportedOperationException(
|
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);
|
advanceTime(colls, collision, world, collision.time);
|
||||||
|
|
||||||
boolean doNotHandle = false;
|
boolean doNotHandle = false;
|
||||||
@ -155,40 +212,72 @@ public class Collider {
|
|||||||
handlePhysics(collision);
|
handlePhysics(collision);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @formatter:off
|
||||||
/*
|
/*
|
||||||
* Here we compute the change in body velocities due to a collision. We make
|
* Here we compute the change in body velocities due to a collision.
|
||||||
* the following simplifications: 1) The bodies are perfectly rigid; 2) The
|
*
|
||||||
* collision is perfectly inelastic (no bouncing); 3) The bodies are
|
* We make the following simplifications:
|
||||||
* spherical; 4) No tangential friction exists (bodies do not experience
|
* 1) The bodies are perfectly rigid;
|
||||||
* friction when sliding against each other); 5) Velocities are not
|
* 2) The collision is perfectly inelastic
|
||||||
* relativistic. Angular momentum is ignored per 3) and 4), e.g. when
|
* (no bouncing);
|
||||||
* something pushes an end of a long stick, the stick does not rotate.
|
* 3) The bodies are spherical;
|
||||||
* DETAILED EXPLANATION: Two spherical (sic) bodies, a and b, experience a
|
* 4) No tangential friction exists
|
||||||
* perfectly inelastic collision along a unit vector _ _ _ _ _ n = (w ⨯ h)
|
* (bodies do not experience friction when sliding against each other);
|
||||||
* / (|w ⨯ h|), _ _ where w and h are two noncollinear nonzero vectors on
|
* 5) Velocities are not relativistic.
|
||||||
* the dividing plane. ___ ___ Body masses and velocities are M_a, M_b and
|
*
|
||||||
* v_a, v_b, respectively. ___ ___ After the collision desired velocities
|
* Angular momentum is ignored per 3) and 4),
|
||||||
* are u_a and u_b, respectively. _ (Notation convention: suffix 'n' denotes
|
* e.g. when something pushes an end of a long stick, the stick does not rotate.
|
||||||
* a vector projection onto vector n, and suffix 't' denotes a vector
|
*
|
||||||
* projection onto the dividing plane.) Consider the law of conservation of
|
* DETAILED EXPLANATION:
|
||||||
* momentum for axis n and the dividing plane: ____________ ____________
|
*
|
||||||
* ________________ n: ⎧ p_a_before_n + p_b_before_n = p_common_after_n;
|
* Two spherical (sic) bodies, a and b, experience a perfectly inelastic collision
|
||||||
* ⎨ ___________ ____________ t: ⎩ p_i_after_t = p_i_before_t for any i
|
* along a unit vector
|
||||||
* 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,
|
* n = (w ⨯ h) / (|w ⨯ h|),
|
||||||
* 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 * (
|
* where w and h are two noncollinear nonzero vectors on the dividing plane.
|
||||||
* 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
|
* Body masses and velocities are M_a, M_b and v_a, v_b, respectively.
|
||||||
* ⋅ 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
|
* After the collision desired velocities are u_a and u_b, respectively.
|
||||||
* masses m_i permits a convenient generalization of the algorithm for
|
* _
|
||||||
* infinite masses, signifying masses "significantly greater" than finite
|
* (Notation convention: suffix 'n' denotes a vector projection onto vector n,
|
||||||
* masses: 1) If both M_a and M_b are finite, let m_i = M_i / (M_a + M_b)
|
* and suffix 't' denotes a vector projection onto the dividing plane.)
|
||||||
* 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
|
* Consider the law of conservation of momentum for axis n and the dividing plane:
|
||||||
* in {a, b}.
|
* ____________ ____________ ________________
|
||||||
|
* 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) {
|
private static void handlePhysics(Collision collision) {
|
||||||
// Fuck JGLM
|
// Fuck JGLM
|
||||||
Vec3 n = Vectors.grab3();
|
Vec3 n = Vectors.grab3();
|
||||||
@ -250,7 +339,12 @@ public class Collider {
|
|||||||
Vectors.release(du_b);
|
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;
|
final float margin = 1e-4f;
|
||||||
|
|
||||||
Vec3 displacement = Vectors.grab3();
|
Vec3 displacement = Vectors.grab3();
|
||||||
@ -264,8 +358,12 @@ public class Collider {
|
|||||||
Vectors.release(displacement);
|
Vectors.release(displacement);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void advanceTime(Collection<? extends Collideable> colls, Collision exceptions, WorldData world,
|
private static void advanceTime(
|
||||||
float step) {
|
Collection<? extends Collideable> colls,
|
||||||
|
Collision exceptions,
|
||||||
|
DefaultWorldData world,
|
||||||
|
float step
|
||||||
|
) {
|
||||||
world.advanceTime(step);
|
world.advanceTime(step);
|
||||||
|
|
||||||
Vec3 tmp = Vectors.grab3();
|
Vec3 tmp = Vectors.grab3();
|
||||||
@ -333,12 +431,17 @@ public class Collider {
|
|||||||
public final Vec3 wallHeight = new Vec3();
|
public final Vec3 wallHeight = new Vec3();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Time offset from the start of the tick. 0 means right now, tickLength
|
* Time offset from the start of the tick.
|
||||||
* means at the end of the tick.
|
* 0 means right now, tickLength means at the end of the tick.
|
||||||
*/
|
*/
|
||||||
public float time;
|
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.a = a;
|
||||||
this.b = b;
|
this.b = b;
|
||||||
wall.getWidth(wallWidth);
|
wall.getWidth(wallWidth);
|
||||||
|
@ -29,11 +29,14 @@ import com.google.common.util.concurrent.MoreExecutors;
|
|||||||
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
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
|
* non-async {@link EventBus} with both a custom identifier and a custom
|
||||||
* exception handler. Which is a shame. Guava maintainers know about the issue
|
* exception handler. Which is a shame. Guava maintainers know about the issue
|
||||||
* but have rejected solutions multiple times <em>without a clearly stated
|
* but have rejected solutions multiple times <em>without a clearly stated
|
||||||
* reason</em>; looks like some dirty reflection will have to do.
|
* 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
|
* @author javapony
|
||||||
*/
|
*/
|
||||||
@ -46,22 +49,30 @@ public class GuavaEventBusHijacker {
|
|||||||
try {
|
try {
|
||||||
Class<?> dispatcherClass = Class.forName("com.google.common.eventbus.Dispatcher");
|
Class<?> dispatcherClass = Class.forName("com.google.common.eventbus.Dispatcher");
|
||||||
|
|
||||||
THE_CONSTRUCTOR = EventBus.class.getDeclaredConstructor(String.class, Executor.class, dispatcherClass,
|
THE_CONSTRUCTOR = EventBus.class.getDeclaredConstructor(
|
||||||
SubscriberExceptionHandler.class);
|
String.class,
|
||||||
|
Executor.class,
|
||||||
|
dispatcherClass,
|
||||||
|
SubscriberExceptionHandler.class
|
||||||
|
);
|
||||||
THE_CONSTRUCTOR.setAccessible(true);
|
THE_CONSTRUCTOR.setAccessible(true);
|
||||||
|
|
||||||
DISPATCHER__PER_THREAD_DISPATCH_QUEUE = dispatcherClass.getDeclaredMethod("perThreadDispatchQueue");
|
DISPATCHER__PER_THREAD_DISPATCH_QUEUE = dispatcherClass.getDeclaredMethod("perThreadDispatchQueue");
|
||||||
DISPATCHER__PER_THREAD_DISPATCH_QUEUE.setAccessible(true);
|
DISPATCHER__PER_THREAD_DISPATCH_QUEUE.setAccessible(true);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw CrashReports.report(e,
|
throw CrashReports
|
||||||
"Something went horribly wrong when setting up EventBus hijacking. Has Guava updated?");
|
.report(e, "Something went horribly wrong when setting up EventBus hijacking. Has Guava updated?");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static EventBus newEventBus(String identifier, SubscriberExceptionHandler exceptionHandler) {
|
public static EventBus newEventBus(String identifier, SubscriberExceptionHandler exceptionHandler) {
|
||||||
try {
|
try {
|
||||||
return THE_CONSTRUCTOR.newInstance(identifier, MoreExecutors.directExecutor(),
|
return THE_CONSTRUCTOR.newInstance(
|
||||||
DISPATCHER__PER_THREAD_DISPATCH_QUEUE.invoke(null), exceptionHandler);
|
identifier,
|
||||||
|
MoreExecutors.directExecutor(),
|
||||||
|
DISPATCHER__PER_THREAD_DISPATCH_QUEUE.invoke(null),
|
||||||
|
exceptionHandler
|
||||||
|
);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw CrashReports.report(e, "Something went horribly wrong when hijacking EventBus. Has Guava updated?");
|
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
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* 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
|
@FunctionalInterface
|
||||||
public interface StateChange<T> {
|
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
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.common.util;
|
package ru.windcorp.progressia.common.util;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import glm.Glm;
|
||||||
|
import glm.mat._3.Mat3;
|
||||||
import glm.mat._4.Mat4;
|
import glm.mat._4.Mat4;
|
||||||
import glm.vec._2.Vec2;
|
import glm.vec._2.Vec2;
|
||||||
import glm.vec._2.d.Vec2d;
|
import glm.vec._2.d.Vec2d;
|
||||||
@ -36,8 +38,50 @@ public class VectorUtil {
|
|||||||
public static enum Axis {
|
public static enum Axis {
|
||||||
X, Y, Z, W;
|
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();
|
Vec3i cursor = Vectors.grab3i();
|
||||||
|
|
||||||
for (int x = x0; x < x1; ++x) {
|
for (int x = x0; x < x1; ++x) {
|
||||||
@ -52,12 +96,23 @@ public class VectorUtil {
|
|||||||
Vectors.release(cursor);
|
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);
|
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,
|
public static void iterateCuboidAround(
|
||||||
Consumer<? super Vec3i> action) {
|
int cx,
|
||||||
|
int cy,
|
||||||
|
int cz,
|
||||||
|
int dx,
|
||||||
|
int dy,
|
||||||
|
int dz,
|
||||||
|
Consumer<? super Vec3i> action
|
||||||
|
) {
|
||||||
if (dx < 0)
|
if (dx < 0)
|
||||||
throw new IllegalArgumentException("dx " + dx + " is negative");
|
throw new IllegalArgumentException("dx " + dx + " is negative");
|
||||||
if (dy < 0)
|
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);
|
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);
|
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);
|
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);
|
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 vec4 = Vectors.grab4();
|
||||||
vec4.set(in, 1f);
|
vec4.set(in, 1f);
|
||||||
|
|
||||||
@ -99,27 +172,206 @@ public class VectorUtil {
|
|||||||
|
|
||||||
out.set(vec4.x, vec4.y, vec4.z);
|
out.set(vec4.x, vec4.y, vec4.z);
|
||||||
Vectors.release(vec4);
|
Vectors.release(vec4);
|
||||||
|
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void applyMat4(Vec3 inOut, Mat4 mat) {
|
public static Vec3 applyMat4(Vec3 inOut, Mat4 mat) {
|
||||||
Vec4 vec4 = Vectors.grab4();
|
return applyMat4(inOut, mat, inOut);
|
||||||
vec4.set(inOut, 1f);
|
}
|
||||||
|
|
||||||
mat.mul(vec4);
|
public static Vec3 rotate(Vec3 in, Vec3 axis, float angle, Vec3 out) {
|
||||||
|
if (out == null) {
|
||||||
inOut.set(vec4.x, vec4.y, vec4.z);
|
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) {
|
public static Vec3 linearCombination(
|
||||||
output.set(va.x * ka + vb.x * kb, va.y * ka + vb.y * kb, va.z * ka + vb.z * kb);
|
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;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Vec3 linearCombination(Vec3 va, float ka, Vec3 vb, float kb, Vec3 vc, float kc, Vec3 output) {
|
public static Vec3 linearCombination(
|
||||||
output.set(va.x * ka + vb.x * kb + vc.x * kc, va.y * ka + vb.y * kb + vc.y * kc,
|
Vec3 va,
|
||||||
va.z * ka + vb.z * kb + vc.z * kc);
|
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;
|
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) {
|
public static float get(Vec2 v, Axis a) {
|
||||||
switch (a) {
|
switch (a) {
|
||||||
|
@ -22,11 +22,27 @@ import com.google.common.eventbus.EventBus;
|
|||||||
|
|
||||||
import ru.windcorp.progressia.common.hacks.GuavaEventBusHijacker;
|
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 {
|
public class ReportingEventBus {
|
||||||
|
|
||||||
private 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) {
|
public static EventBus create(String identifier) {
|
||||||
return GuavaEventBusHijacker.newEventBus(identifier, (throwable, context) -> {
|
return GuavaEventBusHijacker.newEventBus(identifier, (throwable, context) -> {
|
||||||
// Makes sense to append identifier to messageFormat because
|
// 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 glm.vec._3.i.Vec3i;
|
||||||
import ru.windcorp.progressia.common.util.VectorUtil;
|
import ru.windcorp.progressia.common.util.VectorUtil;
|
||||||
import ru.windcorp.progressia.common.util.VectorUtil.Axis;
|
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.*;
|
import static java.lang.Math.*;
|
||||||
|
|
||||||
@ -34,14 +34,22 @@ public class BlockRay {
|
|||||||
private float distance;
|
private float distance;
|
||||||
|
|
||||||
private final Vec3i block = new Vec3i();
|
private final Vec3i block = new Vec3i();
|
||||||
private BlockFace currentFace = null;
|
private AbsFace currentFace = null;
|
||||||
|
|
||||||
private boolean isValid = false;
|
private boolean isValid = false;
|
||||||
|
|
||||||
public void start(Vec3 position, Vec3 direction) {
|
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");
|
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;
|
isValid = true;
|
||||||
this.position.set(position).sub(0.5f); // Make sure lattice points are
|
this.position.set(position).sub(0.5f); // Make sure lattice points are
|
||||||
@ -75,16 +83,14 @@ public class BlockRay {
|
|||||||
tMin = tz;
|
tMin = tz;
|
||||||
axis = Axis.Z;
|
axis = Axis.Z;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert tMin > 0 : "tMin is not positive (" + tMin + ")";
|
||||||
|
|
||||||
// block.(axis) += signum(direction.(axis))
|
// block.(axis) += signum(direction.(axis))
|
||||||
VectorUtil.set(block, axis, VectorUtil.get(block, axis) + (int) signum(VectorUtil.get(direction, axis)));
|
VectorUtil.set(block, axis, VectorUtil.get(block, axis) + (int) signum(VectorUtil.get(direction, axis)));
|
||||||
|
|
||||||
// position += direction * tMin
|
// position += direction * tMin
|
||||||
VectorUtil.linearCombination(position, 1, direction, tMin, position); // position
|
VectorUtil.linearCombination(position, 1, direction, tMin, position);
|
||||||
// +=
|
|
||||||
// direction
|
|
||||||
// *
|
|
||||||
// tMin
|
|
||||||
distance += tMin;
|
distance += tMin;
|
||||||
|
|
||||||
// position.(axis) = round(position.(axis))
|
// position.(axis) = round(position.(axis))
|
||||||
@ -110,18 +116,18 @@ public class BlockRay {
|
|||||||
return (edge - c) / dir;
|
return (edge - c) / dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
private BlockFace computeCurrentFace(Axis axis, int sign) {
|
private AbsFace computeCurrentFace(Axis axis, int sign) {
|
||||||
if (sign == 0)
|
if (sign == 0)
|
||||||
throw new IllegalStateException("sign is zero");
|
throw new IllegalStateException("sign is zero");
|
||||||
|
|
||||||
switch (axis) {
|
switch (axis) {
|
||||||
case X:
|
case X:
|
||||||
return sign > 0 ? BlockFace.SOUTH : BlockFace.NORTH;
|
return sign > 0 ? AbsFace.NEG_X : AbsFace.POS_X;
|
||||||
case Y:
|
case Y:
|
||||||
return sign > 0 ? BlockFace.EAST : BlockFace.WEST;
|
return sign > 0 ? AbsFace.NEG_Y : AbsFace.POS_Y;
|
||||||
default:
|
default:
|
||||||
case Z:
|
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;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlockFace getCurrentFace() {
|
public AbsFace getCurrentFace() {
|
||||||
return currentFace;
|
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;
|
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.BlockData;
|
||||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
import ru.windcorp.progressia.common.world.generic.ChunkGenericWO;
|
||||||
import ru.windcorp.progressia.common.world.generic.GenericChunk;
|
|
||||||
import ru.windcorp.progressia.common.world.tile.TileData;
|
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 interface ChunkData
|
||||||
|
extends ChunkDataRO, ChunkGenericWO<BlockData, TileData, TileDataStack, TileDataReference, ChunkData> {
|
||||||
public static final int BLOCKS_PER_CHUNK = Coordinates.CHUNK_SIZE;
|
|
||||||
|
// currently empty
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,81 +15,78 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.common.world;
|
package ru.windcorp.progressia.common.world;
|
||||||
|
|
||||||
import glm.vec._3.i.Vec3i;
|
import glm.vec._3.i.Vec3i;
|
||||||
import ru.windcorp.progressia.common.world.block.BlockData;
|
import ru.windcorp.progressia.common.world.block.BlockData;
|
||||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
import ru.windcorp.progressia.common.world.rels.RelFace;
|
||||||
import ru.windcorp.progressia.common.world.tile.TileData;
|
import ru.windcorp.progressia.common.world.tile.TileData;
|
||||||
|
|
||||||
public interface ChunkDataListener {
|
public interface ChunkDataListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked after a block has changed in a chunk. This is not triggered when
|
* Invoked after a block has changed in a chunk.
|
||||||
* a change is caused by chunk loading or unloading.
|
* This is not triggered when a change is caused by chunk loading or
|
||||||
|
* unloading.
|
||||||
*
|
*
|
||||||
* @param chunk
|
* @param chunk the chunk that has changed
|
||||||
* the chunk that has changed
|
* @param blockInChunk the {@linkplain Coordinates#blockInChunk chunk
|
||||||
* @param blockInChunk
|
* coordinates} of the change
|
||||||
* the {@linkplain Coordinates#blockInChunk chunk coordinates} of
|
* @param previous the previous occupant of {@code blockInChunk}
|
||||||
* the change
|
* @param current the current (new) occupant of {@code blockInChunk}
|
||||||
* @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
|
* Invoked after a tile has been added or removed from a chunk.
|
||||||
* triggered when a change is caused by chunk loading or unloading.
|
* This is not triggered when a change is caused by chunk loading or
|
||||||
|
* unloading.
|
||||||
*
|
*
|
||||||
* @param chunk
|
* @param chunk the chunk that has changed
|
||||||
* the chunk that has changed
|
* @param blockInChunk the {@linkplain Coordinates#blockInChunk chunk
|
||||||
* @param blockInChunk
|
* coordinates} of the change
|
||||||
* the {@linkplain Coordinates#blockInChunk chunk coordinates} of
|
* @param face the face that the changed tile belongs or belonged to
|
||||||
* the change
|
* @param tile the tile that has been added or removed
|
||||||
* @param face
|
* @param wasAdded {@code true} iff the tile has been added,
|
||||||
* the face that the changed tile belongs or belonged to
|
* {@code false} iff the tile has been removed
|
||||||
* @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,
|
default void onChunkTilesChanged(
|
||||||
boolean wasAdded) {
|
DefaultChunkData chunk,
|
||||||
|
Vec3i blockInChunk,
|
||||||
|
RelFace face,
|
||||||
|
TileData tile,
|
||||||
|
boolean wasAdded
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked whenever a chunk changes, loads or unloads. If some other method
|
* 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
|
* in this
|
||||||
* was caused by a block being removed, this method is called last.
|
* {@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
|
* @param chunk the chunk that has changed
|
||||||
* the chunk that has changed
|
|
||||||
*/
|
*/
|
||||||
default void onChunkChanged(ChunkData chunk) {
|
default void onChunkChanged(DefaultChunkData chunk) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked whenever a chunk has been loaded.
|
* Invoked whenever a chunk has been loaded.
|
||||||
*
|
*
|
||||||
* @param chunk
|
* @param chunk the chunk that has loaded
|
||||||
* the chunk that has loaded
|
|
||||||
*/
|
*/
|
||||||
default void onChunkLoaded(ChunkData chunk) {
|
default void onChunkLoaded(DefaultChunkData chunk) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked whenever a chunk is about to be unloaded.
|
* Invoked whenever a chunk is about to be unloaded.
|
||||||
*
|
*
|
||||||
* @param chunk
|
* @param chunk the chunk that is going to be loaded
|
||||||
* 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) {
|
public static WorldDataListener createAdder(Supplier<ChunkDataListener> listenerSupplier) {
|
||||||
return new WorldDataListener() {
|
return new WorldDataListener() {
|
||||||
@Override
|
@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());
|
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;
|
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;
|
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);
|
super(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void apply(WorldData world);
|
public abstract void apply(DefaultWorldData world);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -53,9 +53,9 @@ public class PacketRevokeChunk extends PacketAffectChunk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void apply(WorldData world) {
|
public void apply(DefaultWorldData world) {
|
||||||
synchronized (world) {
|
synchronized (world) {
|
||||||
ChunkData chunk = world.getChunk(position);
|
DefaultChunkData chunk = world.getChunk(position);
|
||||||
if (chunk != null) {
|
if (chunk != null) {
|
||||||
world.removeChunk(chunk);
|
world.removeChunk(chunk);
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ public class PacketSendChunk extends PacketAffectChunk {
|
|||||||
super(id);
|
super(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set(ChunkData chunk) {
|
public void set(DefaultChunkData chunk) {
|
||||||
this.position.set(chunk.getX(), chunk.getY(), chunk.getZ());
|
this.position.set(chunk.getX(), chunk.getY(), chunk.getZ());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -67,7 +67,7 @@ public class PacketSendChunk extends PacketAffectChunk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void apply(WorldData world) {
|
public void apply(DefaultWorldData world) {
|
||||||
try {
|
try {
|
||||||
world.addChunk(ChunkIO.load(world, position, data.getReader(), IOContext.COMMS));
|
world.addChunk(ChunkIO.load(world, position, data.getReader(), IOContext.COMMS));
|
||||||
} catch (DecodingException | IOException e) {
|
} 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;
|
package ru.windcorp.progressia.common.world;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
import glm.vec._3.i.Vec3i;
|
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.block.BlockData;
|
||||||
import ru.windcorp.progressia.common.world.entity.EntityData;
|
import ru.windcorp.progressia.common.world.entity.EntityData;
|
||||||
import ru.windcorp.progressia.common.world.generic.ChunkMap;
|
import ru.windcorp.progressia.common.world.generic.WorldGenericWO;
|
||||||
import ru.windcorp.progressia.common.world.generic.ChunkSet;
|
import ru.windcorp.progressia.common.world.rels.BlockFace;
|
||||||
import ru.windcorp.progressia.common.world.generic.GenericWorld;
|
|
||||||
import ru.windcorp.progressia.common.world.generic.LongBasedChunkMap;
|
|
||||||
import ru.windcorp.progressia.common.world.tile.TileData;
|
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> {
|
public interface WorldData
|
||||||
|
extends WorldDataRO, WorldGenericWO<BlockData, TileData, TileDataStack, TileDataReference, 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() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ChunkData getChunk(Vec3i pos) {
|
default TileDataStack getTiles(Vec3i blockInWorld, BlockFace face) {
|
||||||
return chunksByPos.get(pos);
|
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
|
@Override
|
||||||
public Collection<ChunkData> getChunks() {
|
ChunkData getChunk(Vec3i pos);
|
||||||
return chunks;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChunkSet getLoadedChunks() {
|
|
||||||
return chunksByPos.keys();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<EntityData> getEntities() {
|
Collection<? extends ChunkData> getChunks();
|
||||||
return entities;
|
|
||||||
}
|
// TODO: rename WGRO.forEachChunk -> forEachChunkRO and define WGWO.forEachChunk
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void forEachEntity(Consumer<? super EntityData> action) {
|
default ChunkData getChunkByBlock(Vec3i blockInWorld) {
|
||||||
synchronized (entitiesById) { // TODO HORRIBLY MUTILATE THE CORPSE OF
|
return (ChunkData) WorldDataRO.super.getChunkByBlock(blockInWorld);
|
||||||
// TROVE4J so that
|
|
||||||
// gnu.trove.impl.sync.SynchronizedCollection.forEach
|
|
||||||
// is synchronized
|
|
||||||
getEntities().forEach(action);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TLongSet getLoadedEntities() {
|
@Override
|
||||||
return entitiesById.keySet();
|
default TileDataStack getTilesOrNull(Vec3i blockInWorld, BlockFace face) {
|
||||||
}
|
return (TileDataStack) WorldDataRO.super.getTilesOrNull(blockInWorld, face);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.common.world;
|
package ru.windcorp.progressia.common.world;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
@ -26,67 +26,58 @@ import ru.windcorp.progressia.common.world.entity.EntityData;
|
|||||||
public interface WorldDataListener {
|
public interface WorldDataListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked when a new {@link ChunkData} instance is created. This method
|
* Invoked when a new {@link DefaultChunkData} instance is created. This method
|
||||||
* should be used to add {@link ChunkDataListener}s to a new chunk. When
|
* should be used to add
|
||||||
* listeners are added with this method, their
|
* {@link ChunkDataListener}s to a new chunk. When listeners are added with
|
||||||
* {@link ChunkDataListener#onChunkLoaded(ChunkData) onChunkLoaded} methods
|
* this method,
|
||||||
* will be invoked.
|
* their {@link ChunkDataListener#onChunkLoaded(DefaultChunkData) onChunkLoaded}
|
||||||
|
* methods will be invoked.
|
||||||
*
|
*
|
||||||
* @param world
|
* @param world the world instance
|
||||||
* the world instance
|
* @param chunk the {@linkplain Coordinates#chunk coordinates of
|
||||||
* @param chunk
|
* chunk} of the chunk about to load
|
||||||
* the {@linkplain Coordinates#chunk coordinates of chunk} of the
|
* @param chunkListenerSink a sink for listeners. All listeners passed to
|
||||||
* chunk about to load
|
* its
|
||||||
* @param chunkListenerSink
|
* {@link Consumer#accept(Object) accept} method
|
||||||
* a sink for listeners. All listeners passed to its
|
* will be added to the chunk.
|
||||||
* {@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.
|
* Invoked whenever a {@link Chunk} has been loaded.
|
||||||
*
|
*
|
||||||
* @param world
|
* @param world the world instance
|
||||||
* the world instance
|
* @param chunk the chunk that has loaded
|
||||||
* @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.
|
* Invoked whenever a {@link Chunk} is about to be unloaded.
|
||||||
*
|
*
|
||||||
* @param world
|
* @param world the world instance
|
||||||
* the world instance
|
* @param chunk the chunk that is going to be unloaded
|
||||||
* @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.
|
* Invoked whenever an {@link EntityData} has been added.
|
||||||
*
|
*
|
||||||
* @param world
|
* @param world the world instance
|
||||||
* the world instance
|
* @param entity the entity that has been added
|
||||||
* @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.
|
* Invoked whenever an {@link EntityData} is about to be removed.
|
||||||
*
|
*
|
||||||
* @param world
|
* @param world the world instance
|
||||||
* the world instance
|
* @param entity the entity that is going to be removed
|
||||||
* @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.AABB;
|
||||||
import ru.windcorp.progressia.common.collision.CollisionModel;
|
import ru.windcorp.progressia.common.collision.CollisionModel;
|
||||||
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
|
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) {
|
public BlockData(String id) {
|
||||||
super(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 glm.vec._3.i.Vec3i;
|
||||||
import ru.windcorp.progressia.common.world.DecodingException;
|
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 {
|
public class PacketSetBlock extends PacketAffectBlock {
|
||||||
|
|
||||||
@ -60,7 +60,7 @@ public class PacketSetBlock extends PacketAffectBlock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void apply(WorldData world) {
|
public void apply(DefaultWorldData world) {
|
||||||
BlockData block = BlockDataRegistry.getInstance().get(getBlockId());
|
BlockData block = BlockDataRegistry.getInstance().get(getBlockId());
|
||||||
world.setBlock(getBlockInWorld(), block, true);
|
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