Merge branch 'master' into falling-block

This commit is contained in:
opfromthestart 2021-08-22 14:03:31 -04:00
commit efff41a6db
307 changed files with 18421 additions and 5072 deletions

View File

@ -24,7 +24,7 @@ import ru.windcorp.progressia.client.graphics.world.Camera;
import ru.windcorp.progressia.client.graphics.world.EntityAnchor;
import ru.windcorp.progressia.client.graphics.world.LocalPlayer;
import ru.windcorp.progressia.client.world.WorldRender;
import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.common.world.DefaultWorldData;
import ru.windcorp.progressia.common.world.entity.EntityData;
public class Client {
@ -36,7 +36,7 @@ public class Client {
private final ServerCommsChannel comms;
public Client(WorldData world, ServerCommsChannel comms) {
public Client(DefaultWorldData world, ServerCommsChannel comms) {
this.world = new WorldRender(world, this);
this.comms = comms;

View File

@ -21,7 +21,7 @@ package ru.windcorp.progressia.client;
import ru.windcorp.progressia.client.comms.localhost.LocalServerCommsChannel;
import ru.windcorp.progressia.client.graphics.GUI;
import ru.windcorp.progressia.client.graphics.world.LayerWorld;
import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.common.world.DefaultWorldData;
import ru.windcorp.progressia.server.ServerState;
import ru.windcorp.progressia.test.LayerAbout;
import ru.windcorp.progressia.test.LayerTestUI;
@ -41,7 +41,7 @@ public class ClientState {
public static void connectToLocalServer() {
WorldData world = new WorldData();
DefaultWorldData world = new DefaultWorldData();
LocalServerCommsChannel channel = new LocalServerCommsChannel(ServerState.getInstance());

View File

@ -15,20 +15,32 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.graphics;
import glm.vec._4.Vec4;
public class Colors {
public static final Vec4 WHITE = toVector(0xFFFFFFFF), BLACK = toVector(0xFF000000),
public static final Vec4 WHITE = toVector(0xFFFFFFFF),
BLACK = toVector(0xFF000000),
GRAY_4 = toVector(0xFF444444), GRAY = toVector(0xFF888888), GRAY_A = toVector(0xFFAAAAAA),
GRAY_4 = toVector(0xFF444444),
GRAY = toVector(0xFF888888),
GRAY_A = toVector(0xFFAAAAAA),
DEBUG_RED = toVector(0xFFFF0000), DEBUG_GREEN = toVector(0xFF00FF00), DEBUG_BLUE = toVector(0xFF0000FF),
DEBUG_CYAN = toVector(0xFF00FFFF), DEBUG_MAGENTA = toVector(0xFFFF00FF),
DEBUG_YELLOW = toVector(0xFFFFFF00);
DEBUG_RED = toVector(0xFFFF0000),
DEBUG_GREEN = toVector(0xFF00FF00),
DEBUG_BLUE = toVector(0xFF0000FF),
DEBUG_CYAN = toVector(0xFF00FFFF),
DEBUG_MAGENTA = toVector(0xFFFF00FF),
DEBUG_YELLOW = toVector(0xFFFFFF00),
LIGHT_GRAY = toVector(0xFFCBCBD0),
BLUE = toVector(0xFF37A2E6),
HOVER_BLUE = toVector(0xFFC3E4F7),
DISABLED_GRAY = toVector(0xFFE5E5E5),
DISABLED_BLUE = toVector(0xFFB2D8ED);
public static Vec4 toVector(int argb) {
return toVector(argb, new Vec4());

View File

@ -24,6 +24,7 @@ import java.util.List;
import com.google.common.eventbus.Subscribe;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.input.CursorEvent;
import ru.windcorp.progressia.client.graphics.input.FrameResizeEvent;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
@ -57,15 +58,24 @@ public class GUI {
}
public static void addBottomLayer(Layer layer) {
modify(layers -> layers.add(layer));
modify(layers -> {
layers.add(layer);
layer.onAdded();
});
}
public static void addTopLayer(Layer layer) {
modify(layers -> layers.add(0, layer));
modify(layers -> {
layers.add(0, layer);
layer.onAdded();
});
}
public static void removeLayer(Layer layer) {
modify(layers -> layers.remove(layer));
modify(layers -> {
layers.remove(layer);
layer.onRemoved();
});
}
private static void modify(LayerStackModification mod) {
@ -78,12 +88,33 @@ public class GUI {
public static void render() {
synchronized (LAYERS) {
MODIFICATION_QUEUE.forEach(action -> action.affect(LAYERS));
MODIFICATION_QUEUE.clear();
if (!MODIFICATION_QUEUE.isEmpty()) {
MODIFICATION_QUEUE.forEach(action -> action.affect(LAYERS));
MODIFICATION_QUEUE.clear();
boolean isMouseCurrentlyCaptured = GraphicsInterface.isMouseCaptured();
Layer.CursorPolicy policy = Layer.CursorPolicy.REQUIRE;
for (Layer layer : LAYERS) {
Layer.CursorPolicy currentPolicy = layer.getCursorPolicy();
if (currentPolicy != Layer.CursorPolicy.INDIFFERENT) {
policy = currentPolicy;
break;
}
}
boolean shouldCaptureMouse = (policy == Layer.CursorPolicy.FORBID);
if (shouldCaptureMouse != isMouseCurrentlyCaptured) {
GraphicsInterface.setMouseCaptured(shouldCaptureMouse);
}
}
for (int i = LAYERS.size() - 1; i >= 0; --i) {
LAYERS.get(i).render();
}
}
}

View File

@ -30,15 +30,52 @@ public abstract class Layer {
private boolean hasInitialized = false;
private final AtomicBoolean isValid = new AtomicBoolean(false);
/**
* Represents various requests that a {@link Layer} can make regarding the
* presence of a visible cursor. The value of the highest layer that is not
* {@link #INDIFFERENT} is used.
*/
public static enum CursorPolicy {
/**
* Require that a cursor is visible.
*/
REQUIRE,
/**
* The {@link Layer} should not affect the presence or absence of a
* visible cursor; lower layers should be consulted.
*/
INDIFFERENT,
/**
* Forbid a visible cursor.
*/
FORBID
}
private CursorPolicy cursorPolicy = CursorPolicy.INDIFFERENT;
public Layer(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Layer " + name;
}
public CursorPolicy getCursorPolicy() {
return cursorPolicy;
}
public void setCursorPolicy(CursorPolicy cursorPolicy) {
this.cursorPolicy = cursorPolicy;
}
void render() {
GraphicsInterface.startNextLayer();
@ -78,5 +115,13 @@ public abstract class Layer {
protected int getHeight() {
return GraphicsInterface.getFrameHeight();
}
protected void onAdded() {
// Do nothing
}
protected void onRemoved() {
// Do nothing
}
}

View File

@ -179,4 +179,18 @@ public class GraphicsBackend {
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
return vidmode.refreshRate();
}
public static boolean isMouseCaptured() {
return glfwGetInputMode(windowHandle, GLFW_CURSOR) == GLFW_CURSOR_DISABLED;
}
public static void setMouseCaptured(boolean capture) {
int mode = capture ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL;
glfwSetInputMode(windowHandle, GLFW_CURSOR, mode);
if (!capture) {
glfwSetCursorPos(windowHandle, FRAME_SIZE.x / 2.0, FRAME_SIZE.y / 2.0);
}
}
}

View File

@ -81,5 +81,13 @@ public class GraphicsInterface {
}
GraphicsBackend.setVSyncEnabled(GraphicsBackend.isVSyncEnabled());
}
public static boolean isMouseCaptured() {
return GraphicsBackend.isMouseCaptured();
}
public static void setMouseCaptured(boolean capture) {
GraphicsBackend.setMouseCaptured(capture);
}
}

View File

@ -65,8 +65,6 @@ class LWJGLInitializer {
GraphicsBackend.setWindowHandle(handle);
glfwSetInputMode(handle, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
glfwMakeContextCurrent(handle);
glfwSwapInterval(0); // TODO: remove after config system is added
}

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.graphics.flat;
import java.util.ArrayList;
@ -29,8 +29,8 @@ import glm.vec._3.Vec3;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.backend.Usage;
import ru.windcorp.progressia.client.graphics.model.Face;
import ru.windcorp.progressia.client.graphics.model.Faces;
import ru.windcorp.progressia.client.graphics.model.ShapePart;
import ru.windcorp.progressia.client.graphics.model.ShapeParts;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.texture.Texture;
@ -45,7 +45,11 @@ public class RenderTarget {
private final Mat4 transform;
private final Renderable renderable;
public Clip(Iterable<TransformedMask> masks, Mat4 transform, Renderable renderable) {
public Clip(
Iterable<TransformedMask> masks,
Mat4 transform,
Renderable renderable
) {
for (TransformedMask mask : masks) {
this.masks.pushMask(mask);
}
@ -80,7 +84,7 @@ public class RenderTarget {
private final Deque<TransformedMask> maskStack = new LinkedList<>();
private final Deque<Mat4> transformStack = new LinkedList<>();
private final List<Face> currentClipFaces = new ArrayList<>();
private final List<ShapePart> currentClipFaces = new ArrayList<>();
private int depth = 0;
@ -90,19 +94,33 @@ public class RenderTarget {
protected void assembleCurrentClipFromFaces() {
if (!currentClipFaces.isEmpty()) {
Face[] faces = currentClipFaces.toArray(new Face[currentClipFaces.size()]);
ShapePart[] faces = currentClipFaces.toArray(
new ShapePart[currentClipFaces.size()]
);
currentClipFaces.clear();
Shape shape = new Shape(Usage.STATIC, FlatRenderProgram.getDefault(), faces);
Shape shape = new Shape(
Usage.STATIC,
FlatRenderProgram.getDefault(),
faces
);
assembled.add(new Clip(maskStack, getTransform(), shape));
assembled.add(
new Clip(
maskStack,
getTransform(),
shape
)
);
}
}
public Clip[] assemble() {
assembleCurrentClipFromFaces();
Clip[] result = assembled.toArray(new Clip[assembled.size()]);
Clip[] result = assembled.toArray(
new Clip[assembled.size()]
);
reset();
@ -124,11 +142,21 @@ public class RenderTarget {
pushTransform(new Mat4().identity().translate(startX, startY, 0));
maskStack.push(new TransformedMask(new Mask(startX, startY, endX, endY), getTransform()));
maskStack.push(
new TransformedMask(
new Mask(startX, startY, endX, endY),
getTransform()
)
);
}
public void pushMask(Mask mask) {
pushMaskStartEnd(mask.getStartX(), mask.getStartY(), mask.getEndX(), mask.getEndY());
pushMaskStartEnd(
mask.getStartX(),
mask.getStartY(),
mask.getEndX(),
mask.getEndY()
);
}
public void pushMaskStartSize(int x, int y, int width, int height) {
@ -161,58 +189,139 @@ public class RenderTarget {
public void addCustomRenderer(Renderable renderable) {
assembleCurrentClipFromFaces();
assembled.add(new Clip(maskStack, getTransform(), renderable));
float depth = this.depth--;
Mat4 transform = new Mat4().translate(0, 0, depth).mul(getTransform());
assembled.add(new Clip(maskStack, transform, renderable));
}
protected void addFaceToCurrentClip(Face face) {
protected void addFaceToCurrentClip(ShapePart face) {
currentClipFaces.add(face);
}
public void drawTexture(int x, int y, int width, int height, Vec4 color, Texture texture) {
addFaceToCurrentClip(createRectagleFace(x, y, width, height, color, texture));
public void drawTexture(
int x,
int y,
int width,
int height,
Vec4 color,
Texture texture
) {
addFaceToCurrentClip(
createRectagleFace(x, y, width, height, color, texture)
);
}
public void drawTexture(int x, int y, int width, int height, int color, Texture texture) {
public void drawTexture(
int x,
int y,
int width,
int height,
int color,
Texture texture
) {
drawTexture(x, y, width, height, Colors.toVector(color), texture);
}
public void drawTexture(int x, int y, int width, int height, Texture texture) {
public void drawTexture(
int x,
int y,
int width,
int height,
Texture texture
) {
drawTexture(x, y, width, height, Colors.WHITE, texture);
}
public void fill(int x, int y, int width, int height, Vec4 color) {
public void fill(
int x,
int y,
int width,
int height,
Vec4 color
) {
drawTexture(x, y, width, height, color, null);
}
public void fill(int x, int y, int width, int height, int color) {
public void fill(
int x,
int y,
int width,
int height,
int color
) {
fill(x, y, width, height, Colors.toVector(color));
}
public void fill(Vec4 color) {
fill(Integer.MIN_VALUE / 2, Integer.MIN_VALUE / 2, Integer.MAX_VALUE, Integer.MAX_VALUE, color);
fill(
Integer.MIN_VALUE / 2,
Integer.MIN_VALUE / 2,
Integer.MAX_VALUE,
Integer.MAX_VALUE,
color
);
}
public void fill(int color) {
fill(Colors.toVector(color));
}
public Face createRectagleFace(int x, int y, int width, int height, Vec4 color, Texture texture) {
public ShapePart createRectagleFace(
int x,
int y,
int width,
int height,
Vec4 color,
Texture texture
) {
float depth = this.depth--;
return Faces.createRectangle(FlatRenderProgram.getDefault(), texture, color, new Vec3(x, y, depth),
new Vec3(width, 0, 0), new Vec3(0, height, 0), false);
return ShapeParts.createRectangle(
FlatRenderProgram.getDefault(),
texture,
color,
new Vec3(x, y, depth),
new Vec3(width, 0, 0),
new Vec3(0, height, 0),
false
);
}
public Face createRectagleFace(int x, int y, int width, int height, int color, Texture texture) {
public ShapePart createRectagleFace(
int x,
int y,
int width,
int height,
int color,
Texture texture
) {
return createRectagleFace(x, y, width, height, Colors.toVector(color), texture);
}
public Shape createRectagle(int x, int y, int width, int height, Vec4 color, Texture texture) {
return new Shape(Usage.STATIC, FlatRenderProgram.getDefault(),
createRectagleFace(x, y, width, height, color, texture));
public Shape createRectagle(
int x,
int y,
int width,
int height,
Vec4 color,
Texture texture
) {
return new Shape(
Usage.STATIC,
FlatRenderProgram.getDefault(),
createRectagleFace(x, y, width, height, color, texture)
);
}
public Shape createRectagle(int x, int y, int width, int height, int color, Texture texture) {
public Shape createRectagle(
int x,
int y,
int width,
int height,
int color,
Texture texture
) {
return createRectagle(x, y, width, height, Colors.toVector(color), texture);
}

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.graphics.font;
import java.util.ArrayList;
@ -33,8 +33,8 @@ import gnu.trove.stack.TIntStack;
import gnu.trove.stack.array.TIntArrayStack;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.backend.Usage;
import ru.windcorp.progressia.client.graphics.model.Face;
import ru.windcorp.progressia.client.graphics.model.Faces;
import ru.windcorp.progressia.client.graphics.model.ShapePart;
import ru.windcorp.progressia.client.graphics.model.ShapeParts;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
@ -105,7 +105,13 @@ public abstract class SpriteTypeface extends Typeface {
public abstract ShapeRenderProgram getProgram();
@Override
public Vec2i getSize(CharSequence chars, int style, float align, float maxWidth, Vec2i output) {
public Vec2i getSize(
CharSequence chars,
int style,
float align,
float maxWidth,
Vec2i output
) {
if (output == null)
output = new Vec2i();
@ -135,8 +141,19 @@ public abstract class SpriteTypeface extends Typeface {
}
private Shape createCharShape(char c) {
return new Shape(Usage.STATIC, getProgram(), Faces.createRectangle(getProgram(), getTexture(c), Colors.WHITE,
Vectors.ZERO_3, new Vec3(getWidth(c), 0, 0), new Vec3(0, getHeight(), 0), false));
return new Shape(
Usage.STATIC,
getProgram(),
ShapeParts.createRectangle(
getProgram(),
getTexture(c),
Colors.WHITE,
Vectors.ZERO_3,
new Vec3(getWidth(c), 0, 0),
new Vec3(0, getHeight(), 0),
false
)
);
}
private class DynamicText implements Renderable, Drawer {
@ -147,8 +164,19 @@ public abstract class SpriteTypeface extends Typeface {
private final float maxWidth;
private final Vec4 color;
private final Renderable unitLine = new Shape(Usage.STATIC, getProgram(), Faces.createRectangle(getProgram(),
null, Vectors.UNIT_4, Vectors.ZERO_3, new Vec3(1, 0, 0), new Vec3(0, 1, 0), false));
private final Renderable unitLine = new Shape(
Usage.STATIC,
getProgram(),
ShapeParts.createRectangle(
getProgram(),
null,
Vectors.UNIT_4,
Vectors.ZERO_3,
new Vec3(1, 0, 0),
new Vec3(0, 1, 0),
false
)
);
private class DynamicWorkspace extends Workspace {
private ShapeRenderHelper renderer;
@ -162,7 +190,13 @@ public abstract class SpriteTypeface extends Typeface {
private final DynamicWorkspace workspace = new DynamicWorkspace();
public DynamicText(Supplier<CharSequence> supplier, int style, float align, float maxWidth, Vec4 color) {
public DynamicText(
Supplier<CharSequence> supplier,
int style,
float align,
float maxWidth,
Vec4 color
) {
this.supplier = supplier;
this.style = style;
this.align = align;
@ -223,7 +257,7 @@ public abstract class SpriteTypeface extends Typeface {
private class SDWorkspace extends SpriteTypeface.Workspace {
private final Collection<Face> faces = new ArrayList<>();
private final Collection<ShapePart> faces = new ArrayList<>();
private final Vec3 origin = new Vec3();
private final Vec3 width = new Vec3();
@ -263,12 +297,25 @@ public abstract class SpriteTypeface extends Typeface {
workspace.width.sub(workspace.origin);
workspace.height.sub(workspace.origin);
workspace.faces.add(Faces.createRectangle(getProgram(), texture, color, workspace.origin, workspace.width,
workspace.height, false));
workspace.faces.add(
ShapeParts.createRectangle(
getProgram(),
texture,
color,
workspace.origin,
workspace.width,
workspace.height,
false
)
);
}
public Renderable assemble() {
return new Shape(Usage.STATIC, getProgram(), workspace.faces.toArray(new Face[workspace.faces.size()]));
return new Shape(
Usage.STATIC,
getProgram(),
workspace.faces.toArray(new ShapePart[workspace.faces.size()])
);
}
}
@ -281,8 +328,13 @@ public abstract class SpriteTypeface extends Typeface {
}
@Override
public Renderable assembleDynamic(Supplier<CharSequence> supplier, int style, float align, float maxWidth,
Vec4 color) {
public Renderable assembleDynamic(
Supplier<CharSequence> supplier,
int style,
float align,
float maxWidth,
Vec4 color
) {
return new DynamicText(supplier, style, align, maxWidth, color);
}
@ -372,8 +424,15 @@ public abstract class SpriteTypeface extends Typeface {
void drawRectangle(Vec2 size, Vec4 color, Mat4 transform);
}
protected void draw(CharSequence text, Drawer drawer, Workspace workspace, int style, float align, float maxWidth,
Vec4 color) {
protected void draw(
CharSequence text,
Drawer drawer,
Workspace workspace,
int style,
float align,
float maxWidth,
Vec4 color
) {
workspace.text = text;
workspace.toIndex = text.length();
workspace.align = align;
@ -430,7 +489,12 @@ public abstract class SpriteTypeface extends Typeface {
return w.align * (w.totalSize.x - w.currentWidth);
}
private static final float[][] OUTLINE_DIRECTIONS = new float[][] { { 0, 1 }, { 1, 0 }, { -1, 0 }, { 0, -1 } };
private static final float[][] OUTLINE_DIRECTIONS = new float[][] {
{ 0, 1 },
{ 1, 0 },
{ -1, 0 },
{ 0, -1 }
};
private void drawLine(Drawer drawer, Workspace workspace) {
int style = workspace.styles.peek();
@ -527,8 +591,11 @@ public abstract class SpriteTypeface extends Typeface {
workspace.styles.pop();
} else {
throw new IllegalArgumentException("Style contains unknown flags " + Integer.toBinaryString(
(style & ~(Style.BOLD | Style.ITALIC | Style.SHADOW | Style.STRIKETHRU | Style.UNDERLINED))));
throw new IllegalArgumentException(
"Style contains unknown flags " + Integer.toBinaryString(
(style & ~(Style.BOLD | Style.ITALIC | Style.SHADOW | Style.STRIKETHRU | Style.UNDERLINED))
)
);
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}
}

View File

@ -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));
}
}
}

View File

@ -19,18 +19,23 @@
package ru.windcorp.progressia.client.graphics.gui;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import org.lwjgl.glfw.GLFW;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import glm.vec._2.i.Vec2i;
import ru.windcorp.progressia.client.graphics.backend.InputTracker;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
import ru.windcorp.progressia.client.graphics.gui.event.ChildAddedEvent;
import ru.windcorp.progressia.client.graphics.gui.event.ChildRemovedEvent;
import ru.windcorp.progressia.client.graphics.gui.event.EnableEvent;
import ru.windcorp.progressia.client.graphics.gui.event.FocusEvent;
import ru.windcorp.progressia.client.graphics.gui.event.HoverEvent;
import ru.windcorp.progressia.client.graphics.gui.event.ParentChangedEvent;
@ -61,6 +66,8 @@ public class Component extends Named {
private Object layoutHint = null;
private Layout layout = null;
private boolean isEnabled = true;
private boolean isFocusable = false;
private boolean isFocused = false;
@ -285,9 +292,30 @@ public class Component extends Named {
return this;
}
/**
* Checks whether this component is focusable. A component needs to be
* focusable to become focused. A component that is focusable may not
* necessarily be ready to gain focus (see {@link #canGainFocusNow()}).
*
* @return {@code true} iff the component is focusable
* @see #canGainFocusNow()
*/
public boolean isFocusable() {
return isFocusable;
}
/**
* Checks whether this component can become focused at this moment.
* <p>
* The implementation of this method in {@link Component} considers the
* component a focus candidate if it is both focusable and enabled.
*
* @return {@code true} iff the component can receive focus
* @see #isFocusable()
*/
public boolean canGainFocusNow() {
return isFocusable() && isEnabled();
}
public Component setFocusable(boolean focusable) {
this.isFocusable = focusable;
@ -337,7 +365,7 @@ public class Component extends Named {
return;
}
if (component.isFocusable()) {
if (component.canGainFocusNow()) {
setFocused(false);
component.setFocused(true);
return;
@ -379,7 +407,7 @@ public class Component extends Named {
return;
}
if (component.isFocusable()) {
if (component.canGainFocusNow()) {
setFocused(false);
component.setFocused(true);
return;
@ -432,13 +460,52 @@ public class Component extends Named {
return null;
}
public boolean isEnabled() {
return isEnabled;
}
/**
* Enables or disables this component. An {@link EnableEvent} is dispatched
* if the state changes.
*
* @param enabled {@code true} to enable the component, {@code false} to
* disable the component
* @see #setEnabledRecursively(boolean)
*/
public void setEnabled(boolean enabled) {
if (this.isEnabled != enabled) {
if (isFocused() && isEnabled()) {
focusNext();
}
if (isEnabled()) {
setHovered(false);
}
this.isEnabled = enabled;
dispatchEvent(new EnableEvent(this));
}
}
/**
* Enables or disables this component and all of its children recursively.
*
* @param enabled {@code true} to enable the components, {@code false} to
* disable the components
* @see #setEnabled(boolean)
*/
public void setEnabledRecursively(boolean enabled) {
setEnabled(enabled);
getChildren().forEach(c -> c.setEnabledRecursively(enabled));
}
public boolean isHovered() {
return isHovered;
}
protected void setHovered(boolean isHovered) {
if (this.isHovered != isHovered) {
if (this.isHovered != isHovered && isEnabled()) {
this.isHovered = isHovered;
if (!isHovered && !getChildren().isEmpty()) {
@ -499,7 +566,7 @@ public class Component extends Named {
}
protected void handleInput(Input input) {
if (inputBus != null) {
if (inputBus != null && isEnabled()) {
inputBus.dispatch(input);
}
}
@ -595,6 +662,17 @@ public class Component extends Named {
}
}
/**
* Schedules the reassembly to occur.
* <p>
* This method is invoked in root components whenever a
* {@linkplain #requestReassembly() reassembly request} is made by one of
* its children. When creating the dedicated root component, override this
* method to perform any implementation-specific actions that will cause a
* reassembly as soon as possible.
* <p>
* The default implementation of this method does nothing.
*/
protected void handleReassemblyRequest() {
// To be overridden
}
@ -634,6 +712,135 @@ public class Component extends Named {
protected void assembleChildren(RenderTarget target) {
getChildren().forEach(child -> child.assemble(target));
}
/*
* Automatic Reassembly
*/
/**
* The various kinds of changes that may be used with
* {@link Component#reassembleAt(ARTrigger...)}.
*/
protected static enum ARTrigger {
/**
* Reassemble the component whenever its hover status changes, e.g.
* whenever the pointer enters or leaves its bounds.
*/
HOVER,
/**
* Reassemble the component whenever it gains or loses focus.
* <p>
* <em>Component must be focusable to be able to gain focus.</em> The
* component will not be reassembled unless
* {@link Component#setFocusable(boolean) setFocusable(true)} has been
* invoked.
*/
FOCUS,
/**
* Reassemble the component whenever it is enabled or disabled.
*/
ENABLE
}
/**
* All trigger objects (event listeners) that are currently registered with
* {@link #eventBus}. The field is {@code null} until the first trigger is
* installed.
*/
private Map<ARTrigger, Object> autoReassemblyTriggerObjects = null;
private Object createTriggerObject(ARTrigger type) {
switch (type) {
case HOVER:
return new Object() {
@Subscribe
public void onHoverChanged(HoverEvent e) {
requestReassembly();
}
};
case FOCUS:
return new Object() {
@Subscribe
public void onFocusChanged(FocusEvent e) {
requestReassembly();
}
};
case ENABLE:
return new Object() {
@Subscribe
public void onEnabled(EnableEvent e) {
requestReassembly();
}
};
default:
throw new NullPointerException("type");
}
}
/**
* Requests that {@link #requestReassembly()} is invoked on this component
* whenever any of the specified changes occur. Duplicate attempts to
* register the same trigger are silently ignored.
* <p>
* {@code triggers} may be empty, which results in a no-op. It must not be
* {@code null}.
*
* @param triggers the {@linkplain ARTrigger triggers} to
* request reassembly with.
* @see #disableAutoReassemblyAt(ARTrigger...)
*/
protected synchronized void reassembleAt(ARTrigger... triggers) {
Objects.requireNonNull(triggers, "triggers");
if (triggers.length == 0)
return;
if (autoReassemblyTriggerObjects == null) {
autoReassemblyTriggerObjects = new EnumMap<>(ARTrigger.class);
}
for (ARTrigger trigger : triggers) {
if (!autoReassemblyTriggerObjects.containsKey(trigger)) {
Object triggerObject = createTriggerObject(trigger);
addListener(trigger);
autoReassemblyTriggerObjects.put(trigger, triggerObject);
}
}
}
/**
* Requests that {@link #requestReassembly()} is no longer invoked on this
* component whenever any of the specified changes occur. After a trigger is
* removed, it may be reinstalled with
* {@link #reassembleAt(ARTrigger...)}. Attempts to remove a
* nonexistant trigger are silently ignored.
* <p>
* {@code triggers} may be empty, which results in a no-op. It must not be
* {@code null}.
*
* @param triggers the {@linkplain ARTrigger triggers} to remove
* @see #reassemblyAt(ARTrigger...)
*/
protected synchronized void disableAutoReassemblyAt(ARTrigger... triggers) {
Objects.requireNonNull(triggers, "triggers");
if (triggers.length == 0)
return;
if (autoReassemblyTriggerObjects == null)
return;
for (ARTrigger trigger : triggers) {
Object triggerObject = autoReassemblyTriggerObjects.remove(trigger);
if (triggerObject != null) {
removeListener(trigger);
}
}
}
// /**
// * Returns a component that displays this component in its center.

View File

@ -15,19 +15,14 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.graphics.gui;
package ru.windcorp.progressia.common.world.tile;
public class Group extends Component {
public interface TileReference {
TileData get();
int getIndex();
TileDataStack getStack();
default boolean isValid() {
return get() != null;
public Group(String name, Layout layout) {
super(name);
setLayout(layout);
}
}

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.graphics.gui;
import glm.mat._4.Mat4;
@ -82,6 +82,11 @@ public class Label extends Component {
public Font getFont() {
return font;
}
public void setFont(Font font) {
this.font = font;
requestReassembly();
}
public String getCurrentText() {
return currentText;
@ -95,13 +100,9 @@ public class Label extends Component {
protected void assembleSelf(RenderTarget target) {
float startX = getX() + font.getAlign() * (getWidth() - currentSize.x);
target.pushTransform(new Mat4().identity().translate(startX, getY(), -1000) // TODO
// wtf
// is
// this
// magic
// <---
.scale(2));
target.pushTransform(
new Mat4().identity().translate(startX, getY(), 0).scale(2)
);
target.addCustomRenderer(font.assemble(currentText, maxWidth));

View File

@ -15,14 +15,66 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.graphics.gui;
public class Panel extends Component {
import java.util.Objects;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
public class Panel extends Group {
private Vec4 fill;
private Vec4 border;
public Panel(String name, Layout layout, Vec4 fill, Vec4 border) {
super(name, layout);
this.fill = Objects.requireNonNull(fill, "fill");
this.border = border;
}
public Panel(String name, Layout layout) {
super(name);
setLayout(layout);
this(name, layout, Colors.WHITE, Colors.LIGHT_GRAY);
}
/**
* @return the fill
*/
public Vec4 getFill() {
return fill;
}
/**
* @param fill the fill to set
*/
public void setFill(Vec4 fill) {
this.fill = Objects.requireNonNull(fill, "fill");
}
/**
* @return the border
*/
public Vec4 getBorder() {
return border;
}
/**
* @param border the border to set
*/
public void setBorder(Vec4 border) {
this.border = border;
}
@Override
protected void assembleSelf(RenderTarget target) {
if (border == null) {
target.fill(getX(), getY(), getWidth(), getHeight(), fill);
} else {
target.fill(getX(), getY(), getWidth(), getHeight(), border);
target.fill(getX() + 2, getY() + 2, getWidth() - 4, getHeight() - 4, fill);
}
}
}

View File

@ -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));
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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 + ")";
}
}

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.graphics.gui.layout;
import java.util.Arrays;
@ -97,14 +97,28 @@ public class LayoutGrid implements Layout {
void setBounds(int column, int row, Component child, Component parent) {
if (!isSummed)
throw new IllegalStateException("Not summed yet");
int width, height;
if (column == columns.length - 1) {
width = parent.getWidth() - margin - columns[column];
} else {
width = columns[column + 1] - columns[column] - gap;
}
if (row == rows.length - 1) {
height = parent.getHeight() - margin - rows[row];
} else {
height = rows[row + 1] - rows[row] - gap;
}
child.setBounds(parent.getX() + columns[column], parent.getY() + rows[row],
child.setBounds(
parent.getX() + columns[column],
parent.getY() + parent.getHeight() - (rows[row] + height),
(column != (columns.length - 1) ? (columns[column + 1] - columns[column] - gap)
: (parent.getWidth() - margin - columns[column])),
(row != (rows.length - 1) ? (rows[row + 1] - rows[row] - gap)
: (parent.getHeight() - margin - rows[row])));
width,
height
);
}
}
@ -129,10 +143,9 @@ public class LayoutGrid implements Layout {
GridDimensions grid = calculateGrid(c);
grid.sum();
int[] coords;
for (Component child : c.getChildren()) {
coords = (int[]) child.getLayoutHint();
grid.setBounds(coords[0], coords[1], child, c);
Vec2i coords = (Vec2i) child.getLayoutHint();
grid.setBounds(coords.x, coords.y, child, c);
}
}
}
@ -146,11 +159,10 @@ public class LayoutGrid implements Layout {
private GridDimensions calculateGrid(Component parent) {
GridDimensions result = new GridDimensions();
int[] coords;
for (Component child : parent.getChildren()) {
coords = (int[]) child.getLayoutHint();
result.add(coords[0], coords[1], child.getPreferredSize());
Vec2i coords = (Vec2i) child.getLayoutHint();
result.add(coords.x, coords.y, child.getPreferredSize());
}
return result;

View File

@ -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();
}
}

View File

@ -15,63 +15,83 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.graphics.model;
import static ru.windcorp.progressia.common.world.block.BlockFace.*;
import static ru.windcorp.progressia.common.world.rels.AbsFace.*;
import com.google.common.collect.ImmutableMap;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.rels.AbsFace;
class BlockFaceVectors {
private static BlockFaceVectors createInner(BlockFaceVectors outer) {
ImmutableMap.Builder<BlockFace, Vec3> originBuilder = ImmutableMap.builder();
ImmutableMap.Builder<AbsFace, Vec3> originBuilder = ImmutableMap.builder();
ImmutableMap.Builder<BlockFace, Vec3> widthBuilder = ImmutableMap.builder();
ImmutableMap.Builder<AbsFace, Vec3> widthBuilder = ImmutableMap.builder();
ImmutableMap.Builder<BlockFace, Vec3> heightBuilder = ImmutableMap.builder();
ImmutableMap.Builder<AbsFace, Vec3> heightBuilder = ImmutableMap.builder();
for (BlockFace face : getFaces()) {
for (AbsFace face : getFaces()) {
Vec3 width = outer.getWidth(face);
Vec3 height = outer.getHeight(face);
originBuilder.put(face, new Vec3(outer.getOrigin(face)));
originBuilder.put(
face,
new Vec3(outer.getOrigin(face))
);
widthBuilder.put(face, new Vec3(width));
heightBuilder.put(face, new Vec3(height));
}
return new BlockFaceVectors(originBuilder.build(), widthBuilder.build(), heightBuilder.build());
return new BlockFaceVectors(
originBuilder.build(),
widthBuilder.build(),
heightBuilder.build()
);
}
private static final BlockFaceVectors OUTER;
private static final BlockFaceVectors INNER;
static {
OUTER = new BlockFaceVectors(ImmutableMap.<BlockFace, Vec3>builder()
OUTER = new BlockFaceVectors(
ImmutableMap.<AbsFace, Vec3>builder()
.put(TOP, new Vec3(-0.5f, +0.5f, +0.5f)).put(BOTTOM, new Vec3(-0.5f, -0.5f, -0.5f))
.put(NORTH, new Vec3(+0.5f, -0.5f, -0.5f)).put(SOUTH, new Vec3(-0.5f, +0.5f, -0.5f))
.put(WEST, new Vec3(+0.5f, +0.5f, -0.5f)).put(EAST, new Vec3(-0.5f, -0.5f, -0.5f))
.put(POS_Z, new Vec3(-0.5f, +0.5f, +0.5f))
.put(NEG_Z, new Vec3(-0.5f, -0.5f, -0.5f))
.put(POS_X, new Vec3(+0.5f, -0.5f, -0.5f))
.put(NEG_X, new Vec3(-0.5f, +0.5f, -0.5f))
.put(POS_Y, new Vec3(+0.5f, +0.5f, -0.5f))
.put(NEG_Y, new Vec3(-0.5f, -0.5f, -0.5f))
.build(),
ImmutableMap.<BlockFace, Vec3>builder()
ImmutableMap.<AbsFace, Vec3>builder()
.put(TOP, new Vec3(0, -1, 0)).put(BOTTOM, new Vec3(0, +1, 0)).put(NORTH, new Vec3(0, +1, 0))
.put(SOUTH, new Vec3(0, -1, 0)).put(WEST, new Vec3(-1, 0, 0)).put(EAST, new Vec3(+1, 0, 0))
.put(POS_Z, new Vec3(0, -1, 0))
.put(NEG_Z, new Vec3(0, +1, 0))
.put(POS_X, new Vec3(0, +1, 0))
.put(NEG_X, new Vec3(0, -1, 0))
.put(POS_Y, new Vec3(-1, 0, 0))
.put(NEG_Y, new Vec3(+1, 0, 0))
.build(),
.build(),
ImmutableMap.<BlockFace, Vec3>builder()
ImmutableMap.<AbsFace, Vec3>builder()
.put(TOP, new Vec3(+1, 0, 0)).put(BOTTOM, new Vec3(+1, 0, 0)).put(NORTH, new Vec3(0, 0, +1))
.put(SOUTH, new Vec3(0, 0, +1)).put(WEST, new Vec3(0, 0, +1)).put(EAST, new Vec3(0, 0, +1))
.put(POS_Z, new Vec3(+1, 0, 0))
.put(NEG_Z, new Vec3(+1, 0, 0))
.put(POS_X, new Vec3(0, 0, +1))
.put(NEG_X, new Vec3(0, 0, +1))
.put(POS_Y, new Vec3(0, 0, +1))
.put(NEG_Y, new Vec3(0, 0, +1))
.build());
.build()
);
INNER = createInner(OUTER);
}
@ -80,26 +100,29 @@ class BlockFaceVectors {
return inner ? INNER : OUTER;
}
private final ImmutableMap<BlockFace, Vec3> origins;
private final ImmutableMap<BlockFace, Vec3> widths;
private final ImmutableMap<BlockFace, Vec3> heights;
private final ImmutableMap<AbsFace, Vec3> origins;
private final ImmutableMap<AbsFace, Vec3> widths;
private final ImmutableMap<AbsFace, Vec3> heights;
public BlockFaceVectors(ImmutableMap<BlockFace, Vec3> origins, ImmutableMap<BlockFace, Vec3> widths,
ImmutableMap<BlockFace, Vec3> heights) {
public BlockFaceVectors(
ImmutableMap<AbsFace, Vec3> origins,
ImmutableMap<AbsFace, Vec3> widths,
ImmutableMap<AbsFace, Vec3> heights
) {
this.origins = origins;
this.widths = widths;
this.heights = heights;
}
public Vec3 getOrigin(BlockFace face) {
public Vec3 getOrigin(AbsFace face) {
return origins.get(face);
}
public Vec3 getWidth(BlockFace face) {
public Vec3 getWidth(AbsFace face) {
return widths.get(face);
}
public Vec3 getHeight(BlockFace face) {
public Vec3 getHeight(AbsFace face) {
return heights.get(face);
}
}

View File

@ -30,10 +30,10 @@ import ru.windcorp.progressia.client.graphics.backend.VertexBufferObject;
public class Shape implements Renderable {
private final ShapeRenderProgram program;
private final Face[] faces;
private final ShapePart[] parts;
private final Usage usage;
private FaceGroup[] groups;
private ShapePartGroup[] groups;
private ByteBuffer vertices;
private ShortBuffer indices;
@ -45,33 +45,33 @@ public class Shape implements Renderable {
private VertexBufferObject verticesVbo;
private VertexBufferObject indicesVbo;
public Shape(Usage usage, ShapeRenderProgram program, Face... faces) {
public Shape(Usage usage, ShapeRenderProgram program, ShapePart... parts) {
this.program = program;
this.faces = faces;
this.parts = parts;
this.usage = usage;
configureFaces();
configureParts();
program.preprocess(this);
assembleBuffers();
}
private void configureFaces() {
for (Face face : faces) {
face.setShape(this);
private void configureParts() {
for (ShapePart part : parts) {
part.setShape(this);
}
}
private void assembleBuffers() {
// TODO optimize: only update faces that requested it
sortFaces();
sortParts();
resizeBuffers();
for (Face face : faces) {
assembleVertices(face);
assembleIndices(face);
face.resetUpdateFlags();
for (ShapePart part : parts) {
assembleVertices(part);
assembleIndices(part);
part.resetUpdateFlags();
}
this.vertices.flip();
@ -85,110 +85,110 @@ public class Shape implements Renderable {
private void resizeBuffers() {
int verticesRequired = 0, indicesRequired = 0;
for (Face face : faces) {
verticesRequired += face.getVertices().remaining();
indicesRequired += face.getIndices().remaining();
for (ShapePart part : parts) {
verticesRequired += part.getVertices().remaining();
indicesRequired += part.getIndices().remaining();
}
if (this.vertices == null || vertices.capacity() < verticesRequired) {
if (vertices == null || vertices.capacity() < verticesRequired) {
this.vertices = BufferUtils.createByteBuffer(verticesRequired);
} else {
this.vertices.position(0).limit(verticesRequired);
vertices.position(0).limit(verticesRequired);
}
if (this.indices == null || this.indices.capacity() < indicesRequired) {
if (indices == null || indices.capacity() < indicesRequired) {
this.indices = BufferUtils.createShortBuffer(indicesRequired);
} else {
this.indices.position(0).limit(indicesRequired);
indices.position(0).limit(indicesRequired);
}
}
private void assembleVertices(Face face) {
face.locationOfVertices = this.vertices.position();
private void assembleVertices(ShapePart part) {
part.locationOfVertices = this.vertices.position();
insertVertices(face);
linkVerticesWith(face);
insertVertices(part);
linkVerticesWith(part);
}
private void insertVertices(Face face) {
ByteBuffer faceVertices = face.getVertices();
private void insertVertices(ShapePart part) {
ByteBuffer partVertices = part.getVertices();
faceVertices.mark();
this.vertices.put(faceVertices);
faceVertices.reset();
partVertices.mark();
this.vertices.put(partVertices);
partVertices.reset();
}
private void linkVerticesWith(Face face) {
private void linkVerticesWith(ShapePart part) {
int limit = vertices.limit();
int position = vertices.position();
vertices.limit(position).position(face.getLocationOfVertices());
face.vertices = vertices.slice();
vertices.limit(position).position(part.getLocationOfVertices());
part.vertices = vertices.slice();
vertices.position(position).limit(limit);
}
private void assembleIndices(Face face) {
short vertexOffset = (short) (face.getLocationOfVertices() / program.getBytesPerVertex());
private void assembleIndices(ShapePart part) {
short vertexOffset = (short) (part.getLocationOfVertices() / program.getBytesPerVertex());
face.locationOfIndices = indices.position();
part.locationOfIndices = indices.position();
ShortBuffer faceIndices = face.getIndices();
ShortBuffer partIndices = part.getIndices();
if (faceIndices == null) {
for (int i = 0; i < face.getVertexCount(); ++i) {
if (partIndices == null) {
for (int i = 0; i < part.getVertexCount(); ++i) {
this.indices.put((short) (vertexOffset + i));
}
} else {
for (int i = faceIndices.position(); i < faceIndices.limit(); ++i) {
short faceIndex = faceIndices.get(i);
faceIndex += vertexOffset;
this.indices.put(faceIndex);
for (int i = partIndices.position(); i < partIndices.limit(); ++i) {
short partIndex = partIndices.get(i);
partIndex += vertexOffset;
this.indices.put(partIndex);
}
}
}
private void sortFaces() {
Arrays.sort(faces);
private void sortParts() {
Arrays.sort(parts);
}
private void assembleGroups() {
int unique = countUniqueFaces();
this.groups = new FaceGroup[unique];
int unique = countUniqueParts();
this.groups = new ShapePartGroup[unique];
if (faces.length == 0)
if (parts.length == 0)
return;
int previousHandle = faces[0].getSortingIndex();
int previousHandle = parts[0].getSortingIndex();
int start = 0;
int groupIndex = 0;
for (int i = 1; i < faces.length; ++i) {
if (previousHandle != faces[i].getSortingIndex()) {
for (int i = 1; i < parts.length; ++i) {
if (previousHandle != parts[i].getSortingIndex()) {
groups[groupIndex] = new FaceGroup(faces, start, i);
groups[groupIndex] = new ShapePartGroup(parts, start, i);
start = i;
groupIndex++;
previousHandle = faces[i].getSortingIndex();
previousHandle = parts[i].getSortingIndex();
}
}
assert groupIndex == groups.length - 1;
groups[groupIndex] = new FaceGroup(faces, start, faces.length);
groups[groupIndex] = new ShapePartGroup(parts, start, parts.length);
}
private int countUniqueFaces() {
if (faces.length == 0)
private int countUniqueParts() {
if (parts.length == 0)
return 0;
int result = 1;
int previousHandle = faces[0].getSortingIndex();
int previousHandle = parts[0].getSortingIndex();
for (int i = 1; i < faces.length; ++i) {
if (previousHandle != faces[i].getSortingIndex()) {
for (int i = 1; i < parts.length; ++i) {
if (previousHandle != parts[i].getSortingIndex()) {
result++;
previousHandle = faces[i].getSortingIndex();
previousHandle = parts[i].getSortingIndex();
}
}
@ -238,11 +238,11 @@ public class Shape implements Renderable {
return program;
}
public Face[] getFaces() {
return faces;
public ShapePart[] getParts() {
return parts;
}
public FaceGroup[] getGroups() {
public ShapePartGroup[] getGroups() {
return groups;
}

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.graphics.model;
import java.nio.ByteBuffer;
@ -24,7 +24,7 @@ import java.util.Objects;
import ru.windcorp.progressia.client.graphics.texture.Texture;
public class Face implements Comparable<Face> {
public class ShapePart implements Comparable<ShapePart> {
private static final ShortBuffer GENERATE_SUCCESSIVE_LATER = null;
@ -40,13 +40,20 @@ public class Face implements Comparable<Face> {
private ShortBuffer userIndices;
private boolean userIndicesUpdated = true;
public Face(Texture texture, ByteBuffer vertices, ShortBuffer indices) {
public ShapePart(
Texture texture,
ByteBuffer vertices,
ShortBuffer indices
) {
setTexture(texture);
setVertices(vertices);
setIndices(indices);
}
public Face(Texture texture, ByteBuffer vertices) {
public ShapePart(
Texture texture,
ByteBuffer vertices
) {
this(texture, vertices, null);
}
@ -59,16 +66,22 @@ public class Face implements Comparable<Face> {
private void checkVertices() {
if (vertices.remaining() % getBytesPerVertex() != 0) {
throw new IllegalArgumentException("Invalid vertex buffer: " + (vertices.remaining() % getBytesPerVertex())
+ " extra bytes after last vertex");
throw new IllegalArgumentException(
"Invalid vertex buffer: " +
(vertices.remaining() % getBytesPerVertex()) +
" extra bytes after last vertex"
);
}
}
private void checkIndices() {
if (userIndices != GENERATE_SUCCESSIVE_LATER) {
if (userIndices.remaining() % 3 != 0) {
throw new IllegalArgumentException("Invalid vertex indices: " + (userIndices.remaining() % 3)
+ " extra indices after last triangle");
throw new IllegalArgumentException(
"Invalid vertex indices: " +
(userIndices.remaining() % 3) +
" extra indices after last triangle"
);
}
userIndices.mark();
@ -78,15 +91,21 @@ public class Face implements Comparable<Face> {
short index = userIndices.get();
if (index < 0 || index >= vertexCount) {
throw new IllegalArgumentException(
"Invalid vertex index " + index + " (" + vertexCount + " vertices available)");
"Invalid vertex index " + index +
" (" + vertexCount + " vertices available)"
);
}
}
userIndices.reset();
} else {
if (getVertexCount() % 3 != 0) {
throw new IllegalArgumentException("Invalid vertices: " + (getVertexCount() % 3)
+ " extra indices after last triangle " + "(indices are automatic)");
throw new IllegalArgumentException(
"Invalid vertices: " +
(getVertexCount() % 3) +
" extra indices after last triangle " +
"(indices are automatic)"
);
}
}
}
@ -136,7 +155,7 @@ public class Face implements Comparable<Face> {
return vertices;
}
public Face setVertices(ByteBuffer vertices) {
public ShapePart setVertices(ByteBuffer vertices) {
this.vertices = Objects.requireNonNull(vertices, "vertices");
markForVertexUpdate();
return this;
@ -183,7 +202,7 @@ public class Face implements Comparable<Face> {
return userIndices.remaining();
}
public Face setIndices(ShortBuffer indices) {
public ShapePart setIndices(ShortBuffer indices) {
if (indices == null) {
indices = GENERATE_SUCCESSIVE_LATER;
}
@ -226,7 +245,7 @@ public class Face implements Comparable<Face> {
}
@Override
public int compareTo(Face o) {
public int compareTo(ShapePart o) {
return Integer.compare(getSortingIndex(), o.getSortingIndex());
}

View File

@ -21,13 +21,13 @@ package ru.windcorp.progressia.client.graphics.model;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.graphics.texture.TexturePrimitive;
public class FaceGroup {
public class ShapePartGroup {
private final TexturePrimitive texture;
private final int indexCount;
private final int byteOffsetOfIndices;
FaceGroup(Face[] faces, int start, int end) {
ShapePartGroup(ShapePart[] faces, int start, int end) {
Texture t = faces[start].getTexture();
this.texture = t == null ? null : t.getSprite().getPrimitive();
@ -36,7 +36,7 @@ public class FaceGroup {
int indexCount = 0;
for (int i = start; i < end; ++i) {
Face face = faces[i];
ShapePart face = faces[i];
assert this.texture == null ? (face.getTexture() == null)
: (face.getTexture().getSprite().getPrimitive() == this.texture);

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.graphics.model;
import java.nio.ShortBuffer;
@ -25,37 +25,93 @@ import glm.vec._3.Vec3;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram.VertexBuilder;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.rels.AbsFace;
public class Faces {
public class ShapeParts {
private Faces() {
private ShapeParts() {
}
public static Face createRectangle(ShapeRenderProgram program, Texture texture, Vec4 colorMultiplier, Vec3 origin,
Vec3 width, Vec3 height, boolean flip) {
public static ShapePart createRectangle(
ShapeRenderProgram program,
Texture texture,
Vec4 colorMultiplier,
Vec3 origin,
Vec3 width,
Vec3 height,
boolean flip
) {
VertexBuilder builder = program.getVertexBuilder();
builder.addVertex(origin, colorMultiplier, new Vec2(0, 0))
.addVertex(origin.add_(height), colorMultiplier, new Vec2(0, 1))
.addVertex(origin.add_(width), colorMultiplier, new Vec2(1, 0))
.addVertex(origin.add_(width).add(height), colorMultiplier, new Vec2(1, 1));
builder.addVertex(
origin,
colorMultiplier,
new Vec2(0, 0)
).addVertex(
origin.add_(height),
colorMultiplier,
new Vec2(0, 1)
).addVertex(
origin.add_(width),
colorMultiplier,
new Vec2(1, 0)
).addVertex(
origin.add_(width).add(height),
colorMultiplier,
new Vec2(1, 1)
);
ShortBuffer buffer = flip ? ShortBuffer.wrap(new short[] { 0, 1, 3, 0, 3, 2 })
: ShortBuffer.wrap(new short[] { 3, 1, 0, 2, 3, 0 });
ShortBuffer buffer = flip ? ShortBuffer.wrap(
new short[] {
0,
1,
3,
0,
3,
2
}
)
: ShortBuffer.wrap(
new short[] {
3,
1,
0,
2,
3,
0
}
);
return new Face(texture, builder.assemble(), buffer);
return new ShapePart(
texture,
builder.assemble(),
buffer
);
}
public static Face createBlockFace(ShapeRenderProgram program, Texture texture, Vec4 colorMultiplier,
Vec3 blockCenter, BlockFace face, boolean inner) {
public static ShapePart createBlockFace(
ShapeRenderProgram program,
Texture texture,
Vec4 colorMultiplier,
Vec3 blockCenter,
AbsFace face,
boolean inner
) {
BlockFaceVectors vectors = BlockFaceVectors.get(inner);
Vec3 origin = blockCenter.add_(vectors.getOrigin(face));
Vec3 width = vectors.getWidth(face);
Vec3 height = vectors.getHeight(face);
return createRectangle(program, texture, colorMultiplier, origin, width, height, inner);
return createRectangle(
program,
texture,
colorMultiplier,
origin,
width,
height,
inner
);
}
}

View File

@ -100,7 +100,7 @@ public class ShapeRenderProgram extends Program {
try {
enableAttributes();
for (FaceGroup group : shape.getGroups()) {
for (ShapePartGroup group : shape.getGroups()) {
renderFaceGroup(group);
}
} finally {
@ -145,7 +145,7 @@ public class ShapeRenderProgram extends Program {
indices.bind(BindTarget.ELEMENT_ARRAY);
}
protected void renderFaceGroup(FaceGroup group) {
protected void renderFaceGroup(ShapePartGroup group) {
TexturePrimitive texture = group.getTexture();
if (texture != null) {
@ -165,12 +165,12 @@ public class ShapeRenderProgram extends Program {
}
public void preprocess(Shape shape) {
for (Face face : shape.getFaces()) {
for (ShapePart face : shape.getParts()) {
applySprites(face);
}
}
private void applySprites(Face face) {
private void applySprites(ShapePart face) {
if (face.getTexture() == null)
return;

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.graphics.model;
import java.util.Map;
@ -24,42 +24,102 @@ import glm.vec._3.Vec3;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.backend.Usage;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.rels.AbsFace;
public class Shapes {
public static Shape createParallelepiped(
// Try saying that 10 times fast
ShapeRenderProgram program,
// Try saying that 10 times fast
ShapeRenderProgram program,
Vec3 origin,
Vec3 origin,
Vec3 width, Vec3 height, Vec3 depth,
Vec3 width,
Vec3 height,
Vec3 depth,
Vec4 colorMultiplier,
Vec4 colorMultiplier,
Texture topTexture, Texture bottomTexture, Texture northTexture, Texture southTexture, Texture eastTexture,
Texture westTexture,
Texture topTexture,
Texture bottomTexture,
Texture northTexture,
Texture southTexture,
Texture eastTexture,
Texture westTexture,
boolean flip) {
boolean flip
) {
Face top = Faces.createRectangle(program, topTexture, colorMultiplier, origin.add_(height).add(width),
width.negate_(), depth, flip);
ShapePart top = ShapeParts.createRectangle(
program,
topTexture,
colorMultiplier,
origin.add_(height).add(width),
width.negate_(),
depth,
flip
);
Face bottom = Faces.createRectangle(program, bottomTexture, colorMultiplier, origin, width, depth, flip);
ShapePart bottom = ShapeParts.createRectangle(
program,
bottomTexture,
colorMultiplier,
origin,
width,
depth,
flip
);
Face north = Faces.createRectangle(program, northTexture, colorMultiplier, origin.add_(depth), width, height,
flip);
ShapePart north = ShapeParts.createRectangle(
program,
northTexture,
colorMultiplier,
origin.add_(depth),
width,
height,
flip
);
Face south = Faces.createRectangle(program, southTexture, colorMultiplier, origin.add_(width), width.negate_(),
height, flip);
ShapePart south = ShapeParts.createRectangle(
program,
southTexture,
colorMultiplier,
origin.add_(width),
width.negate_(),
height,
flip
);
Face east = Faces.createRectangle(program, eastTexture, colorMultiplier, origin, depth, height, flip);
ShapePart east = ShapeParts.createRectangle(
program,
eastTexture,
colorMultiplier,
origin,
depth,
height,
flip
);
Face west = Faces.createRectangle(program, westTexture, colorMultiplier, origin.add_(width).add(depth),
depth.negate_(), height, flip);
ShapePart west = ShapeParts.createRectangle(
program,
westTexture,
colorMultiplier,
origin.add_(width).add(depth),
depth.negate_(),
height,
flip
);
Shape result = new Shape(Usage.STATIC, program, top, bottom, north, south, east, west);
Shape result = new Shape(
Usage.STATIC,
program,
top,
bottom,
north,
south,
east,
west
);
return result;
}
@ -85,8 +145,15 @@ public class Shapes {
private boolean flip = false;
public PppBuilder(ShapeRenderProgram program, Texture top, Texture bottom, Texture north, Texture south,
Texture east, Texture west) {
public PppBuilder(
ShapeRenderProgram program,
Texture top,
Texture bottom,
Texture north,
Texture south,
Texture east,
Texture west
) {
this.program = program;
this.topTexture = top;
this.bottomTexture = bottom;
@ -96,10 +163,19 @@ public class Shapes {
this.westTexture = west;
}
public PppBuilder(ShapeRenderProgram program, Map<BlockFace, Texture> textureMap) {
this(program, textureMap.get(BlockFace.TOP), textureMap.get(BlockFace.BOTTOM),
textureMap.get(BlockFace.NORTH), textureMap.get(BlockFace.SOUTH), textureMap.get(BlockFace.EAST),
textureMap.get(BlockFace.WEST));
public PppBuilder(
ShapeRenderProgram program,
Map<AbsFace, Texture> textureMap
) {
this(
program,
textureMap.get(AbsFace.POS_Z),
textureMap.get(AbsFace.NEG_Z),
textureMap.get(AbsFace.POS_X),
textureMap.get(AbsFace.NEG_X),
textureMap.get(AbsFace.NEG_Y),
textureMap.get(AbsFace.POS_Y)
);
}
public PppBuilder(ShapeRenderProgram program, Texture texture) {
@ -190,8 +266,21 @@ public class Shapes {
}
public Shape create() {
return createParallelepiped(program, origin, width, height, depth, colorMultiplier, topTexture,
bottomTexture, northTexture, southTexture, eastTexture, westTexture, flip);
return createParallelepiped(
program,
origin,
width,
height,
depth,
colorMultiplier,
topTexture,
bottomTexture,
northTexture,
southTexture,
eastTexture,
westTexture,
flip
);
}
}

View File

@ -15,13 +15,13 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.graphics.texture;
import java.util.Map;
import glm.vec._2.Vec2;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.rels.AbsFace;
public class ComplexTexture {
@ -30,27 +30,67 @@ public class ComplexTexture {
private final float assumedWidth;
private final float assumedHeight;
public ComplexTexture(TexturePrimitive primitive, int abstractWidth, int abstractHeight) {
public ComplexTexture(
TexturePrimitive primitive,
int abstractWidth,
int abstractHeight
) {
this.primitive = primitive;
this.assumedWidth = abstractWidth / (float) primitive.getWidth() * primitive.getBufferWidth();
this.assumedWidth = abstractWidth
/ (float) primitive.getWidth() * primitive.getBufferWidth();
this.assumedHeight = abstractHeight / (float) primitive.getHeight() * primitive.getBufferHeight();
this.assumedHeight = abstractHeight
/ (float) primitive.getHeight() * primitive.getBufferHeight();
}
public Texture get(int x, int y, int width, int height) {
return new SimpleTexture(new Sprite(primitive, new Vec2(x / assumedWidth, y / assumedHeight),
new Vec2(width / assumedWidth, height / assumedHeight)));
return new SimpleTexture(
new Sprite(
primitive,
new Vec2(x / assumedWidth, y / assumedHeight),
new Vec2(width / assumedWidth, height / assumedHeight)
)
);
}
public Map<BlockFace, Texture> getCuboidTextures(int x, int y, int width, int height, int depth) {
return BlockFace.mapToFaces(get(x + depth + width, y + height + depth, -width, -depth),
get(x + depth + width + width, y + height + depth, -width, -depth), get(x + depth, y, width, height),
get(x + depth + width + depth, y, width, height), get(x, y, depth, height),
get(x + depth + width, y, depth, height));
public Map<AbsFace, Texture> getCuboidTextures(
int x,
int y,
int width,
int height,
int depth
) {
return AbsFace.mapToFaces(
get(
x + depth + width,
y + height + depth,
-width,
-depth
),
get(
x + depth + width + width,
y + height + depth,
-width,
-depth
),
get(x + depth, y, width, height),
get(
x + depth + width + depth,
y,
width,
height
),
get(x, y, depth, height),
get(x + depth + width, y, depth, height)
);
}
public Map<BlockFace, Texture> getCuboidTextures(int x, int y, int size) {
public Map<AbsFace, Texture> getCuboidTextures(
int x,
int y,
int size
) {
return getCuboidTextures(x, y, size, size, size);
}

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.graphics.world;
import static java.lang.Math.*;
@ -29,6 +29,9 @@ import glm.mat._4.Mat4;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.world.Camera.Anchor.Mode;
import ru.windcorp.progressia.client.world.entity.NPedModel;
import ru.windcorp.progressia.common.util.Matrices;
import ru.windcorp.progressia.common.util.Vectors;
public class Camera {
@ -42,7 +45,10 @@ public class Camera {
void applyCameraRotation(Mat4 output);
public static Mode of(Consumer<Vec3> offsetGetter, Consumer<Mat4> rotator) {
public static Mode of(
Consumer<Vec3> offsetGetter,
Consumer<Mat4> rotator
) {
return new Mode() {
@Override
public void getCameraOffset(Vec3 output) {
@ -57,13 +63,13 @@ public class Camera {
}
}
void getCameraPosition(Vec3 output);
Vec3 getCameraPosition(Vec3 output);
void getCameraVelocity(Vec3 output);
Vec3 getCameraVelocity(Vec3 output);
float getCameraYaw();
float getCameraPitch();
Vec3 getLookingAt(Vec3 output);
Vec3 getUpVector(Vec3 output);
Collection<Mode> getCameraModes();
@ -81,14 +87,11 @@ public class Camera {
*/
private final Vec3 lastAnchorPosition = new Vec3();
private float lastAnchorYaw;
private float lastAnchorPitch;
private final Vec3 lastAnchorLookingAt = new Vec3();
private final Vec3 lastAnchorUpVector = new Vec3();
private final Mat4 lastCameraMatrix = new Mat4();
private final Vec3 lastAnchorLookingAt = new Vec3();
private final Vec3 lastAnchorUp = new Vec3();
{
invalidateCache();
}
@ -105,6 +108,9 @@ public class Camera {
*/
public void apply(WorldRenderHelper helper) {
if (NPedModel.flag) {
// System.out.println("Camera.apply()");
}
applyPerspective(helper);
rotateCoordinateSystem(helper);
@ -118,8 +124,13 @@ public class Camera {
private void applyPerspective(WorldRenderHelper helper) {
Mat4 previous = helper.getViewTransform();
Glm.perspective(computeFovY(), GraphicsInterface.getAspectRatio(), 0.01f, 150.0f, helper.pushViewTransform())
.mul(previous);
Glm.perspective(
computeFovY(),
GraphicsInterface.getAspectRatio(),
0.01f,
150.0f,
helper.pushViewTransform()
).mul(previous);
}
private void rotateCoordinateSystem(WorldRenderHelper helper) {
@ -141,17 +152,34 @@ public class Camera {
}
private void applyDirection(WorldRenderHelper helper) {
float pitch = anchor.getCameraPitch();
float yaw = anchor.getCameraYaw();
anchor.getLookingAt(lastAnchorLookingAt);
anchor.getUpVector(lastAnchorUpVector);
helper.pushViewTransform().rotateY(-pitch).rotateZ(-yaw);
lookAt(helper.pushViewTransform());
}
this.lastAnchorYaw = yaw;
this.lastAnchorPitch = pitch;
this.lastAnchorLookingAt.set(cos(pitch) * cos(yaw), cos(pitch) * sin(yaw), sin(pitch));
this.lastAnchorUp.set(cos(pitch + PI_F / 2) * cos(yaw), cos(pitch + PI_F / 2) * sin(yaw),
sin(pitch + PI_F / 2));
private void lookAt(Mat4 result) {
Vec3 f = this.lastAnchorLookingAt;
Vec3 s = Vectors.grab3();
Vec3 u = Vectors.grab3();
f.cross(this.lastAnchorUpVector, s);
s.normalize();
s.cross(f, u);
Mat4 workspace = Matrices.grab4();
workspace.set(
+f.x, -s.x, +u.x, 0,
+f.y, -s.y, +u.y, 0,
+f.z, -s.z, +u.z, 0,
0, 0, 0, 1
);
result.mul(workspace);
Matrices.release(workspace);
Vectors.release(s);
Vectors.release(u);
}
private void applyPosition(WorldRenderHelper helper) {
@ -177,7 +205,11 @@ public class Camera {
if (widthOverHeight >= 1) {
return fieldOfView;
} else {
return (float) (2 * atan(1 / widthOverHeight * tan(fieldOfView / 2)));
return (float) (2 * atan(
1 / widthOverHeight
*
tan(fieldOfView / 2)
));
}
}
@ -213,7 +245,9 @@ public class Camera {
if (modesCollection.isEmpty()) {
throw new IllegalArgumentException(
"Anchor " + anchor + " returned no camera modes," + " at least one required");
"Anchor " + anchor + " returned no camera modes,"
+ " at least one required"
);
}
this.anchor = anchor;
@ -224,14 +258,28 @@ public class Camera {
private void invalidateCache() {
this.lastAnchorPosition.set(Float.NaN);
this.lastAnchorYaw = Float.NaN;
this.lastAnchorPitch = Float.NaN;
this.lastCameraMatrix.set(Float.NaN, Float.NaN, Float.NaN, Float.NaN, Float.NaN, Float.NaN, Float.NaN,
Float.NaN, Float.NaN, Float.NaN, Float.NaN, Float.NaN, Float.NaN, Float.NaN, Float.NaN, Float.NaN);
this.lastCameraMatrix.set(
Float.NaN,
Float.NaN,
Float.NaN,
Float.NaN,
Float.NaN,
Float.NaN,
Float.NaN,
Float.NaN,
Float.NaN,
Float.NaN,
Float.NaN,
Float.NaN,
Float.NaN,
Float.NaN,
Float.NaN,
Float.NaN
);
this.lastAnchorLookingAt.set(Float.NaN);
this.lastAnchorUp.set(Float.NaN);
this.lastAnchorUpVector.set(Float.NaN);
}
public Anchor.Mode getMode() {
@ -250,14 +298,6 @@ public class Camera {
return currentModeIndex;
}
public float getLastAnchorYaw() {
return lastAnchorYaw;
}
public float getLastAnchorPitch() {
return lastAnchorPitch;
}
public Vec3 getLastAnchorPosition() {
return lastAnchorPosition;
}
@ -271,7 +311,7 @@ public class Camera {
}
public Vec3 getLastAnchorUp() {
return lastAnchorUp;
return lastAnchorUpVector;
}
}

View File

@ -52,24 +52,32 @@ public class EntityAnchor implements Anchor {
}
@Override
public void getCameraPosition(Vec3 output) {
public Vec3 getCameraPosition(Vec3 output) {
if (output == null) output = new Vec3();
model.getViewPoint(output);
output.add(entity.getPosition());
output.add(model.getPosition());
return output;
}
@Override
public void getCameraVelocity(Vec3 output) {
public Vec3 getCameraVelocity(Vec3 output) {
if (output == null) output = new Vec3();
output.set(entity.getVelocity());
return output;
}
@Override
public float getCameraYaw() {
return entity.getYaw();
public Vec3 getLookingAt(Vec3 output) {
if (output == null) output = new Vec3();
model.getLookingAt(output);
return output;
}
@Override
public float getCameraPitch() {
return entity.getPitch();
public Vec3 getUpVector(Vec3 output) {
if (output == null) output = new Vec3();
model.getUpVector(output);
return output;
}
@Override

View File

@ -41,6 +41,8 @@ import ru.windcorp.progressia.common.Units;
import ru.windcorp.progressia.common.collision.Collideable;
import ru.windcorp.progressia.common.collision.colliders.Collider;
import ru.windcorp.progressia.common.util.FloatMathUtil;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.GravityModel;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.test.CollisionModelRenderer;
import ru.windcorp.progressia.test.TestPlayerControls;
@ -57,6 +59,8 @@ public class LayerWorld extends Layer {
super("World");
this.client = client;
this.inputBasedControls = new InputBasedControls(client);
setCursorPolicy(CursorPolicy.FORBID);
}
@Override
@ -181,16 +185,25 @@ public class LayerWorld extends Layer {
entity.getVelocity().mul((float) Math.exp(-FRICTION_COEFF / entity.getCollisionMass() * tickLength));
}
private static final float MC_g = Units.get("32 m/s^2");
private static final float IRL_g = Units.get("9.8 m/s^2");
private void tmp_applyGravity(EntityData entity, float tickLength) {
GravityModel gm = ClientState.getInstance().getWorld().getData().getGravityModel();
Vec3 upVector = Vectors.grab3();
gm.getUp(entity.getPosition(), upVector);
entity.changeUpVector(upVector);
Vectors.release(upVector);
if (ClientState.getInstance().getLocalPlayer().getEntity() == entity && tmp_testControls.isFlying()) {
return;
}
final float gravitationalAcceleration = tmp_testControls.useMinecraftGravity() ? MC_g : IRL_g;
entity.getVelocity().add(0, 0, -gravitationalAcceleration * tickLength);
Vec3 gravitationalAcceleration = Vectors.grab3();
gm.getGravity(entity.getPosition(), gravitationalAcceleration);
gravitationalAcceleration.mul(tickLength);
entity.getVelocity().add(gravitationalAcceleration);
Vectors.release(gravitationalAcceleration);
}
@Override

View File

@ -23,13 +23,13 @@ import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.world.WorldRender;
import ru.windcorp.progressia.common.world.BlockRay;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.rels.AbsFace;
public class Selection {
private final Vec3i block = new Vec3i();
private BlockFace surface = null;
private AbsFace surface = null;
private final Vec2 pointOnSurface = new Vec2(0.5f, 0.5f);
private final Vec3 point = new Vec3();
@ -38,10 +38,9 @@ public class Selection {
private BlockRay ray = new BlockRay();
public void update(WorldRender world, EntityData player) {
Vec3 direction = new Vec3();
Vec3 start = new Vec3();
player.getLookingAtVector(direction);
Vec3 direction = player.getLookingAt();
world.getEntityRenderable(player).getViewPoint(start);
start.add(player.getPosition());
@ -71,7 +70,7 @@ public class Selection {
return exists ? point : null;
}
public BlockFace getSurface() {
public AbsFace getSurface() {
return exists ? surface : null;
}

View File

@ -33,7 +33,7 @@ import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.backend.VertexBufferObject;
import ru.windcorp.progressia.client.graphics.backend.shaders.attributes.*;
import ru.windcorp.progressia.client.graphics.backend.shaders.uniforms.*;
import ru.windcorp.progressia.client.graphics.model.Face;
import ru.windcorp.progressia.client.graphics.model.ShapePart;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
@ -120,12 +120,12 @@ public class WorldRenderProgram extends ShapeRenderProgram {
public void preprocess(Shape shape) {
super.preprocess(shape);
for (Face face : shape.getFaces()) {
for (ShapePart face : shape.getParts()) {
computeNormals(face);
}
}
private void computeNormals(Face face) {
private void computeNormals(ShapePart face) {
Vec3 a = Vectors.grab3();
Vec3 b = Vectors.grab3();
Vec3 c = Vectors.grab3();
@ -160,7 +160,7 @@ public class WorldRenderProgram extends ShapeRenderProgram {
normal.normalize();
}
private void loadVertexPosition(Face face, int index, Vec3 result) {
private void loadVertexPosition(ShapePart face, int index, Vec3 result) {
ByteBuffer vertices = face.getVertices();
int offset = vertices.position() + index * getBytesPerVertex();
@ -168,7 +168,7 @@ public class WorldRenderProgram extends ShapeRenderProgram {
vertices.getFloat(offset + 2 * Float.BYTES));
}
private void saveVertexNormal(Face face, int index, Vec3 normal) {
private void saveVertexNormal(ShapePart face, int index, Vec3 normal) {
ByteBuffer vertices = face.getVertices();
int offset = vertices.position() + index * getBytesPerVertex()
+ (3 * Float.BYTES + 4 * Float.BYTES + 2 * Float.BYTES);

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.world;
import java.util.Collections;
@ -27,24 +27,29 @@ import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.client.world.block.BlockRender;
import ru.windcorp.progressia.client.world.block.BlockRenderRegistry;
import ru.windcorp.progressia.client.world.tile.TileRender;
import ru.windcorp.progressia.client.world.tile.TileRenderReference;
import ru.windcorp.progressia.client.world.tile.TileRenderRegistry;
import ru.windcorp.progressia.client.world.tile.TileRenderStack;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.generic.GenericChunk;
import ru.windcorp.progressia.common.world.tile.TileDataStack;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.TileDataReference;
import ru.windcorp.progressia.common.world.TileDataStack;
import ru.windcorp.progressia.common.world.generic.ChunkGenericRO;
import ru.windcorp.progressia.common.world.rels.AbsFace;
import ru.windcorp.progressia.common.world.rels.BlockFace;
import ru.windcorp.progressia.common.world.rels.RelFace;
public class ChunkRender implements GenericChunk<ChunkRender, BlockRender, TileRender, TileRenderStack> {
public class ChunkRender
implements ChunkGenericRO<BlockRender, TileRender, TileRenderStack, TileRenderReference, ChunkRender> {
private final WorldRender world;
private final ChunkData data;
private final DefaultChunkData data;
private final ChunkRenderModel model;
private final Map<TileDataStack, TileRenderStackImpl> tileRenderLists = Collections
.synchronizedMap(new WeakHashMap<>());
.synchronizedMap(new WeakHashMap<>());
public ChunkRender(WorldRender world, ChunkData data) {
public ChunkRender(WorldRender world, DefaultChunkData data) {
this.world = world;
this.data = data;
this.model = new ChunkRenderModel(this);
@ -54,10 +59,17 @@ public class ChunkRender implements GenericChunk<ChunkRender, BlockRender, TileR
public Vec3i getPosition() {
return getData().getPosition();
}
@Override
public AbsFace getUp() {
return getData().getUp();
}
@Override
public BlockRender getBlock(Vec3i posInChunk) {
return BlockRenderRegistry.getInstance().get(getData().getBlock(posInChunk).getId());
return BlockRenderRegistry.getInstance().get(
getData().getBlock(posInChunk).getId()
);
}
@Override
@ -71,18 +83,21 @@ public class ChunkRender implements GenericChunk<ChunkRender, BlockRender, TileR
}
private TileRenderStack getTileStackWrapper(TileDataStack tileDataList) {
return tileRenderLists.computeIfAbsent(tileDataList, TileRenderStackImpl::new);
return tileRenderLists.computeIfAbsent(
tileDataList,
TileRenderStackImpl::new
);
}
public WorldRender getWorld() {
return world;
}
public ChunkData getData() {
public DefaultChunkData getData() {
return data;
}
public synchronized void markForUpdate() {
public void markForUpdate() {
getWorld().markChunkForUpdate(getPosition());
}
@ -95,6 +110,28 @@ public class ChunkRender implements GenericChunk<ChunkRender, BlockRender, TileR
}
private class TileRenderStackImpl extends TileRenderStack {
private class TileRenderReferenceImpl implements TileRenderReference {
private final TileDataReference parent;
public TileRenderReferenceImpl(TileDataReference parent) {
this.parent = parent;
}
@Override
public TileRender get() {
return TileRenderRegistry.getInstance().get(parent.get().getId());
}
@Override
public int getIndex() {
return parent.getIndex();
}
@Override
public TileRenderStack getStack() {
return TileRenderStackImpl.this;
}
}
private final TileDataStack parent;
@ -113,9 +150,24 @@ public class ChunkRender implements GenericChunk<ChunkRender, BlockRender, TileR
}
@Override
public BlockFace getFace() {
public RelFace getFace() {
return parent.getFace();
}
@Override
public TileRenderReference getReference(int index) {
return new TileRenderReferenceImpl(parent.getReference(index));
}
@Override
public int getIndexByTag(int tag) {
return parent.getIndexByTag(tag);
}
@Override
public int getTagByIndex(int index) {
return parent.getTagByIndex(index);
}
@Override
public TileRender get(int index) {

View File

@ -35,13 +35,15 @@ import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerRegistry;
import ru.windcorp.progressia.client.world.tile.TileRender;
import ru.windcorp.progressia.client.world.tile.TileRenderNone;
import ru.windcorp.progressia.client.world.tile.TileRenderStack;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.generic.GenericChunks;
import ru.windcorp.progressia.common.world.rels.AxisRotations;
import ru.windcorp.progressia.common.world.rels.RelFace;
public class ChunkRenderModel implements Renderable {
private final ChunkRender chunk;
private final Collection<ChunkRenderOptimizer> optimizers = new ArrayList<>();
private Model model = null;
@ -51,42 +53,48 @@ public class ChunkRenderModel implements Renderable {
@Override
public void render(ShapeRenderHelper renderer) {
if (model == null)
return;
renderer.pushTransform().translate(chunk.getX() * ChunkData.BLOCKS_PER_CHUNK,
chunk.getY() * ChunkData.BLOCKS_PER_CHUNK, chunk.getZ() * ChunkData.BLOCKS_PER_CHUNK);
if (model == null) return;
float offset = DefaultChunkData.BLOCKS_PER_CHUNK / 2 - 0.5f;
renderer.pushTransform().translate(
chunk.getX() * DefaultChunkData.BLOCKS_PER_CHUNK,
chunk.getY() * DefaultChunkData.BLOCKS_PER_CHUNK,
chunk.getZ() * DefaultChunkData.BLOCKS_PER_CHUNK
).translate(offset, offset, offset)
.mul(AxisRotations.getResolutionMatrix4(chunk.getUp()))
.translate(-offset, -offset, -offset);
model.render(renderer);
renderer.popTransform();
}
public void update() {
setupCROs();
StaticModel.Builder sink = StaticModel.builder();
optimizers.forEach(ChunkRenderOptimizer::startRender);
chunk.forEachBiC(blockInChunk -> {
processBlockAndTiles(blockInChunk, sink);
GenericChunks.forEachBiC(relBlockInChunk -> {
processBlockAndTiles(relBlockInChunk, sink);
});
for (ChunkRenderOptimizer optimizer : optimizers) {
Renderable renderable = optimizer.endRender();
if (renderable != null) {
sink.addPart(renderable);
}
}
this.model = sink.build();
this.optimizers.clear();
}
private void setupCROs() {
Set<String> ids = ChunkRenderOptimizerRegistry.getInstance().keySet();
for (String id : ids) {
ChunkRenderOptimizer optimizer = ChunkRenderOptimizerRegistry.getInstance().create(id);
optimizer.setup(chunk);
@ -94,61 +102,65 @@ public class ChunkRenderModel implements Renderable {
}
}
private void processBlockAndTiles(Vec3i blockInChunk, Builder sink) {
processBlock(blockInChunk, sink);
for (BlockFace face : BlockFace.getFaces()) {
processTileStack(blockInChunk, face, sink);
private void processBlockAndTiles(Vec3i relBlockInChunk, Builder sink) {
processBlock(relBlockInChunk, sink);
for (RelFace face : RelFace.getFaces()) {
processTileStack(relBlockInChunk, face, sink);
}
}
private void processBlock(Vec3i blockInChunk, Builder sink) {
BlockRender block = chunk.getBlock(blockInChunk);
private void processBlock(Vec3i relBlockInChunk, Builder sink) {
BlockRender block = chunk.getBlockRel(relBlockInChunk);
if (block instanceof BlockRenderNone) {
return;
}
if (block.needsOwnRenderable()) {
sink.addPart(block.createRenderable(chunk.getData(), blockInChunk),
new Mat4().identity().translate(blockInChunk.x, blockInChunk.y, blockInChunk.z));
sink.addPart(
block.createRenderable(chunk.getData(), relBlockInChunk),
new Mat4().identity().translate(relBlockInChunk.x, relBlockInChunk.y, relBlockInChunk.z)
);
}
processBlockWithCROs(block, blockInChunk);
processBlockWithCROs(block, relBlockInChunk);
}
private void processBlockWithCROs(BlockRender block, Vec3i blockInChunk) {
private void processBlockWithCROs(BlockRender block, Vec3i relBlockInChunk) {
for (ChunkRenderOptimizer optimizer : optimizers) {
optimizer.addBlock(block, blockInChunk);
optimizer.addBlock(block, relBlockInChunk);
}
}
private void processTileStack(Vec3i blockInChunk, BlockFace face, Builder sink) {
TileRenderStack trs = chunk.getTilesOrNull(blockInChunk, face);
private void processTileStack(Vec3i relBlockInChunk, RelFace face, Builder sink) {
TileRenderStack trs = chunk.getTilesOrNullRel(relBlockInChunk, face);
if (trs == null || trs.isEmpty()) {
return;
}
trs.forEach(tile -> processTile(tile, blockInChunk, face, sink));
trs.forEach(tile -> processTile(tile, relBlockInChunk, face, sink));
}
private void processTile(TileRender tile, Vec3i blockInChunk, BlockFace face, Builder sink) {
private void processTile(TileRender tile, Vec3i relBlockInChunk, RelFace face, Builder sink) {
if (tile instanceof TileRenderNone) {
return;
}
if (tile.needsOwnRenderable()) {
sink.addPart(tile.createRenderable(chunk.getData(), blockInChunk, face),
new Mat4().identity().translate(blockInChunk.x, blockInChunk.y, blockInChunk.z));
sink.addPart(
tile.createRenderable(chunk.getData(), relBlockInChunk, face),
new Mat4().identity().translate(relBlockInChunk.x, relBlockInChunk.y, relBlockInChunk.z)
);
}
processTileWithCROs(tile, blockInChunk, face);
processTileWithCROs(tile, relBlockInChunk, face);
}
private void processTileWithCROs(TileRender tile, Vec3i blockInChunk, BlockFace face) {
private void processTileWithCROs(TileRender tile, Vec3i relBlockInChunk, RelFace face) {
for (ChunkRenderOptimizer optimizer : optimizers) {
optimizer.addTile(tile, blockInChunk, face);
optimizer.addTile(tile, relBlockInChunk, face);
}
}

View File

@ -15,16 +15,17 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.world;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.ChunkDataListener;
import ru.windcorp.progressia.common.world.block.BlockData;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.rels.AbsFace;
import ru.windcorp.progressia.common.world.rels.RelFace;
import ru.windcorp.progressia.common.world.tile.TileData;
class ChunkUpdateListener implements ChunkDataListener {
@ -36,58 +37,63 @@ class ChunkUpdateListener implements ChunkDataListener {
}
@Override
public void onChunkChanged(ChunkData chunk) {
public void onChunkChanged(DefaultChunkData chunk) {
world.getChunk(chunk).markForUpdate();
}
@Override
public void onChunkLoaded(ChunkData chunk) {
public void onChunkLoaded(DefaultChunkData chunk) {
Vec3i cursor = new Vec3i();
for (BlockFace face : BlockFace.getFaces()) {
for (AbsFace face : AbsFace.getFaces()) {
cursor.set(chunk.getX(), chunk.getY(), chunk.getZ());
cursor.add(face.getVector());
world.markChunkForUpdate(cursor);
}
}
@Override
public void onChunkBlockChanged(ChunkData chunk, Vec3i blockInChunk, BlockData previous, BlockData current) {
public void onChunkBlockChanged(DefaultChunkData chunk, Vec3i blockInChunk, BlockData previous, BlockData current) {
onLocationChanged(chunk, blockInChunk);
}
@Override
public void onChunkTilesChanged(
DefaultChunkData chunk,
Vec3i blockInChunk,
RelFace face,
TileData tile,
boolean wasAdded
) {
onLocationChanged(chunk, blockInChunk);
}
@Override
public void onChunkTilesChanged(ChunkData chunk, Vec3i blockInChunk, BlockFace face, TileData tile,
boolean wasAdded) {
onLocationChanged(chunk, blockInChunk);
}
private void onLocationChanged(ChunkData chunk, Vec3i blockInChunk) {
private void onLocationChanged(DefaultChunkData chunk, Vec3i blockInChunk) {
Vec3i chunkPos = Vectors.grab3i().set(chunk.getX(), chunk.getY(), chunk.getZ());
checkCoordinate(blockInChunk, chunkPos, VectorUtil.Axis.X);
checkCoordinate(blockInChunk, chunkPos, VectorUtil.Axis.Y);
checkCoordinate(blockInChunk, chunkPos, VectorUtil.Axis.Z);
Vectors.release(chunkPos);
}
private void checkCoordinate(Vec3i blockInChunk, Vec3i chunkPos, VectorUtil.Axis axis) {
int block = VectorUtil.get(blockInChunk, axis);
int diff = 0;
if (block == 0) {
diff = -1;
} else if (block == ChunkData.BLOCKS_PER_CHUNK - 1) {
} else if (block == DefaultChunkData.BLOCKS_PER_CHUNK - 1) {
diff = +1;
} else {
return;
}
int previousChunkPos = VectorUtil.get(chunkPos, axis);
VectorUtil.set(chunkPos, axis, previousChunkPos + diff);
world.markChunkForUpdate(chunkPos);
VectorUtil.set(chunkPos, axis, previousChunkPos);
}

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.world;
import java.util.Collection;
@ -34,57 +34,58 @@ import ru.windcorp.progressia.client.world.block.BlockRender;
import ru.windcorp.progressia.client.world.entity.EntityRenderRegistry;
import ru.windcorp.progressia.client.world.entity.EntityRenderable;
import ru.windcorp.progressia.client.world.tile.TileRender;
import ru.windcorp.progressia.client.world.tile.TileRenderReference;
import ru.windcorp.progressia.client.world.tile.TileRenderStack;
import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.ChunkDataListeners;
import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.common.world.DefaultWorldData;
import ru.windcorp.progressia.common.world.WorldDataListener;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.generic.ChunkSet;
import ru.windcorp.progressia.common.world.generic.ChunkSets;
import ru.windcorp.progressia.common.world.generic.GenericWorld;
import ru.windcorp.progressia.common.world.generic.WorldGenericRO;
public class WorldRender
implements GenericWorld<BlockRender, TileRender, TileRenderStack, ChunkRender, EntityRenderable> {
implements WorldGenericRO<BlockRender, TileRender, TileRenderStack, TileRenderReference, ChunkRender, EntityRenderable> {
private final WorldData data;
private final DefaultWorldData data;
private final Client client;
private final Map<ChunkData, ChunkRender> chunks = Collections.synchronizedMap(new HashMap<>());
private final Map<DefaultChunkData, ChunkRender> chunks = Collections.synchronizedMap(new HashMap<>());
private final Map<EntityData, EntityRenderable> entityModels = Collections.synchronizedMap(new WeakHashMap<>());
private final ChunkSet chunksToUpdate = ChunkSets.newSyncHashSet();
public WorldRender(WorldData data, Client client) {
public WorldRender(DefaultWorldData data, Client client) {
this.data = data;
this.client = client;
data.addListener(ChunkDataListeners.createAdder(new ChunkUpdateListener(this)));
data.addListener(new WorldDataListener() {
@Override
public void onChunkLoaded(WorldData world, ChunkData chunk) {
public void onChunkLoaded(DefaultWorldData world, DefaultChunkData chunk) {
addChunk(chunk);
}
@Override
public void beforeChunkUnloaded(WorldData world, ChunkData chunk) {
public void beforeChunkUnloaded(DefaultWorldData world, DefaultChunkData chunk) {
removeChunk(chunk);
}
});
}
protected void addChunk(ChunkData chunk) {
protected void addChunk(DefaultChunkData chunk) {
chunks.put(chunk, new ChunkRender(WorldRender.this, chunk));
markChunkForUpdate(chunk.getPosition());
}
protected void removeChunk(ChunkData chunk) {
protected void removeChunk(DefaultChunkData chunk) {
chunks.remove(chunk);
}
public WorldData getData() {
public DefaultWorldData getData() {
return data;
}
@ -92,7 +93,7 @@ public class WorldRender
return client;
}
public ChunkRender getChunk(ChunkData chunkData) {
public ChunkRender getChunk(DefaultChunkData chunkData) {
return chunks.get(chunkData);
}
@ -110,6 +111,13 @@ public class WorldRender
public Collection<EntityRenderable> getEntities() {
return entityModels.values();
}
@Override
public EntityRenderable getEntity(long entityId) {
EntityData entityData = getData().getEntity(entityId);
if (entityData == null) return null;
return getEntityRenderable(entityData);
}
public void render(ShapeRenderHelper renderer) {
updateChunks();
@ -207,11 +215,15 @@ public class WorldRender
}
public EntityRenderable getEntityRenderable(EntityData entity) {
return entityModels.computeIfAbsent(entity, WorldRender::createEntityRenderable);
return entityModels.computeIfAbsent(
entity,
WorldRender::createEntityRenderable
);
}
private static EntityRenderable createEntityRenderable(EntityData entity) {
return EntityRenderRegistry.getInstance().get(entity.getId()).createRenderable(entity);
return EntityRenderRegistry.getInstance().get(entity.getId())
.createRenderable(entity);
}
public void markChunkForUpdate(Vec3i chunkPos) {

View File

@ -15,27 +15,22 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.world.block;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.generic.GenericBlock;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.generic.BlockGeneric;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.model.Renderable;
public abstract class BlockRender extends Namespaced implements GenericBlock {
public abstract class BlockRender extends Namespaced implements BlockGeneric {
public BlockRender(String id) {
super(id);
}
public void render(ShapeRenderHelper renderer) {
throw new UnsupportedOperationException("BlockRender.render() not implemented in " + this);
}
public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk) {
public Renderable createRenderable(DefaultChunkData chunk, Vec3i relBlockInChunk) {
return null;
}

View File

@ -21,7 +21,7 @@ package ru.windcorp.progressia.client.world.block;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.model.EmptyModel;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.DefaultChunkData;
public class BlockRenderNone extends BlockRender {
@ -30,7 +30,7 @@ public class BlockRenderNone extends BlockRender {
}
@Override
public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk) {
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk) {
return EmptyModel.getInstance();
}

View File

@ -15,25 +15,60 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.world.block;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.rels.RelFace;
public class BlockRenderOpaqueCube extends BlockRenderTexturedCube {
public BlockRenderOpaqueCube(String id, Texture topTexture, Texture bottomTexture, Texture northTexture,
Texture southTexture, Texture eastTexture, Texture westTexture) {
super(id, topTexture, bottomTexture, northTexture, southTexture, eastTexture, westTexture);
public BlockRenderOpaqueCube(
String id,
Texture topTexture,
Texture bottomTexture,
Texture northTexture,
Texture southTexture,
Texture westTexture,
Texture eastTexture
) {
super(
id,
topTexture,
bottomTexture,
northTexture,
southTexture,
westTexture,
eastTexture
);
}
public BlockRenderOpaqueCube(String id, Texture texture) {
this(id, texture, texture, texture, texture, texture, texture);
this(
id,
texture,
texture,
texture,
texture,
texture,
texture
);
}
public BlockRenderOpaqueCube(String id, Texture topTexture, Texture bottomTexture, Texture sideTexture) {
this(
id,
topTexture,
bottomTexture,
sideTexture,
sideTexture,
sideTexture,
sideTexture
);
}
@Override
public boolean isOpaque(BlockFace face) {
public boolean isOpaque(RelFace face) {
return true;
}

View File

@ -15,12 +15,11 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.world.block;
import static ru.windcorp.progressia.common.world.block.BlockFace.*;
import static ru.windcorp.progressia.common.world.rels.AbsFace.*;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
@ -29,69 +28,86 @@ import glm.vec._3.i.Vec3i;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.backend.Usage;
import ru.windcorp.progressia.client.graphics.model.Face;
import ru.windcorp.progressia.client.graphics.model.Faces;
import ru.windcorp.progressia.client.graphics.model.ShapePart;
import ru.windcorp.progressia.client.graphics.model.ShapeParts;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSurface.BlockOptimizedSurface;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.rels.AbsFace;
import ru.windcorp.progressia.common.world.rels.RelFace;
public abstract class BlockRenderTexturedCube extends BlockRender implements BlockOptimizedSurface {
public abstract class BlockRenderTexturedCube
extends BlockRender
implements BlockOptimizedSurface {
private final Map<BlockFace, Texture> textures = new HashMap<>();
private final Map<RelFace, Texture> textures;
public BlockRenderTexturedCube(String id, Texture topTexture, Texture bottomTexture, Texture northTexture,
Texture southTexture, Texture eastTexture, Texture westTexture) {
public BlockRenderTexturedCube(
String id,
Texture topTexture,
Texture bottomTexture,
Texture northTexture,
Texture southTexture,
Texture westTexture,
Texture eastTexture
) {
super(id);
textures.put(TOP, topTexture);
textures.put(BOTTOM, bottomTexture);
textures.put(NORTH, northTexture);
textures.put(SOUTH, southTexture);
textures.put(EAST, eastTexture);
textures.put(WEST, westTexture);
this.textures = RelFace.mapToFaces(topTexture, bottomTexture, northTexture, southTexture, westTexture, eastTexture);
}
public Texture getTexture(BlockFace blockFace) {
public Texture getTexture(RelFace blockFace) {
return textures.get(blockFace);
}
public Vec4 getColorMultiplier(BlockFace blockFace) {
public Vec4 getColorMultiplier(RelFace blockFace) {
return Colors.WHITE;
}
@Override
public final void getFaces(ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace, boolean inner,
Consumer<Face> output, Vec3 offset) {
public final void getShapeParts(
DefaultChunkData chunk, Vec3i blockInChunk, RelFace blockFace,
boolean inner,
Consumer<ShapePart> output,
Vec3 offset
) {
output.accept(createFace(chunk, blockInChunk, blockFace, inner, offset));
}
private Face createFace(ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace, boolean inner, Vec3 offset) {
return Faces.createBlockFace(WorldRenderProgram.getDefault(), getTexture(blockFace),
getColorMultiplier(blockFace), offset, blockFace, inner);
private ShapePart createFace(
DefaultChunkData chunk, Vec3i blockInChunk, RelFace blockFace,
boolean inner,
Vec3 offset
) {
return ShapeParts.createBlockFace(
WorldRenderProgram.getDefault(),
getTexture(blockFace),
getColorMultiplier(blockFace),
offset,
blockFace.resolve(AbsFace.POS_Z),
inner
);
}
@Override
public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk) {
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk) {
boolean opaque = isBlockOpaque();
Face[] faces = new Face[BLOCK_FACE_COUNT + (opaque ? BLOCK_FACE_COUNT : 0)];
ShapePart[] faces = new ShapePart[BLOCK_FACE_COUNT + (opaque ? BLOCK_FACE_COUNT : 0)];
for (int i = 0; i < BLOCK_FACE_COUNT; ++i) {
faces[i] = createFace(chunk, blockInChunk, BlockFace.getFaces().get(i), false, Vectors.ZERO_3);
faces[i] = createFace(chunk, blockInChunk, RelFace.getFaces().get(i), false, Vectors.ZERO_3);
}
if (!opaque) {
for (int i = 0; i < BLOCK_FACE_COUNT; ++i) {
faces[i + BLOCK_FACE_COUNT] = createFace(chunk, blockInChunk, BlockFace.getFaces().get(i), true,
Vectors.ZERO_3);
faces[i + BLOCK_FACE_COUNT] = createFace(chunk, blockInChunk, RelFace.getFaces().get(i), true, Vectors.ZERO_3);
}
}
return new Shape(Usage.STATIC, WorldRenderProgram.getDefault(), faces);
}

View File

@ -15,25 +15,60 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.world.block;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.rels.RelFace;
public class BlockRenderTransparentCube extends BlockRenderTexturedCube {
public BlockRenderTransparentCube(String id, Texture topTexture, Texture bottomTexture, Texture northTexture,
Texture southTexture, Texture eastTexture, Texture westTexture) {
super(id, topTexture, bottomTexture, northTexture, southTexture, eastTexture, westTexture);
public BlockRenderTransparentCube(
String id,
Texture topTexture,
Texture bottomTexture,
Texture northTexture,
Texture southTexture,
Texture westTexture,
Texture eastTexture
) {
super(
id,
topTexture,
bottomTexture,
northTexture,
southTexture,
westTexture,
eastTexture
);
}
public BlockRenderTransparentCube(String id, Texture texture) {
this(id, texture, texture, texture, texture, texture, texture);
this(
id,
texture,
texture,
texture,
texture,
texture,
texture
);
}
public BlockRenderTransparentCube(String id, Texture topTexture, Texture bottomTexture, Texture sideTexture) {
this(
id,
topTexture,
bottomTexture,
sideTexture,
sideTexture,
sideTexture,
sideTexture
);
}
@Override
public boolean isOpaque(BlockFace face) {
public boolean isOpaque(RelFace face) {
return false;
}

View File

@ -24,7 +24,7 @@ import ru.windcorp.progressia.client.world.ChunkRender;
import ru.windcorp.progressia.client.world.block.BlockRender;
import ru.windcorp.progressia.client.world.tile.TileRender;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.rels.RelFace;
/**
* Chunk render optimizer (CRO) is an object that produces optimized models for
@ -35,15 +35,22 @@ import ru.windcorp.progressia.common.world.block.BlockFace;
* tiles. An example of a CRO is {@link ChunkRenderOptimizerSurface}: it removes
* block surfaces and tiles that it knows cannot be seen, thus significantly
* reducing total polygon count.
* <h3>CRO lifecycle</h3> A CRO instance is created by
* {@link ChunkRenderOptimizerRegistry}. It may then be used to work on multiple
* chunks sequentially. Each chunk is processed in the following way:
* <p>
* As with everything related to rendering chunks, CROs are interacted with
* using the relative local chunk coordinate system. In this coordinate system,
* the coordinates are the chunk coordinates relativized using the chunks's up
* direction. In simpler terms, coordinates are {@code [0; BLOCKS_PER_CHUNK)}
* and Z is always up.
* <h3>CRO lifecycle</h3>
* A CRO instance is created by {@link ChunkRenderOptimizerRegistry}. It may
* then be used to work on multiple chunks sequentially. Each chunk is processed
* in the following way:
* <ol>
* <li>{@link #setup(ChunkRender)} is invoked to provide the {@link ChunkRender}
* instance.</li>
* <li>{@link #startRender()} is invoked. The CRO must reset its state.</li>
* <li>{@link #addBlock(BlockRender, Vec3i)} and
* {@link #addTile(TileRender, Vec3i, BlockFace)} are invoked for each block and
* {@link #addTile(TileRender, Vec3i, RelFace)} are invoked for each block and
* tile that this CRO should optimize. {@code addTile} specifies tiles in order
* of ascension within a tile stack.</li>
* <li>{@link #endRender()} is invoked. The CRO may perform any pending
@ -62,8 +69,7 @@ public abstract class ChunkRenderOptimizer extends Namespaced {
/**
* Creates a new CRO instance with the specified ID.
*
* @param id
* the ID of this CRO
* @param id the ID of this CRO
*/
public ChunkRenderOptimizer(String id) {
super(id);
@ -74,8 +80,7 @@ public abstract class ChunkRenderOptimizer extends Namespaced {
* specify the chunk. When overriding, {@code super.setup(chunk)} must be
* invoked.
*
* @param chunk
* the chunk that will be processed next
* @param chunk the chunk that will be processed next
*/
public void setup(ChunkRender chunk) {
this.chunk = chunk;
@ -99,13 +104,13 @@ public abstract class ChunkRenderOptimizer extends Namespaced {
* method is only invoked once per block. This method is not necessarily
* invoked for each block.
*
* @param block
* a {@link BlockRender} instance describing the block. It
* corresponds to {@code getChunk().getBlock(blockInChunk)}.
* @param blockInChunk
* the position of the block
* @param block a {@link BlockRender} instance describing the
* block.
* It corresponds to
* {@code getChunk().getBlock(blockInChunk)}.
* @param relBlockInChunk the relative position of the block
*/
public abstract void addBlock(BlockRender block, Vec3i blockInChunk);
public abstract void addBlock(BlockRender block, Vec3i relBlockInChunk);
/**
* Requests that this CRO processes the provided tile. This method may only
@ -114,14 +119,12 @@ public abstract class ChunkRenderOptimizer extends Namespaced {
* invoked for each tile. When multiple tiles in a tile stack are requested,
* this method is invoked for lower tiles first.
*
* @param tile
* a {@link BlockRender} instance describing the tile
* @param blockInChunk
* the position of the block that the tile belongs to
* @param blockFace
* the face that the tile belongs to
* @param tile a {@link BlockRender} instance describing the tile
* @param relBlockInChunk the relative position of the block that the tile
* belongs to
* @param blockFace the face that the tile belongs to
*/
public abstract void addTile(TileRender tile, Vec3i blockInChunk, BlockFace blockFace);
public abstract void addTile(TileRender tile, Vec3i relBlockInChunk, RelFace blockFace);
/**
* Requests that the CRO assembles and outputs its model. This method may

View File

@ -18,9 +18,9 @@
package ru.windcorp.progressia.client.world.cro;
import static ru.windcorp.progressia.common.world.ChunkData.BLOCKS_PER_CHUNK;
import static ru.windcorp.progressia.common.world.block.BlockFace.BLOCK_FACE_COUNT;
import static ru.windcorp.progressia.common.world.generic.GenericTileStack.TILES_PER_FACE;
import static ru.windcorp.progressia.common.world.DefaultChunkData.BLOCKS_PER_CHUNK;
import static ru.windcorp.progressia.common.world.generic.TileGenericStackRO.TILES_PER_FACE;
import static ru.windcorp.progressia.common.world.rels.AbsFace.BLOCK_FACE_COUNT;
import java.util.ArrayList;
import java.util.Collection;
@ -29,7 +29,7 @@ import java.util.function.Consumer;
import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.backend.Usage;
import ru.windcorp.progressia.client.graphics.model.Face;
import ru.windcorp.progressia.client.graphics.model.ShapePart;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
@ -37,8 +37,9 @@ import ru.windcorp.progressia.client.world.ChunkRender;
import ru.windcorp.progressia.client.world.block.BlockRender;
import ru.windcorp.progressia.client.world.tile.TileRender;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.generic.GenericChunks;
import ru.windcorp.progressia.common.world.rels.RelFace;
public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
@ -52,38 +53,42 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
private static interface OptimizedSurface {
/**
* Creates and outputs a set of faces that correspond to this surface.
* The coordinates of the face vertices must be in chunk coordinate
* system.
* Creates and outputs a set of shape parts that correspond to this
* surface. The coordinates of the face vertices must be in chunk
* coordinate system.
*
* @param chunk
* the chunk that contains the requested face
* @param blockInChunk
* the block in chunk
* @param blockFace
* the requested face
* @param inner
* whether this face should be visible from inside
* ({@code true}) or outside ({@code false})
* @param output
* a consumer that the created faces must be given to
* @param offset
* an additional offset that must be applied to all vertices
* @param chunk the chunk that contains the requested face
* @param relBlockInChunk the relative block in chunk
* @param blockFace the requested face
* @param inner whether this face should be visible from
* inside
* ({@code true}) or outside ({@code false})
* @param output a consumer that the created shape parts must
* be
* given to
* @param offset an additional offset that must be applied to
* all
* vertices
*/
void getFaces(ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace, boolean inner, Consumer<Face> output,
Vec3 offset /* kostyl 156% */
void getShapeParts(
DefaultChunkData chunk,
Vec3i relBlockInChunk,
RelFace blockFace,
boolean inner,
Consumer<ShapePart> output,
Vec3 offset /* kostyl 156% */
);
/**
* Returns the opacity of the surface identified by the provided
* {@link BlockFace}. Opaque surfaces prevent surfaces behind them from
* being included in chunk models.
* {@link RelFace}.
* Opaque surfaces prevent surfaces behind them from being included in
* chunk models.
*
* @param blockFace
* the face to query
* @param blockFace the face to query
* @return {@code true} iff the surface is opaque.
*/
boolean isOpaque(BlockFace blockFace);
boolean isOpaque(RelFace blockFace);
}
/**
@ -94,7 +99,8 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
/**
* Returns the opacity of the block. Opaque blocks do not expect that
* the camera can be inside them. Opaque blocks prevent surfaces that
* face them from being included in chunk models.
* face them
* from being included in chunk models.
*
* @return {@code true} iff the block is opaque.
*/
@ -157,29 +163,29 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
}
@Override
public void addBlock(BlockRender block, Vec3i pos) {
public void addBlock(BlockRender block, Vec3i relBlockInChunk) {
if (!(block instanceof BlockOptimizedSurface))
return;
BlockOptimizedSurface bos = (BlockOptimizedSurface) block;
addBlock(pos, bos);
addBlock(relBlockInChunk, bos);
}
@Override
public void addTile(TileRender tile, Vec3i pos, BlockFace face) {
public void addTile(TileRender tile, Vec3i relBlockInChunk, RelFace face) {
if (!(tile instanceof TileOptimizedSurface))
return;
TileOptimizedSurface tos = (TileOptimizedSurface) tile;
addTile(pos, face, tos);
addTile(relBlockInChunk, face, tos);
}
protected void addBlock(Vec3i pos, BlockOptimizedSurface block) {
getBlock(pos).block = block;
private void addBlock(Vec3i relBlockInChunk, BlockOptimizedSurface block) {
getBlock(relBlockInChunk).block = block;
}
private void addTile(Vec3i pos, BlockFace face, TileOptimizedSurface tile) {
FaceInfo faceInfo = getFace(pos, face);
private void addTile(Vec3i relBlockInChunk, RelFace face, TileOptimizedSurface tile) {
FaceInfo faceInfo = getFace(relBlockInChunk, face);
int index = faceInfo.tileCount;
faceInfo.tileCount++;
@ -195,119 +201,130 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
}
}
protected BlockInfo getBlock(Vec3i cursor) {
return data[cursor.x][cursor.y][cursor.z];
protected BlockInfo getBlock(Vec3i relBlockInChunk) {
return data[relBlockInChunk.x][relBlockInChunk.y][relBlockInChunk.z];
}
protected FaceInfo getFace(Vec3i cursor, BlockFace face) {
return getBlock(cursor).faces[face.getId()];
protected FaceInfo getFace(Vec3i relBlockInChunk, RelFace face) {
return getBlock(relBlockInChunk).faces[face.getId()];
}
@Override
public Renderable endRender() {
Collection<Face> shapeFaces = new ArrayList<>(BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * 3);
Collection<ShapePart> shapeParts = new ArrayList<>(
BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * 3
);
Vec3i cursor = new Vec3i();
Consumer<Face> consumer = shapeFaces::add;
Consumer<ShapePart> consumer = shapeParts::add;
for (cursor.x = 0; cursor.x < BLOCKS_PER_CHUNK; ++cursor.x) {
for (cursor.y = 0; cursor.y < BLOCKS_PER_CHUNK; ++cursor.y) {
for (cursor.z = 0; cursor.z < BLOCKS_PER_CHUNK; ++cursor.z) {
processInnerFaces(cursor, consumer);
processOuterFaces(cursor, consumer);
}
}
}
GenericChunks.forEachBiC(relBlockInChunk -> {
processInnerFaces(relBlockInChunk, consumer);
processOuterFaces(relBlockInChunk, consumer);
});
if (shapeFaces.isEmpty()) {
if (shapeParts.isEmpty()) {
return null;
}
return new Shape(Usage.STATIC, WorldRenderProgram.getDefault(),
shapeFaces.toArray(new Face[shapeFaces.size()]));
return new Shape(
Usage.STATIC,
WorldRenderProgram.getDefault(),
shapeParts.toArray(new ShapePart[shapeParts.size()])
);
}
private void processOuterFaces(Vec3i blockInChunk, Consumer<Face> output) {
for (BlockFace blockFace : BlockFace.getFaces()) {
processOuterFace(blockInChunk, blockFace, output);
private void processOuterFaces(
Vec3i relBlockInChunk,
Consumer<ShapePart> output
) {
for (RelFace blockFace : RelFace.getFaces()) {
processOuterFace(relBlockInChunk, blockFace, output);
}
}
private void processOuterFace(Vec3i blockInChunk, BlockFace blockFace, Consumer<Face> output) {
if (!shouldRenderOuterFace(blockInChunk, blockFace))
private void processOuterFace(Vec3i relBlockInChunk, RelFace blockFace, Consumer<ShapePart> output) {
if (!shouldRenderOuterFace(relBlockInChunk, blockFace))
return;
FaceInfo info = getFace(blockInChunk, blockFace);
FaceInfo info = getFace(relBlockInChunk, blockFace);
if (info.tileCount == 0 && info.block.block == null)
return;
Vec3 faceOrigin = new Vec3(blockInChunk.x, blockInChunk.y, blockInChunk.z);
Vec3 offset = new Vec3(blockFace.getFloatVector()).mul(OVERLAY_OFFSET);
Vec3 faceOrigin = new Vec3(relBlockInChunk.x, relBlockInChunk.y, relBlockInChunk.z);
Vec3 offset = new Vec3(blockFace.getRelFloatVector()).mul(OVERLAY_OFFSET);
for (int layer = info.topOpaqueSurface; layer < info.tileCount; ++layer) {
for (
int layer = info.topOpaqueSurface;
layer < info.tileCount;
++layer
) {
OptimizedSurface surface = info.getSurface(layer);
if (surface == null)
continue; // layer may be BLOCK_LAYER, then block may be null
surface.getFaces(chunk.getData(), blockInChunk, blockFace, false, output, faceOrigin);
surface.getShapeParts(chunk.getData(), relBlockInChunk, blockFace, false, output, faceOrigin);
faceOrigin.add(offset);
}
}
private void processInnerFaces(Vec3i blockInChunk, Consumer<Face> output) {
for (BlockFace blockFace : BlockFace.getFaces()) {
processInnerFace(blockInChunk, blockFace, output);
private void processInnerFaces(Vec3i relBlockInChunk, Consumer<ShapePart> output) {
for (RelFace blockFace : RelFace.getFaces()) {
processInnerFace(relBlockInChunk, blockFace, output);
}
}
private void processInnerFace(Vec3i blockInChunk, BlockFace blockFace, Consumer<Face> output) {
if (!shouldRenderInnerFace(blockInChunk, blockFace))
private void processInnerFace(Vec3i relBlockInChunk, RelFace blockFace, Consumer<ShapePart> output) {
if (!shouldRenderInnerFace(relBlockInChunk, blockFace))
return;
FaceInfo info = getFace(blockInChunk, blockFace);
FaceInfo info = getFace(relBlockInChunk, blockFace);
if (info.tileCount == 0 && info.block.block == null)
return;
Vec3 faceOrigin = new Vec3(blockInChunk.x, blockInChunk.y, blockInChunk.z);
Vec3 offset = new Vec3(blockFace.getFloatVector()).mul(OVERLAY_OFFSET);
Vec3 faceOrigin = new Vec3(relBlockInChunk.x, relBlockInChunk.y, relBlockInChunk.z);
Vec3 offset = new Vec3(blockFace.getRelFloatVector()).mul(OVERLAY_OFFSET);
for (int layer = FaceInfo.BLOCK_LAYER; layer <= info.bottomOpaqueSurface && layer < info.tileCount; ++layer) {
for (
int layer = FaceInfo.BLOCK_LAYER;
layer <= info.bottomOpaqueSurface && layer < info.tileCount;
++layer
) {
OptimizedSurface surface = info.getSurface(layer);
if (surface == null)
continue; // layer may be BLOCK_LAYER, then block may be null
surface.getFaces(chunk.getData(), blockInChunk, blockFace, true, output, faceOrigin);
surface.getShapeParts(chunk.getData(), relBlockInChunk, blockFace, true, output, faceOrigin);
faceOrigin.add(offset);
}
}
private boolean shouldRenderOuterFace(Vec3i blockInChunk, BlockFace face) {
blockInChunk.add(face.getVector());
private boolean shouldRenderOuterFace(Vec3i relBlockInChunk, RelFace face) {
relBlockInChunk.add(face.getRelVector());
try {
return shouldRenderWhenFacing(blockInChunk, face);
return shouldRenderWhenFacing(relBlockInChunk, face);
} finally {
blockInChunk.sub(face.getVector());
relBlockInChunk.sub(face.getRelVector());
}
}
private boolean shouldRenderInnerFace(Vec3i blockInChunk, BlockFace face) {
return shouldRenderWhenFacing(blockInChunk, face);
private boolean shouldRenderInnerFace(Vec3i relBlockInChunk, RelFace face) {
return shouldRenderWhenFacing(relBlockInChunk, face);
}
private boolean shouldRenderWhenFacing(Vec3i blockInChunk, BlockFace face) {
if (chunk.containsBiC(blockInChunk)) {
return shouldRenderWhenFacingLocal(blockInChunk, face);
private boolean shouldRenderWhenFacing(Vec3i relBlockInChunk, RelFace face) {
if (GenericChunks.containsBiC(relBlockInChunk)) {
return shouldRenderWhenFacingLocal(relBlockInChunk, face);
} else {
return shouldRenderWhenFacingNeighbor(blockInChunk, face);
return shouldRenderWhenFacingNeighbor(relBlockInChunk, face);
}
}
private boolean shouldRenderWhenFacingLocal(Vec3i blockInChunk, BlockFace face) {
BlockOptimizedSurface block = getBlock(blockInChunk).block;
private boolean shouldRenderWhenFacingLocal(Vec3i relBlockInChunk, RelFace face) {
BlockOptimizedSurface block = getBlock(relBlockInChunk).block;
if (block == null) {
return true;
@ -319,33 +336,38 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
return true;
}
private boolean shouldRenderWhenFacingNeighbor(Vec3i blockInLocalChunk, BlockFace face) {
Vec3i blockInChunk = Vectors.grab3i().set(blockInLocalChunk.x, blockInLocalChunk.y, blockInLocalChunk.z);
private boolean shouldRenderWhenFacingNeighbor(Vec3i relBlockInLocalChunk, RelFace face) {
Vec3i blockInChunk = Vectors.grab3i();
chunk.resolve(relBlockInLocalChunk, blockInChunk);
Vec3i chunkPos = Vectors.grab3i().set(chunk.getX(), chunk.getY(), chunk.getZ());
try {
// Determine blockInChunk and chunkPos
if (blockInLocalChunk.x == -1) {
if (blockInChunk.x == -1) {
blockInChunk.x = BLOCKS_PER_CHUNK - 1;
chunkPos.x -= 1;
} else if (blockInLocalChunk.x == BLOCKS_PER_CHUNK) {
} else if (blockInChunk.x == BLOCKS_PER_CHUNK) {
blockInChunk.x = 0;
chunkPos.x += 1;
} else if (blockInLocalChunk.y == -1) {
} else if (blockInChunk.y == -1) {
blockInChunk.y = BLOCKS_PER_CHUNK - 1;
chunkPos.y -= 1;
} else if (blockInLocalChunk.y == BLOCKS_PER_CHUNK) {
} else if (blockInChunk.y == BLOCKS_PER_CHUNK) {
blockInChunk.y = 0;
chunkPos.y += 1;
} else if (blockInLocalChunk.z == -1) {
} else if (blockInChunk.z == -1) {
blockInChunk.z = BLOCKS_PER_CHUNK - 1;
chunkPos.z -= 1;
} else if (blockInLocalChunk.z == BLOCKS_PER_CHUNK) {
} else if (blockInChunk.z == BLOCKS_PER_CHUNK) {
blockInChunk.z = 0;
chunkPos.z += 1;
} else {
throw new AssertionError("Requested incorrent neighbor (" + blockInLocalChunk.x + "; "
+ blockInLocalChunk.y + "; " + blockInLocalChunk.z + ")");
throw new AssertionError(
"Requested incorrent neighbor ("
+ relBlockInLocalChunk.x + "; "
+ relBlockInLocalChunk.y + "; "
+ relBlockInLocalChunk.z + ")"
);
}
ChunkRender chunk = this.chunk.getWorld().getChunk(chunkPos);
@ -357,8 +379,11 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
return true;
BlockOptimizedSurface bos = (BlockOptimizedSurface) block;
if (!bos.isOpaque(face))
RelFace rotatedFace = face.rotate(this.chunk.getUp(), chunk.getUp());
if (!bos.isOpaque(rotatedFace)) {
return true;
}
return false;

View File

@ -19,18 +19,45 @@
package ru.windcorp.progressia.client.world.entity;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.generic.GenericEntity;
import ru.windcorp.progressia.common.world.generic.EntityGeneric;
public abstract class EntityRenderable implements Renderable, GenericEntity {
public abstract class EntityRenderable implements Renderable, EntityGeneric {
private final EntityData data;
private long stateComputedForFrame = -1;
public EntityRenderable(EntityData data) {
this.data = data;
}
/**
* Updates the state of this model. This method is invoked exactly once per
* renderable per frame before this entity is queried for the first time.
*/
protected void update() {
// Do nothing
}
private void updateIfNecessary() {
if (stateComputedForFrame != GraphicsInterface.getFramesRendered()) {
update();
stateComputedForFrame = GraphicsInterface.getFramesRendered();
}
}
@Override
public final void render(ShapeRenderHelper renderer) {
updateIfNecessary();
doRender(renderer);
}
protected abstract void doRender(ShapeRenderHelper renderer);
public EntityData getData() {
return data;
}
@ -44,8 +71,42 @@ public abstract class EntityRenderable implements Renderable, GenericEntity {
public String getId() {
return getData().getId();
}
@Override
public long getEntityId() {
return getData().getEntityId();
}
public void getViewPoint(Vec3 output) {
public final Vec3 getLookingAt(Vec3 output) {
if (output == null) output = new Vec3();
updateIfNecessary();
doGetLookingAt(output);
return output;
}
protected void doGetLookingAt(Vec3 output) {
output.set(getData().getLookingAt());
}
public final Vec3 getUpVector(Vec3 output) {
if (output == null) output = new Vec3();
updateIfNecessary();
doGetUpVector(output);
return output;
}
protected void doGetUpVector(Vec3 output) {
output.set(getData().getUpVector());
}
public final Vec3 getViewPoint(Vec3 output) {
if (output == null) output = new Vec3();
updateIfNecessary();
doGetViewPoint(output);
return output;
}
protected void doGetViewPoint(Vec3 output) {
output.set(0, 0, 0);
}

View File

@ -39,6 +39,7 @@ public class HumanoidModel extends NPedModel {
@Override
protected void applyTransform(Mat4 mat, NPedModel model) {
super.applyTransform(mat, model);
float phase = model.getWalkingFrequency() * model.getWalkingParameter() + animationOffset;
float value = sin(phase);
float amplitude = getSwingAmplitude((HumanoidModel) model) * model.getVelocityParameter();

View File

@ -15,14 +15,11 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.world.entity;
import static java.lang.Math.atan2;
import static java.lang.Math.min;
import static java.lang.Math.pow;
import static java.lang.Math.toRadians;
import static ru.windcorp.progressia.common.util.FloatMathUtil.normalizeAngle;
import glm.Glm;
import glm.mat._4.Mat4;
@ -32,6 +29,9 @@ import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.common.Units;
import ru.windcorp.progressia.common.util.FloatMathUtil;
import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.entity.EntityData;
public abstract class NPedModel extends EntityRenderable {
@ -47,30 +47,34 @@ public abstract class NPedModel extends EntityRenderable {
}
}
protected void render(ShapeRenderHelper renderer, NPedModel model) {
renderer.pushTransform().translate(translation);
protected void render(
ShapeRenderHelper renderer,
NPedModel model
) {
applyTransform(renderer.pushTransform(), model);
renderable.render(renderer);
renderer.popTransform();
renderer.popTransform();
}
protected abstract void applyTransform(Mat4 mat, NPedModel model);
protected void applyTransform(Mat4 mat, NPedModel model) {
mat.translate(getTranslation());
}
public Vec3 getTranslation() {
return translation;
}
public Mat4 getTransform(Mat4 output, NPedModel model) {
if (output == null) output = new Mat4().identity();
applyTransform(output, model);
return output;
}
}
public static class Body extends BodyPart {
public Body(Renderable renderable) {
super(renderable, null);
}
@Override
protected void applyTransform(Mat4 mat, NPedModel model) {
// Do nothing
}
}
public static class Head extends BodyPart {
@ -79,7 +83,13 @@ public abstract class NPedModel extends EntityRenderable {
private final Vec3 viewPoint;
public Head(Renderable renderable, Vec3 joint, double maxYawDegrees, double maxPitchDegrees, Vec3 viewPoint) {
public Head(
Renderable renderable,
Vec3 joint,
double maxYawDegrees,
double maxPitchDegrees,
Vec3 viewPoint
) {
super(renderable, joint);
this.maxYaw = (float) toRadians(maxYawDegrees);
this.maxPitch = (float) toRadians(maxPitchDegrees);
@ -88,7 +98,8 @@ public abstract class NPedModel extends EntityRenderable {
@Override
protected void applyTransform(Mat4 mat, NPedModel model) {
mat.rotateZ(model.getHeadYaw()).rotateY(model.getHeadPitch());
super.applyTransform(mat, model);
mat.rotateZ(-model.getHeadYaw()).rotateY(-model.getHeadPitch());
}
public Vec3 getViewPoint() {
@ -96,6 +107,8 @@ public abstract class NPedModel extends EntityRenderable {
}
}
public static boolean flag;
protected final Body body;
protected final Head head;
@ -119,7 +132,9 @@ public abstract class NPedModel extends EntityRenderable {
private float walkingFrequency;
private float bodyYaw = Float.NaN;
private final Vec3 bodyLookingAt = new Vec3().set(0);
private final Mat4 bodyTransform = new Mat4();
private float headYaw;
private float headPitch;
@ -129,63 +144,121 @@ public abstract class NPedModel extends EntityRenderable {
this.head = head;
this.scale = scale;
evaluateAngles();
computeRotations();
}
@Override
public void render(ShapeRenderHelper renderer) {
renderer.pushTransform().scale(scale).rotateZ(bodyYaw);
protected void doRender(ShapeRenderHelper renderer) {
renderer.pushTransform().scale(scale).mul(bodyTransform);
renderBodyParts(renderer);
renderer.popTransform();
accountForVelocity();
evaluateAngles();
}
protected void renderBodyParts(ShapeRenderHelper renderer) {
body.render(renderer, this);
head.render(renderer, this);
}
private void evaluateAngles() {
float globalYaw = normalizeAngle(getData().getYaw());
if (Float.isNaN(bodyYaw)) {
bodyYaw = globalYaw;
headYaw = 0;
} else {
headYaw = normalizeAngle(globalYaw - bodyYaw);
if (headYaw > +head.maxYaw) {
bodyYaw += headYaw - +head.maxYaw;
headYaw = +head.maxYaw;
} else if (headYaw < -head.maxYaw) {
bodyYaw += headYaw - -head.maxYaw;
headYaw = -head.maxYaw;
}
}
bodyYaw = normalizeAngle(bodyYaw);
headPitch = Glm.clamp(getData().getPitch(), -head.maxPitch, head.maxPitch);
@Override
protected void update() {
advanceTime();
computeRotations();
}
private void accountForVelocity() {
Vec3 horizontal = new Vec3(getData().getVelocity());
horizontal.z = 0;
private void computeRotations() {
if (!bodyLookingAt.any()) {
getData().getForwardVector(bodyLookingAt);
headYaw = 0;
} else {
ensureBodyLookingAtIsPerpendicularToUpVector();
computeDesiredHeadYaw();
clampHeadYawAndChangeBodyLookingAt();
}
recomputeBodyTransform();
setHeadPitch();
}
private void ensureBodyLookingAtIsPerpendicularToUpVector() {
Vec3 up = getData().getUpVector();
if (up.dot(bodyLookingAt) > 1 - 1e-4) return;
Vec3 tmp = Vectors.grab3();
tmp.set(up).mul(-up.dot(bodyLookingAt)).add(bodyLookingAt);
float tmpLength = tmp.length();
if (tmpLength > 1e-4) {
bodyLookingAt.set(tmp).div(tmpLength);
} else {
// bodyLookingAt is suddenly parallel to up vector -- PANIC! ENTERING RESCUE MODE!
getData().getForwardVector(bodyLookingAt);
}
Vectors.release(tmp);
}
private void computeDesiredHeadYaw() {
Vec3 newDirection = getData().getForwardVector(null);
Vec3 oldDirection = bodyLookingAt;
Vec3 up = getData().getUpVector();
headYaw = (float) VectorUtil.getAngle(oldDirection, newDirection, up);
}
private void clampHeadYawAndChangeBodyLookingAt() {
float bodyYawChange = 0;
if (headYaw > +head.maxYaw) {
bodyYawChange = headYaw - +head.maxYaw;
headYaw = +head.maxYaw;
} else if (headYaw < -head.maxYaw) {
bodyYawChange = headYaw - -head.maxYaw;
headYaw = -head.maxYaw;
}
if (bodyYawChange != 0) {
VectorUtil.rotate(bodyLookingAt, getData().getUpVector(), bodyYawChange);
}
}
private void recomputeBodyTransform() {
Vec3 u = getData().getUpVector();
Vec3 f = bodyLookingAt;
Vec3 s = Vectors.grab3();
s.set(u).cross(f);
bodyTransform.identity().set(
+f.x, +f.y, +f.z, 0,
-s.x, -s.y, -s.z, 0,
+u.x, +u.y, +u.z, 0,
0, 0, 0, 1
);
Vectors.release(s);
}
private void setHeadPitch() {
headPitch = Glm.clamp((float) getData().getPitch(), -head.maxPitch, +head.maxPitch);
}
private void advanceTime() {
Vec3 horizontal = getData().getUpVector()
.mul_(-getData().getUpVector().dot(getData().getVelocity()))
.add(getData().getVelocity());
velocity = horizontal.length();
evaluateVelocityCoeff();
computeVelocityParameter();
// TODO switch to world time
walkingParameter += velocity * GraphicsInterface.getFrameLength() * 1000;
bodyYaw += velocityParameter * normalizeAngle((float) (atan2(horizontal.y, horizontal.x) - bodyYaw))
* min(1, GraphicsInterface.getFrameLength() * 10);
rotateBodyWithMovement(horizontal);
}
private void evaluateVelocityCoeff() {
private void computeVelocityParameter() {
if (velocity > maxEffectiveVelocity) {
velocityParameter = 1;
} else {
@ -193,12 +266,39 @@ public abstract class NPedModel extends EntityRenderable {
}
}
private void rotateBodyWithMovement(Vec3 target) {
if (velocityParameter == 0 || !target.any() || Glm.equals(target, bodyLookingAt)) {
return;
}
Vec3 axis = getData().getUpVector();
float yawDifference = FloatMathUtil.normalizeAngle(
(float) VectorUtil.getAngle(
bodyLookingAt,
target.normalize_(),
axis
)
);
float bodyYawChange =
velocityParameter *
yawDifference *
(float) Math.expm1(GraphicsInterface.getFrameLength() * 10);
VectorUtil.rotate(bodyLookingAt, axis, bodyYawChange);
}
@Override
public void getViewPoint(Vec3 output) {
protected void doGetViewPoint(Vec3 output) {
Mat4 m = new Mat4();
Vec4 v = new Vec4();
m.identity().scale(scale).rotateZ(bodyYaw).translate(head.getTranslation()).rotateZ(headYaw).rotateY(headPitch);
m.identity()
.scale(scale)
.mul(bodyTransform);
head.getTransform(m, this);
v.set(head.getViewPoint(), 1);
m.mul(v);
@ -213,9 +313,9 @@ public abstract class NPedModel extends EntityRenderable {
public Head getHead() {
return head;
}
public float getBodyYaw() {
return bodyYaw;
public Vec3 getBodyLookingAt() {
return bodyLookingAt;
}
public float getHeadYaw() {
@ -228,8 +328,9 @@ public abstract class NPedModel extends EntityRenderable {
/**
* Returns a number in the range [0; 1] that can be used to scale animation
* effects that depend on speed. This parameter is 0 when the entity is not
* moving and 1 when it's moving "fast".
* effects that depend on speed.
* This parameter is 0 when the entity is not moving and 1 when it's moving
* "fast".
*
* @return velocity parameter
*/
@ -239,8 +340,9 @@ public abstract class NPedModel extends EntityRenderable {
/**
* Returns a number that can be used to parameterize animation effects that
* depend on walking. This parameter increases when the entity moves (e.g.
* this can be total traveled distance).
* depend on walking.
* This parameter increases when the entity moves (e.g. this can be total
* traveled distance).
*
* @return walking parameter
*/

View File

@ -40,6 +40,7 @@ public class QuadripedModel extends NPedModel {
@Override
protected void applyTransform(Mat4 mat, NPedModel model) {
super.applyTransform(mat, model);
float phase = model.getWalkingFrequency() * model.getWalkingParameter() + animationOffset;
float value = sin(phase);
float amplitude = ((QuadripedModel) model).getWalkingSwing() * model.getVelocityParameter();

View File

@ -15,29 +15,24 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.world.tile;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizer;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.generic.GenericTile;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.generic.TileGeneric;
import ru.windcorp.progressia.common.world.rels.RelFace;
public class TileRender extends Namespaced implements GenericTile {
public class TileRender extends Namespaced implements TileGeneric {
public TileRender(String id) {
super(id);
}
public void render(ShapeRenderHelper renderer, BlockFace face) {
throw new UnsupportedOperationException("TileRender.render() not implemented in " + this);
}
public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk, BlockFace face) {
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk, RelFace face) {
return null;
}

View File

@ -20,8 +20,8 @@ package ru.windcorp.progressia.client.world.tile;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.model.EmptyModel;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.rels.RelFace;
public class TileRenderNone extends TileRender {
@ -30,7 +30,7 @@ public class TileRenderNone extends TileRender {
}
@Override
public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk, BlockFace face) {
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk, RelFace face) {
return EmptyModel.getInstance();
}

View File

@ -19,7 +19,7 @@
package ru.windcorp.progressia.client.world.tile;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.rels.RelFace;
public class TileRenderOpaqueSurface extends TileRenderSurface {
@ -28,7 +28,7 @@ public class TileRenderOpaqueSurface extends TileRenderSurface {
}
@Override
public boolean isOpaque(BlockFace face) {
public boolean isOpaque(RelFace face) {
return true;
}

View File

@ -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> {
}

View File

@ -15,14 +15,18 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.world.tile;
import ru.windcorp.progressia.client.world.ChunkRender;
import ru.windcorp.progressia.common.world.generic.GenericTileStack;
import ru.windcorp.progressia.common.world.tile.TileDataStack;
import java.util.AbstractList;
public abstract class TileRenderStack extends GenericTileStack<TileRenderStack, TileRender, ChunkRender> {
import ru.windcorp.progressia.client.world.ChunkRender;
import ru.windcorp.progressia.client.world.block.BlockRender;
import ru.windcorp.progressia.common.world.TileDataStack;
import ru.windcorp.progressia.common.world.generic.TileGenericStackRO;
public abstract class TileRenderStack
extends AbstractList<TileRender>
implements TileGenericStackRO<BlockRender, TileRender, TileRenderStack, TileRenderReference, ChunkRender> {
public abstract TileDataStack getData();

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.world.tile;
import java.util.function.Consumer;
@ -25,16 +25,17 @@ import glm.vec._3.i.Vec3i;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.backend.Usage;
import ru.windcorp.progressia.client.graphics.model.Face;
import ru.windcorp.progressia.client.graphics.model.Faces;
import ru.windcorp.progressia.client.graphics.model.ShapePart;
import ru.windcorp.progressia.client.graphics.model.ShapeParts;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSurface.TileOptimizedSurface;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.rels.AbsFace;
import ru.windcorp.progressia.common.world.rels.RelFace;
public abstract class TileRenderSurface extends TileRender implements TileOptimizedSurface {
@ -44,36 +45,53 @@ public abstract class TileRenderSurface extends TileRender implements TileOptimi
super(id);
this.texture = texture;
}
public TileRenderSurface(String id) {
this(id, null);
}
public Texture getTexture(BlockFace blockFace) {
public Texture getTexture(RelFace blockFace) {
return texture;
}
public Vec4 getColorMultiplier(BlockFace blockFace) {
public Vec4 getColorMultiplier(RelFace blockFace) {
return Colors.WHITE;
}
@Override
public final void getFaces(ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace, boolean inner,
Consumer<Face> output, Vec3 offset) {
output.accept(createFace(chunk, blockInChunk, blockFace, inner, offset));
public final void getShapeParts(
DefaultChunkData chunk, Vec3i relBlockInChunk, RelFace blockFace,
boolean inner,
Consumer<ShapePart> output,
Vec3 offset
) {
output.accept(createFace(chunk, relBlockInChunk, blockFace, inner, offset));
}
private Face createFace(ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace, boolean inner, Vec3 offset) {
return Faces.createBlockFace(WorldRenderProgram.getDefault(), getTexture(blockFace),
getColorMultiplier(blockFace), offset, blockFace, inner);
private ShapePart createFace(
DefaultChunkData chunk, Vec3i blockInChunk, RelFace blockFace,
boolean inner,
Vec3 offset
) {
return ShapeParts.createBlockFace(
WorldRenderProgram.getDefault(),
getTexture(blockFace),
getColorMultiplier(blockFace),
offset,
blockFace.resolve(AbsFace.POS_Z),
inner
);
}
@Override
public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace) {
return new Shape(Usage.STATIC, WorldRenderProgram.getDefault(),
createFace(chunk, blockInChunk, blockFace, false, Vectors.ZERO_3),
createFace(chunk, blockInChunk, blockFace, true, Vectors.ZERO_3));
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk, RelFace blockFace) {
return new Shape(
Usage.STATIC,
WorldRenderProgram.getDefault(),
createFace(chunk, blockInChunk, blockFace, false, Vectors.ZERO_3),
createFace(chunk, blockInChunk, blockFace, true, Vectors.ZERO_3)
);
}
@Override

View File

@ -19,7 +19,7 @@
package ru.windcorp.progressia.client.world.tile;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.rels.RelFace;
public class TileRenderTransparentSurface extends TileRenderSurface {
@ -28,7 +28,7 @@ public class TileRenderTransparentSurface extends TileRenderSurface {
}
@Override
public boolean isOpaque(BlockFace face) {
public boolean isOpaque(RelFace face) {
return false;
}

View File

@ -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");
}
}
}

View File

@ -19,7 +19,7 @@
package ru.windcorp.progressia.common.collision;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.rels.AbsFace;
public interface AABBoid extends CollisionModel {
@ -27,7 +27,7 @@ public interface AABBoid extends CollisionModel {
void getSize(Vec3 output);
default Wall getWall(BlockFace face) {
default Wall getWall(AbsFace face) {
return getWall(face.getId());
}

View File

@ -20,7 +20,7 @@ package ru.windcorp.progressia.common.collision;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.rels.AbsFace;
public class TranslatedAABB implements AABBoid {
@ -51,7 +51,7 @@ public class TranslatedAABB implements AABBoid {
private AABBoid parent;
private final Vec3 translation = new Vec3();
private final TranslatedAABBWall[] walls = new TranslatedAABBWall[BlockFace.BLOCK_FACE_COUNT];
private final TranslatedAABBWall[] walls = new TranslatedAABBWall[AbsFace.BLOCK_FACE_COUNT];
{
for (int id = 0; id < walls.length; ++id) {

View File

@ -24,7 +24,7 @@ import java.util.Collection;
import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.util.LowOverheadCache;
import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.common.world.DefaultWorldData;
public class WorldCollisionHelper {
@ -79,7 +79,7 @@ public class WorldCollisionHelper {
* @param maxTime
* maximum collision time
*/
public void tuneToCollideable(WorldData world, Collideable collideable, float maxTime) {
public void tuneToCollideable(DefaultWorldData world, Collideable collideable, float maxTime) {
activeBlockModels.forEach(blockModelCache::release);
activeBlockModels.clear();
CollisionPathComputer.forEveryBlockInCollisionPath(collideable, maxTime,

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.common.collision.colliders;
import glm.mat._3.Mat3;
@ -25,12 +25,18 @@ import ru.windcorp.progressia.common.collision.colliders.Collider.ColliderWorksp
import ru.windcorp.progressia.common.collision.colliders.Collider.Collision;
import ru.windcorp.progressia.common.util.Matrices;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.rels.AbsFace;
class AABBoidCollider {
static Collider.Collision computeModelCollision(Collideable aBody, Collideable bBody, AABBoid aModel,
AABBoid bModel, float tickLength, ColliderWorkspace workspace) {
static Collider.Collision computeModelCollision(
Collideable aBody,
Collideable bBody,
AABBoid aModel,
AABBoid bModel,
float tickLength,
ColliderWorkspace workspace
) {
Collideable obstacleBody = bBody;
Collideable colliderBody = aBody;
AABBoid obstacleModel = bModel;
@ -44,11 +50,18 @@ class AABBoidCollider {
computeCollisionVelocity(collisionVelocity, obstacleBody, colliderBody);
// For every wall of collision space
for (int i = 0; i < BlockFace.BLOCK_FACE_COUNT; ++i) {
for (int i = 0; i < AbsFace.BLOCK_FACE_COUNT; ++i) {
Wall wall = originCollisionSpace.getWall(i);
Collision collision = computeWallCollision(wall, colliderModel, collisionVelocity, tickLength, workspace,
aBody, bBody);
Collision collision = computeWallCollision(
wall,
colliderModel,
collisionVelocity,
tickLength,
workspace,
aBody,
bBody
);
// Update result
if (collision != null) {
@ -73,7 +86,11 @@ class AABBoidCollider {
return result;
}
private static void computeCollisionVelocity(Vec3 output, Collideable obstacleBody, Collideable colliderBody) {
private static void computeCollisionVelocity(
Vec3 output,
Collideable obstacleBody,
Collideable colliderBody
) {
Vec3 obstacleVelocity = Vectors.grab3();
Vec3 colliderVelocity = Vectors.grab3();
@ -105,31 +122,60 @@ class AABBoidCollider {
return output;
}
// @formatter:off
/*
* Here we determine whether a collision has actually happened, and if it
* did, at what moment. The basic idea is to compute the moment of collision
* and impact coordinates in wall coordinate space. Then, we can check
* impact coordinates to determine if we actually hit the wall or flew by
* and then check time to make sure the collision is not too far in the
* future and not in the past. DETAILED EXPLANATION: Consider a surface
* defined by an origin r_wall and two noncollinear nonzero vectors w and h.
* Consider a line defined by an origin r_line and a nonzero vector v. Then,
* a collision occurs if there exist x, y and t such that ______ _ r_line +
* v * t and ______ _ _ r_wall + w * x + h * y describe the same location
* (indeed, this corresponds to a collision at moment t0 + t with a point on
* the wall with coordinates (x; y) in (w; h) coordinate system). Therefore,
* ______ _ ______ _ _ r_line + v*t = r_wall + w*x + h*y; _ w_x h_x
* -v_x x _ ______ ______ r = w_y h_y -v_y * y, where r
* = r_line - r_wall; w_z h_z -v_z t x w_x h_x -v_x
* -1 _ y = w_y h_y -v_y * r, if the matrix is invertible.
* t w_z h_z -v_z Then, one only needs to ensure that: 0 < x <
* 1, 0 < y < 1, and 0 < t < T, where T is remaining tick time. If the
* matrix is not invertible or any of the conditions are not met, no
* collision happened. If all conditions are satisfied, then the moment of
* impact is t0 + t.
* Here we determine whether a collision has actually happened, and if it did, at what moment.
*
* The basic idea is to compute the moment of collision and impact coordinates in wall coordinate space.
* Then, we can check impact coordinates to determine if we actually hit the wall or flew by and then
* check time to make sure the collision is not too far in the future and not in the past.
*
* DETAILED EXPLANATION:
*
* Consider a surface defined by an origin r_wall and two noncollinear nonzero vectors w and h.
* Consider a line defined by an origin r_line and a nonzero vector v.
*
* Then, a collision occurs if there exist x, y and t such that
* ______ _
* r_line + v * t
*
* and
* ______ _ _
* r_wall + w * x + h * y
*
* describe the same location (indeed, this corresponds to a collision at moment t0 + t
* with a point on the wall with coordinates (x; y) in (w; h) coordinate system).
*
* Therefore,
* ______ _ ______ _ _
* r_line + v*t = r_wall + w*x + h*y;
*
* _ w_x h_x -v_x x _ ______ ______
* r = w_y h_y -v_y * y, where r = r_line - r_wall;
* w_z h_z -v_z t
*
* x w_x h_x -v_x -1 _
* y = w_y h_y -v_y * r, if the matrix is invertible.
* t w_z h_z -v_z
*
* Then, one only needs to ensure that:
* 0 < x < 1,
* 0 < y < 1, and
* 0 < t < T, where T is remaining tick time.
*
* If the matrix is not invertible or any of the conditions are not met, no collision happened.
* If all conditions are satisfied, then the moment of impact is t0 + t.
*/
private static Collision computeWallCollision(Wall obstacleWall, AABBoid colliderModel, Vec3 collisionVelocity,
float tickLength, ColliderWorkspace workspace, Collideable aBody, Collideable bBody) {
// @formatter:on
private static Collision computeWallCollision(
Wall obstacleWall,
AABBoid colliderModel,
Vec3 collisionVelocity,
float tickLength,
ColliderWorkspace workspace,
Collideable aBody,
Collideable bBody
) {
Vec3 w = Vectors.grab3();
Vec3 h = Vectors.grab3();
Vec3 v = Vectors.grab3();

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.common.collision.colliders;
import java.util.Collection;
@ -27,7 +27,7 @@ import glm.vec._3.Vec3;
import ru.windcorp.progressia.common.collision.*;
import ru.windcorp.progressia.common.util.LowOverheadCache;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.common.world.DefaultWorldData;
public class Collider {
@ -36,15 +36,19 @@ public class Collider {
/**
* Dear Princess Celestia,
* <p>
* When {@linkplain #advanceTime(Collection, Collision, WorldData, float)
* advancing time}, time step for all entities <em>except</em> currently
* colliding bodies is the current collisions's timestamp relative to now.
* However, currently colliding bodies (Collision.a and Collision.b) have a
* smaller time step. This is done to make sure they don't intersect due to
* rounding errors.
* When {@linkplain #advanceTime(Collection, Collision, DefaultWorldData, float)
* advancing time},
* time step for all entities <em>except</em> currently colliding bodies is
* the current
* collisions's timestamp relative to now. However, currently colliding
* bodies
* (Collision.a and Collision.b) have a smaller time step. This is done to
* make sure
* they don't intersect due to rounding errors.
* <p>
* Today I learned that bad code has nothing to do with friendship, although
* lemme tell ya: it's got some dank magic.
* lemme tell ya:
* it's got some dank magic.
* <p>
* Your faithful student,<br />
* Kostyl.
@ -55,14 +59,21 @@ public class Collider {
* 1f
*/;
public static void performCollisions(List<? extends Collideable> colls, WorldData world, float tickLength,
ColliderWorkspace workspace) {
public static void performCollisions(
List<? extends Collideable> colls,
DefaultWorldData world,
float tickLength,
ColliderWorkspace workspace
) {
int collisionCount = 0;
int maxCollisions = colls.size() * MAX_COLLISIONS_PER_ENTITY;
while (true) {
if (collisionCount > maxCollisions) {
LogManager.getLogger().warn("Attempted to handle more than {} collisions", maxCollisions);
LogManager.getLogger().warn(
"Attempted to handle more than {} collisions",
maxCollisions
);
return;
}
@ -82,8 +93,12 @@ public class Collider {
advanceTime(colls, null, world, tickLength);
}
private static Collision getFirstCollision(List<? extends Collideable> colls, float tickLength, WorldData world,
ColliderWorkspace workspace) {
private static Collision getFirstCollision(
List<? extends Collideable> colls,
float tickLength,
DefaultWorldData world,
ColliderWorkspace workspace
) {
Collision result = null;
Collideable worldColl = workspace.worldCollisionHelper.getCollideable();
@ -93,7 +108,10 @@ public class Collider {
tuneWorldCollisionHelper(a, tickLength, world, workspace);
result = workspace.updateLatestCollision(result, getCollision(a, worldColl, tickLength, workspace));
result = workspace.updateLatestCollision(
result,
getCollision(a, worldColl, tickLength, workspace)
);
for (int j = i + 1; j < colls.size(); ++j) {
Collideable b = colls.get(j);
@ -105,42 +123,81 @@ public class Collider {
return result;
}
private static void tuneWorldCollisionHelper(Collideable coll, float tickLength, WorldData world,
ColliderWorkspace workspace) {
private static void tuneWorldCollisionHelper(
Collideable coll,
float tickLength,
DefaultWorldData world,
ColliderWorkspace workspace
) {
WorldCollisionHelper wch = workspace.worldCollisionHelper;
wch.tuneToCollideable(world, coll, tickLength);
}
static Collision getCollision(Collideable a, Collideable b, float tickLength, ColliderWorkspace workspace) {
static Collision getCollision(
Collideable a,
Collideable b,
float tickLength,
ColliderWorkspace workspace
) {
CollisionModel aModel = a.getCollisionModel();
CollisionModel bModel = b.getCollisionModel();
return getCollision(a, b, aModel, bModel, tickLength, workspace);
}
static Collision getCollision(Collideable aBody, Collideable bBody, CollisionModel aModel, CollisionModel bModel,
float tickLength, ColliderWorkspace workspace) {
static Collision getCollision(
Collideable aBody,
Collideable bBody,
CollisionModel aModel,
CollisionModel bModel,
float tickLength,
ColliderWorkspace workspace
) {
if (aModel instanceof AABBoid && bModel instanceof AABBoid) {
return AABBoidCollider.computeModelCollision(aBody, bBody, (AABBoid) aModel, (AABBoid) bModel, tickLength,
workspace);
return AABBoidCollider.computeModelCollision(
aBody,
bBody,
(AABBoid) aModel,
(AABBoid) bModel,
tickLength,
workspace
);
}
if (aModel instanceof CompoundCollisionModel) {
return AnythingWithCompoundCollider.computeModelCollision(aBody, bBody, (CompoundCollisionModel) aModel,
bModel, tickLength, workspace);
return AnythingWithCompoundCollider.computeModelCollision(
aBody,
bBody,
(CompoundCollisionModel) aModel,
bModel,
tickLength,
workspace
);
}
if (bModel instanceof CompoundCollisionModel) {
return AnythingWithCompoundCollider.computeModelCollision(bBody, aBody, (CompoundCollisionModel) bModel,
aModel, tickLength, workspace);
return AnythingWithCompoundCollider.computeModelCollision(
bBody,
aBody,
(CompoundCollisionModel) bModel,
aModel,
tickLength,
workspace
);
}
throw new UnsupportedOperationException(
"Collisions between " + aModel + " and " + bModel + " are not yet implemented");
"Collisions between " + aModel + " and " + bModel + " are not yet implemented"
);
}
private static void collide(Collision collision,
private static void collide(
Collision collision,
Collection<? extends Collideable> colls, WorldData world, float tickLength, ColliderWorkspace workspace) {
Collection<? extends Collideable> colls,
DefaultWorldData world,
float tickLength,
ColliderWorkspace workspace
) {
advanceTime(colls, collision, world, collision.time);
boolean doNotHandle = false;
@ -155,40 +212,72 @@ public class Collider {
handlePhysics(collision);
}
// @formatter:off
/*
* Here we compute the change in body velocities due to a collision. We make
* the following simplifications: 1) The bodies are perfectly rigid; 2) The
* collision is perfectly inelastic (no bouncing); 3) The bodies are
* spherical; 4) No tangential friction exists (bodies do not experience
* friction when sliding against each other); 5) Velocities are not
* relativistic. Angular momentum is ignored per 3) and 4), e.g. when
* something pushes an end of a long stick, the stick does not rotate.
* DETAILED EXPLANATION: Two spherical (sic) bodies, a and b, experience a
* perfectly inelastic collision along a unit vector _ _ _ _ _ n = (w h)
* / (|w h|), _ _ where w and h are two noncollinear nonzero vectors on
* the dividing plane. ___ ___ Body masses and velocities are M_a, M_b and
* v_a, v_b, respectively. ___ ___ After the collision desired velocities
* are u_a and u_b, respectively. _ (Notation convention: suffix 'n' denotes
* a vector projection onto vector n, and suffix 't' denotes a vector
* projection onto the dividing plane.) Consider the law of conservation of
* momentum for axis n and the dividing plane: ____________ ____________
* ________________ n: p_a_before_n + p_b_before_n = p_common_after_n;
* ___________ ____________ t: p_i_after_t = p_i_before_t for any i
* in {a, b}. Expressing all p_* in given terms: ___ _ ___ _ ___ ___ ____
* ____ n: M_a * (v_a n) + M_b * (v_b n) = (M_a + M_b) * u_n,
* where u_n u_an = u_bn; ____ ___ _ ___ _ t: u_it = v_i - n *
* (v_i n) for any i in {a, b}. Therefore: ___ _ ___ _ ___ _ u_n = n * (
* M_a/(M_a + M_b) * v_a n + M_b/(M_a + M_b) * v_b n ); or,
* equivalently, ___ _ ___ _ ___ _ u_n = n * ( m_a * v_a n + m_b * v_b
* n ), where m_a and m_b are relative masses (see below). Finally, ___
* ____ ___ u_i = u_it + u_n for any i in {a, b}. The usage of relative
* masses m_i permits a convenient generalization of the algorithm for
* infinite masses, signifying masses "significantly greater" than finite
* masses: 1) If both M_a and M_b are finite, let m_i = M_i / (M_a + M_b)
* for any i in {a, b}. 2) If M_i is finite but M_j is infinite, let m_i = 0
* and m_j = 1. 3) If both M_a and M_b are infinite, let m_i = 1/2 for any i
* in {a, b}.
* Here we compute the change in body velocities due to a collision.
*
* We make the following simplifications:
* 1) The bodies are perfectly rigid;
* 2) The collision is perfectly inelastic
* (no bouncing);
* 3) The bodies are spherical;
* 4) No tangential friction exists
* (bodies do not experience friction when sliding against each other);
* 5) Velocities are not relativistic.
*
* Angular momentum is ignored per 3) and 4),
* e.g. when something pushes an end of a long stick, the stick does not rotate.
*
* DETAILED EXPLANATION:
*
* Two spherical (sic) bodies, a and b, experience a perfectly inelastic collision
* along a unit vector
* _ _ _ _ _
* n = (w h) / (|w h|),
* _ _
* where w and h are two noncollinear nonzero vectors on the dividing plane.
* ___ ___
* Body masses and velocities are M_a, M_b and v_a, v_b, respectively.
* ___ ___
* After the collision desired velocities are u_a and u_b, respectively.
* _
* (Notation convention: suffix 'n' denotes a vector projection onto vector n,
* and suffix 't' denotes a vector projection onto the dividing plane.)
*
* Consider the law of conservation of momentum for axis n and the dividing plane:
* ____________ ____________ ________________
* n: p_a_before_n + p_b_before_n = p_common_after_n;
* ___________ ____________
* t: p_i_after_t = p_i_before_t for any i in {a, b}.
*
* Expressing all p_* in given terms:
* ___ _ ___ _ ___ ___ ____ ____
* n: M_a * (v_a n) + M_b * (v_b n) = (M_a + M_b) * u_n, where u_n u_an = u_bn;
* ____ ___ _ ___ _
* t: u_it = v_i - n * (v_i n) for any i in {a, b}.
*
* Therefore:
* ___ _ ___ _ ___ _
* u_n = n * ( M_a/(M_a + M_b) * v_a n + M_b/(M_a + M_b) * v_b n );
*
* or, equivalently,
* ___ _ ___ _ ___ _
* u_n = n * ( m_a * v_a n + m_b * v_b n ),
*
* where m_a and m_b are relative masses (see below).
*
* Finally,
* ___ ____ ___
* u_i = u_it + u_n for any i in {a, b}.
*
* The usage of relative masses m_i permits a convenient generalization of the algorithm
* for infinite masses, signifying masses "significantly greater" than finite masses:
*
* 1) If both M_a and M_b are finite, let m_i = M_i / (M_a + M_b) for any i in {a, b}.
* 2) If M_i is finite but M_j is infinite, let m_i = 0 and m_j = 1.
* 3) If both M_a and M_b are infinite, let m_i = 1/2 for any i in {a, b}.
*/
// @formatter:on
private static void handlePhysics(Collision collision) {
// Fuck JGLM
Vec3 n = Vectors.grab3();
@ -250,7 +339,12 @@ public class Collider {
Vectors.release(du_b);
}
private static void separate(Collision collision, Vec3 normal, float aRelativeMass, float bRelativeMass) {
private static void separate(
Collision collision,
Vec3 normal,
float aRelativeMass,
float bRelativeMass
) {
final float margin = 1e-4f;
Vec3 displacement = Vectors.grab3();
@ -264,8 +358,12 @@ public class Collider {
Vectors.release(displacement);
}
private static void advanceTime(Collection<? extends Collideable> colls, Collision exceptions, WorldData world,
float step) {
private static void advanceTime(
Collection<? extends Collideable> colls,
Collision exceptions,
DefaultWorldData world,
float step
) {
world.advanceTime(step);
Vec3 tmp = Vectors.grab3();
@ -333,12 +431,17 @@ public class Collider {
public final Vec3 wallHeight = new Vec3();
/**
* Time offset from the start of the tick. 0 means right now, tickLength
* means at the end of the tick.
* Time offset from the start of the tick.
* 0 means right now, tickLength means at the end of the tick.
*/
public float time;
public Collision set(Collideable a, Collideable b, Wall wall, float time) {
public Collision set(
Collideable a,
Collideable b,
Wall wall,
float time
) {
this.a = a;
this.b = b;
wall.getWidth(wallWidth);

View File

@ -29,11 +29,14 @@ import com.google.common.util.concurrent.MoreExecutors;
import ru.windcorp.progressia.common.util.crash.CrashReports;
/**
* This class had to be written because there is not legal way to instantiate a
* This class had to be written because there is no legal way to instantiate a
* non-async {@link EventBus} with both a custom identifier and a custom
* exception handler. Which is a shame. Guava maintainers know about the issue
* but have rejected solutions multiple times <em>without a clearly stated
* reason</em>; looks like some dirty reflection will have to do.
* <p>
* When explicitly referencing this class, please mention its usage in
* implementation notes because it is unreliable long-term.
*
* @author javapony
*/
@ -46,22 +49,30 @@ public class GuavaEventBusHijacker {
try {
Class<?> dispatcherClass = Class.forName("com.google.common.eventbus.Dispatcher");
THE_CONSTRUCTOR = EventBus.class.getDeclaredConstructor(String.class, Executor.class, dispatcherClass,
SubscriberExceptionHandler.class);
THE_CONSTRUCTOR = EventBus.class.getDeclaredConstructor(
String.class,
Executor.class,
dispatcherClass,
SubscriberExceptionHandler.class
);
THE_CONSTRUCTOR.setAccessible(true);
DISPATCHER__PER_THREAD_DISPATCH_QUEUE = dispatcherClass.getDeclaredMethod("perThreadDispatchQueue");
DISPATCHER__PER_THREAD_DISPATCH_QUEUE.setAccessible(true);
} catch (Exception e) {
throw CrashReports.report(e,
"Something went horribly wrong when setting up EventBus hijacking. Has Guava updated?");
throw CrashReports
.report(e, "Something went horribly wrong when setting up EventBus hijacking. Has Guava updated?");
}
}
public static EventBus newEventBus(String identifier, SubscriberExceptionHandler exceptionHandler) {
try {
return THE_CONSTRUCTOR.newInstance(identifier, MoreExecutors.directExecutor(),
DISPATCHER__PER_THREAD_DISPATCH_QUEUE.invoke(null), exceptionHandler);
return THE_CONSTRUCTOR.newInstance(
identifier,
MoreExecutors.directExecutor(),
DISPATCHER__PER_THREAD_DISPATCH_QUEUE.invoke(null),
exceptionHandler
);
} catch (Exception e) {
throw CrashReports.report(e, "Something went horribly wrong when hijacking EventBus. Has Guava updated?");
}

View File

@ -15,8 +15,8 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.server.world.tasks;
package ru.windcorp.progressia.common.state;
@FunctionalInterface
public interface StateChange<T> {

View File

@ -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++;
}
}
}
}

View File

@ -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;
}
}

View File

@ -15,11 +15,13 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.common.util;
import java.util.function.Consumer;
import glm.Glm;
import glm.mat._3.Mat3;
import glm.mat._4.Mat4;
import glm.vec._2.Vec2;
import glm.vec._2.d.Vec2d;
@ -36,8 +38,50 @@ public class VectorUtil {
public static enum Axis {
X, Y, Z, W;
}
public static enum SignedAxis {
POS_X(Axis.X, +1),
NEG_X(Axis.X, -1),
POS_Y(Axis.Y, +1),
NEG_Y(Axis.Y, -1),
POS_Z(Axis.Z, +1),
NEG_Z(Axis.Z, -1),
POS_W(Axis.W, +1),
NEG_W(Axis.W, -1);
private final Axis axis;
private final boolean isPositive;
private SignedAxis(Axis axis, int sign) {
this.axis = axis;
this.isPositive = (sign == +1 ? true : false);
}
/**
* @return the axis
*/
public Axis getAxis() {
return axis;
}
public boolean isPositive() {
return isPositive;
}
public int getSign() {
return isPositive ? +1 : -1;
}
}
public static void iterateCuboid(int x0, int y0, int z0, int x1, int y1, int z1, Consumer<? super Vec3i> action) {
public static void iterateCuboid(
int x0,
int y0,
int z0,
int x1,
int y1,
int z1,
Consumer<? super Vec3i> action
) {
Vec3i cursor = Vectors.grab3i();
for (int x = x0; x < x1; ++x) {
@ -52,12 +96,23 @@ public class VectorUtil {
Vectors.release(cursor);
}
public static void iterateCuboid(Vec3i vMin, Vec3i vMax, Consumer<? super Vec3i> action) {
public static void iterateCuboid(
Vec3i vMin,
Vec3i vMax,
Consumer<? super Vec3i> action
) {
iterateCuboid(vMin.x, vMin.y, vMin.z, vMax.x, vMax.y, vMax.z, action);
}
public static void iterateCuboidAround(int cx, int cy, int cz, int dx, int dy, int dz,
Consumer<? super Vec3i> action) {
public static void iterateCuboidAround(
int cx,
int cy,
int cz,
int dx,
int dy,
int dz,
Consumer<? super Vec3i> action
) {
if (dx < 0)
throw new IllegalArgumentException("dx " + dx + " is negative");
if (dy < 0)
@ -79,19 +134,37 @@ public class VectorUtil {
iterateCuboid(cx - dx, cy - dy, cz - dz, cx + dx + 1, cy + dy + 1, cz + dz + 1, action);
}
public static void iterateCuboidAround(Vec3i center, Vec3i diameters, Consumer<? super Vec3i> action) {
public static void iterateCuboidAround(
Vec3i center,
Vec3i diameters,
Consumer<? super Vec3i> action
) {
iterateCuboidAround(center.x, center.y, center.z, diameters.x, diameters.y, diameters.z, action);
}
public static void iterateCuboidAround(int cx, int cy, int cz, int diameter, Consumer<? super Vec3i> action) {
public static void iterateCuboidAround(
int cx,
int cy,
int cz,
int diameter,
Consumer<? super Vec3i> action
) {
iterateCuboidAround(cx, cy, cz, diameter, diameter, diameter, action);
}
public static void iterateCuboidAround(Vec3i center, int diameter, Consumer<? super Vec3i> action) {
public static void iterateCuboidAround(
Vec3i center,
int diameter,
Consumer<? super Vec3i> action
) {
iterateCuboidAround(center.x, center.y, center.z, diameter, action);
}
public static void applyMat4(Vec3 in, Mat4 mat, Vec3 out) {
public static Vec3 applyMat4(Vec3 in, Mat4 mat, Vec3 out) {
if (out == null) {
out = new Vec3();
}
Vec4 vec4 = Vectors.grab4();
vec4.set(in, 1f);
@ -99,27 +172,206 @@ public class VectorUtil {
out.set(vec4.x, vec4.y, vec4.z);
Vectors.release(vec4);
return out;
}
public static void applyMat4(Vec3 inOut, Mat4 mat) {
Vec4 vec4 = Vectors.grab4();
vec4.set(inOut, 1f);
mat.mul(vec4);
inOut.set(vec4.x, vec4.y, vec4.z);
public static Vec3 applyMat4(Vec3 inOut, Mat4 mat) {
return applyMat4(inOut, mat, inOut);
}
public static Vec3 rotate(Vec3 in, Vec3 axis, float angle, Vec3 out) {
if (out == null) {
out = new Vec3();
}
Mat3 mat = Matrices.grab3();
mat.identity().rotate(angle, axis);
mat.mul(in, out);
Matrices.release(mat);
return out;
}
public static Vec3 rotate(Vec3 inOut, Vec3 axis, float angle) {
return rotate(inOut, axis, angle, inOut);
}
public static double getAngle(Vec3 from, Vec3 to, Vec3 normal) {
Vec3 left = Vectors.grab3();
left.set(normal).cross(from);
double sign = Math.signum(left.dot(to));
double result = (float) Math.acos(Glm.clamp(from.dot(to), -1, +1)) * sign;
Vectors.release(left);
return result;
}
public static Vec3 projectOnSurface(Vec3 in, Vec3 normal, Vec3 out) {
if (in == out) {
return projectOnSurface(in, normal);
}
if (out == null) {
out = new Vec3();
}
out.set(normal).mul(-normal.dot(in)).add(in);
return out;
}
public static Vec3 projectOnSurface(Vec3 inOut, Vec3 normal) {
Vec3 buffer = Vectors.grab3();
projectOnSurface(inOut, normal, buffer);
inOut.set(buffer);
Vectors.release(buffer);
return inOut;
}
public static Vec3 projectOnVector(Vec3 in, Vec3 vector, Vec3 out) {
if (out == null) {
out = new Vec3();
}
float dot = vector.dot(in);
out.set(vector).mul(dot);
return out;
}
public static Vec3 projectOnVector(Vec3 inOut, Vec3 vector) {
return projectOnVector(inOut, vector);
}
public static Vec3 linearCombination(Vec3 va, float ka, Vec3 vb, float kb, Vec3 output) {
output.set(va.x * ka + vb.x * kb, va.y * ka + vb.y * kb, va.z * ka + vb.z * kb);
public static Vec3 linearCombination(
Vec3 va,
float ka,
Vec3 vb,
float kb,
Vec3 output
) {
if (output == null) {
output = new Vec3();
}
output.set(
va.x * ka + vb.x * kb,
va.y * ka + vb.y * kb,
va.z * ka + vb.z * kb
);
return output;
}
public static Vec3 linearCombination(Vec3 va, float ka, Vec3 vb, float kb, Vec3 vc, float kc, Vec3 output) {
output.set(va.x * ka + vb.x * kb + vc.x * kc, va.y * ka + vb.y * kb + vc.y * kc,
va.z * ka + vb.z * kb + vc.z * kc);
public static Vec3 linearCombination(
Vec3 va,
float ka,
Vec3 vb,
float kb,
Vec3 vc,
float kc,
Vec3 output
) {
if (output == null) {
output = new Vec3();
}
output.set(
va.x * ka + vb.x * kb + vc.x * kc,
va.y * ka + vb.y * kb + vc.y * kc,
va.z * ka + vb.z * kb + vc.z * kc
);
return output;
}
public static Vec3i sort(Vec3i input, Vec3i output) {
if (output == null) {
output = new Vec3i();
}
int ax = input.x, ay = input.y, az = input.z;
if (ax > ay) {
if (ax > az) {
output.x = ax;
output.y = ay > az ? ay : az;
output.z = ay > az ? az : ay;
} else {
output.x = az;
output.y = ax;
output.z = ay;
}
} else {
if (ay > az) {
output.x = ay;
output.y = ax > az ? ax : az;
output.z = ax > az ? az : ax;
} else {
output.x = az;
output.y = ay;
output.z = ax;
}
}
return output;
}
public static Vec3 sort(Vec3 input, Vec3 output) {
if (output == null) {
output = new Vec3();
}
float ax = input.x, ay = input.y, az = input.z;
if (ax > ay) {
if (ax > az) {
output.x = ax;
output.y = ay > az ? ay : az;
output.z = ay > az ? az : ay;
} else {
output.x = az;
output.y = ax;
output.z = ay;
}
} else {
if (ay > az) {
output.x = ay;
output.y = ax > az ? ax : az;
output.z = ax > az ? az : ax;
} else {
output.x = az;
output.y = ay;
output.z = ax;
}
}
return output;
}
public static Vec3i sortAfterAbs(Vec3i input, Vec3i output) {
if (output == null) {
output = new Vec3i();
}
input.abs(output);
return sort(output, output);
}
public static Vec3 sortAfterAbs(Vec3 input, Vec3 output) {
if (output == null) {
output = new Vec3();
}
input.abs(output);
return sort(output, output);
}
public static float get(Vec2 v, Axis a) {
switch (a) {

View File

@ -22,11 +22,27 @@ import com.google.common.eventbus.EventBus;
import ru.windcorp.progressia.common.hacks.GuavaEventBusHijacker;
/**
* A utility for creating Guava's {@link EventBus}es that
* {@linkplain CrashReports report} exceptions instead of suppressing them.
*
* @author javapony
*/
public class ReportingEventBus {
private ReportingEventBus() {
}
/**
* Instantiates a new {@link EventBus} with the provided identifier that
* reports any unhandled exceptions with {@link CrashReports}.
*
* @param identifier the identifier of the new bus
* @return the created event bus
* @implNote This implementation relies on {@link GuavaEventBusHijacker} for
* creating buses with custom identifiers and uncaught exception
* handlers. It may break suddenly with a Guava update.
*/
public static EventBus create(String identifier) {
return GuavaEventBusHijacker.newEventBus(identifier, (throwable, context) -> {
// Makes sense to append identifier to messageFormat because

View File

@ -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);
}

View File

@ -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;
}
}
}

View File

@ -22,7 +22,7 @@ import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.util.VectorUtil.Axis;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.rels.AbsFace;
import static java.lang.Math.*;
@ -34,14 +34,22 @@ public class BlockRay {
private float distance;
private final Vec3i block = new Vec3i();
private BlockFace currentFace = null;
private AbsFace currentFace = null;
private boolean isValid = false;
public void start(Vec3 position, Vec3 direction) {
if (!direction.any()) {
if (direction.x == 0 && direction.y == 0 && direction.z == 0) {
throw new IllegalArgumentException("Direction is a zero vector");
}
if (Float.isNaN(direction.x) || Float.isNaN(direction.y) || Float.isNaN(direction.z)) {
throw new IllegalArgumentException("Direction contains NaN: " + direction);
}
if (Float.isNaN(position.x) || Float.isNaN(position.y) || Float.isNaN(position.z)) {
throw new IllegalArgumentException("Position contains NaN: " + position);
}
isValid = true;
this.position.set(position).sub(0.5f); // Make sure lattice points are
@ -75,16 +83,14 @@ public class BlockRay {
tMin = tz;
axis = Axis.Z;
}
assert tMin > 0 : "tMin is not positive (" + tMin + ")";
// block.(axis) += signum(direction.(axis))
VectorUtil.set(block, axis, VectorUtil.get(block, axis) + (int) signum(VectorUtil.get(direction, axis)));
// position += direction * tMin
VectorUtil.linearCombination(position, 1, direction, tMin, position); // position
// +=
// direction
// *
// tMin
VectorUtil.linearCombination(position, 1, direction, tMin, position);
distance += tMin;
// position.(axis) = round(position.(axis))
@ -110,18 +116,18 @@ public class BlockRay {
return (edge - c) / dir;
}
private BlockFace computeCurrentFace(Axis axis, int sign) {
private AbsFace computeCurrentFace(Axis axis, int sign) {
if (sign == 0)
throw new IllegalStateException("sign is zero");
switch (axis) {
case X:
return sign > 0 ? BlockFace.SOUTH : BlockFace.NORTH;
return sign > 0 ? AbsFace.NEG_X : AbsFace.POS_X;
case Y:
return sign > 0 ? BlockFace.EAST : BlockFace.WEST;
return sign > 0 ? AbsFace.NEG_Y : AbsFace.POS_Y;
default:
case Z:
return sign > 0 ? BlockFace.BOTTOM : BlockFace.TOP;
return sign > 0 ? AbsFace.NEG_Z : AbsFace.POS_Z;
}
}
@ -137,7 +143,7 @@ public class BlockRay {
return output;
}
public BlockFace getCurrentFace() {
public AbsFace getCurrentFace() {
return currentFace;
}

View File

@ -1,534 +1,12 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.common.world;
import static ru.windcorp.progressia.common.world.block.BlockFace.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.world.block.BlockData;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.generic.GenericChunk;
import ru.windcorp.progressia.common.world.generic.ChunkGenericWO;
import ru.windcorp.progressia.common.world.tile.TileData;
import ru.windcorp.progressia.common.world.tile.TileDataStack;
import ru.windcorp.progressia.common.world.tile.TileReference;
import ru.windcorp.progressia.common.world.tile.TileStackIsFullException;
public class ChunkData implements GenericChunk<ChunkData, BlockData, TileData, TileDataStack> {
public static final int BLOCKS_PER_CHUNK = Coordinates.CHUNK_SIZE;
private final Vec3i position = new Vec3i();
private final WorldData world;
private final BlockData[] blocks = new BlockData[BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK];
private final TileDataStack[] tiles = new TileDataStack[BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK
* BLOCK_FACE_COUNT];
private Object generationHint = null;
private final Collection<ChunkDataListener> listeners = Collections.synchronizedCollection(new ArrayList<>());
public ChunkData(Vec3i position, WorldData world) {
this.position.set(position.x, position.y, position.z);
this.world = world;
}
@Override
public Vec3i getPosition() {
return position;
}
@Override
public BlockData getBlock(Vec3i posInChunk) {
return blocks[getBlockIndex(posInChunk)];
}
public void setBlock(Vec3i posInChunk, BlockData block, boolean notify) {
BlockData previous = blocks[getBlockIndex(posInChunk)];
blocks[getBlockIndex(posInChunk)] = block;
if (notify) {
getListeners().forEach(l -> {
l.onChunkBlockChanged(this, posInChunk, previous, block);
l.onChunkChanged(this);
});
}
}
@Override
public TileDataStack getTilesOrNull(Vec3i blockInChunk, BlockFace face) {
return tiles[getTileIndex(blockInChunk, face)];
}
/**
* Internal use only. Modify a list returned by
* {@link #getTiles(Vec3i, BlockFace)} or
* {@link #getTilesOrNull(Vec3i, BlockFace)} to change tiles.
*/
protected void setTiles(Vec3i blockInChunk, BlockFace face, TileDataStack tiles) {
this.tiles[getTileIndex(blockInChunk, face)] = tiles;
}
@Override
public boolean hasTiles(Vec3i blockInChunk, BlockFace face) {
return getTilesOrNull(blockInChunk, face) != null;
}
@Override
public TileDataStack getTiles(Vec3i blockInChunk, BlockFace face) {
int index = getTileIndex(blockInChunk, face);
if (tiles[index] == null) {
createTileStack(blockInChunk, face);
}
return tiles[index];
}
private void createTileStack(Vec3i blockInChunk, BlockFace face) {
Vec3i independentBlockInChunk = conjureIndependentBlockInChunkVec3i(blockInChunk);
TileDataStackImpl stack = new TileDataStackImpl(independentBlockInChunk, face);
setTiles(blockInChunk, face, stack);
}
private Vec3i conjureIndependentBlockInChunkVec3i(Vec3i blockInChunk) {
for (int i = 0; i < BlockFace.BLOCK_FACE_COUNT; ++i) {
TileDataStack stack = getTilesOrNull(blockInChunk, BlockFace.getFaces().get(i));
if (stack instanceof TileDataStackImpl) {
return ((TileDataStackImpl) stack).blockInChunk;
}
}
return new Vec3i(blockInChunk);
}
private static int getBlockIndex(Vec3i posInChunk) {
checkLocalCoordinates(posInChunk);
return posInChunk.z * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK + posInChunk.y * BLOCKS_PER_CHUNK + posInChunk.x;
}
private static int getTileIndex(Vec3i posInChunk, BlockFace face) {
return getBlockIndex(posInChunk) * BLOCK_FACE_COUNT + face.getId();
}
private static void checkLocalCoordinates(Vec3i posInChunk) {
if (!isInBounds(posInChunk)) {
throw new IllegalCoordinatesException(
"Coordinates " + str(posInChunk) + " " + "are not legal chunk coordinates");
}
}
public static boolean isInBounds(Vec3i posInChunk) {
return posInChunk.x >= 0 && posInChunk.x < BLOCKS_PER_CHUNK && posInChunk.y >= 0
&& posInChunk.y < BLOCKS_PER_CHUNK && posInChunk.z >= 0 && posInChunk.z < BLOCKS_PER_CHUNK;
}
public boolean isBorder(Vec3i blockInChunk, BlockFace face) {
final int min = 0, max = BLOCKS_PER_CHUNK - 1;
return (blockInChunk.x == min && face == SOUTH) || (blockInChunk.x == max && face == NORTH)
|| (blockInChunk.y == min && face == EAST) || (blockInChunk.y == max && face == WEST)
|| (blockInChunk.z == min && face == BOTTOM) || (blockInChunk.z == max && face == TOP);
}
public void forEachBlock(Consumer<Vec3i> action) {
VectorUtil.iterateCuboid(0, 0, 0, BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK, action);
}
public void forEachTileStack(Consumer<TileDataStack> action) {
forEachBlock(blockInChunk -> {
for (BlockFace face : BlockFace.getFaces()) {
TileDataStack stack = getTilesOrNull(blockInChunk, face);
if (stack == null)
continue;
action.accept(stack);
}
});
}
/**
* Iterates over all tiles in this chunk.
*
* @param action
* the action to perform. {@code TileLocation} refers to each
* tile using its primary block
*/
public void forEachTile(BiConsumer<TileDataStack, TileData> action) {
forEachTileStack(stack -> stack.forEach(tileData -> action.accept(stack, tileData)));
}
public WorldData getWorld() {
return world;
}
public Collection<ChunkDataListener> getListeners() {
return listeners;
}
public void addListener(ChunkDataListener listener) {
this.listeners.add(listener);
}
public void removeListener(ChunkDataListener listener) {
this.listeners.remove(listener);
}
private static String str(Vec3i v) {
return "(" + v.x + "; " + v.y + "; " + v.z + ")";
}
protected void onLoaded() {
getListeners().forEach(l -> l.onChunkLoaded(this));
}
protected void beforeUnloaded() {
getListeners().forEach(l -> l.beforeChunkUnloaded(this));
}
public Object getGenerationHint() {
return generationHint;
}
public void setGenerationHint(Object generationHint) {
this.generationHint = generationHint;
}
/**
* Implementation of {@link TileDataStack} used internally by
* {@link ChunkData} to actually store the tiles. This is basically an array
* wrapper with reporting capabilities.
*
* @author javapony
*/
private class TileDataStackImpl extends TileDataStack {
private class TileReferenceImpl implements TileReference {
private int index;
public TileReferenceImpl(int index) {
this.index = index;
}
public void incrementIndex() {
this.index++;
}
public void decrementIndex() {
this.index--;
}
public void invalidate() {
this.index = 0;
}
@Override
public TileData get() {
if (!isValid())
return null;
return TileDataStackImpl.this.get(this.index);
}
@Override
public int getIndex() {
return index;
}
@Override
public TileDataStack getStack() {
return TileDataStackImpl.this;
}
@Override
public boolean isValid() {
return this.index >= 0;
}
}
private final TileData[] tiles = new TileData[TILES_PER_FACE];
private int size = 0;
private final TileReferenceImpl[] references = new TileReferenceImpl[tiles.length];
private final int[] indicesByTag = new int[tiles.length];
private final int[] tagsByIndex = new int[tiles.length];
{
Arrays.fill(indicesByTag, -1);
Arrays.fill(tagsByIndex, -1);
}
/*
* Potentially shared
*/
private final Vec3i blockInChunk;
private final BlockFace face;
public TileDataStackImpl(Vec3i blockInChunk, BlockFace face) {
this.blockInChunk = blockInChunk;
this.face = face;
}
@Override
public Vec3i getBlockInChunk(Vec3i output) {
if (output == null)
output = new Vec3i();
output.set(blockInChunk.x, blockInChunk.y, blockInChunk.z);
return output;
}
@Override
public BlockFace getFace() {
return face;
}
@Override
public ChunkData getChunk() {
return ChunkData.this;
}
@Override
public int size() {
return size;
}
@Override
public TileData get(int index) {
checkIndex(index, false);
return tiles[index];
}
@Override
public TileData set(int index, TileData tile) {
Objects.requireNonNull(tile, "tile");
TileData previous = get(index); // checks index
tiles[index] = tile;
if (references[index] != null) {
references[index].invalidate();
references[index] = null;
}
assert checkConsistency();
report(previous, tile);
return previous;
}
@Override
public void add(int index, TileData tile) {
Objects.requireNonNull(tile, "tile");
checkIndex(index, true);
if (index != size()) {
System.arraycopy(tiles, index + 1, tiles, index + 2, size - index);
for (int i = index; i < size; ++i) {
if (references[i] != null) {
references[i].incrementIndex();
}
indicesByTag[tagsByIndex[i]]++;
}
System.arraycopy(references, index + 1, references, index + 2, size - index);
System.arraycopy(tagsByIndex, index + 1, tagsByIndex, index + 2, size - index);
}
size++;
tiles[index] = tile;
references[index] = null;
for (int tag = 0; tag < indicesByTag.length; ++tag) {
if (tagsByIndex[tag] == -1) {
indicesByTag[tag] = index;
tagsByIndex[index] = tag;
break;
}
}
modCount++;
assert checkConsistency();
report(null, tile);
}
@Override
public void load(TileData tile, int tag) {
addFarthest(tile);
int assignedTag = getIndexByTag(tag);
if (assignedTag == tag)
return;
if (assignedTag == -1) {
throw new IllegalArgumentException(
"Tag " + tag + " already used by tile at index " + getIndexByTag(tag));
}
indicesByTag[tagsByIndex[size() - 1]] = -1;
tagsByIndex[size() - 1] = tag;
indicesByTag[tag] = size() - 1;
assert checkConsistency();
}
@Override
public TileData remove(int index) {
TileData previous = get(index); // checks index
if (references[index] != null) {
references[index].invalidate();
}
indicesByTag[tagsByIndex[index]] = -1;
if (index != size() - 1) {
System.arraycopy(tiles, index + 1, tiles, index, size - index - 1);
for (int i = index + 1; i < size; ++i) {
if (references[i] != null) {
references[i].decrementIndex();
}
indicesByTag[tagsByIndex[i]]--;
}
System.arraycopy(references, index + 1, references, index, size - index - 1);
System.arraycopy(tagsByIndex, index + 1, tagsByIndex, index, size - index - 1);
}
size--;
tiles[size] = null;
references[size] = null;
tagsByIndex[size] = -1;
modCount++;
assert checkConsistency();
report(previous, null);
return previous;
}
@Override
public TileReference getReference(int index) {
checkIndex(index, false);
if (references[index] == null) {
references[index] = new TileReferenceImpl(index);
}
return references[index];
}
@Override
public int getIndexByTag(int tag) {
return indicesByTag[tag];
}
@Override
public int getTagByIndex(int index) {
checkIndex(index, false);
return tagsByIndex[index];
}
@Override
public void clear() {
while (!isEmpty()) {
removeFarthest();
}
}
private void checkIndex(int index, boolean isSizeAllowed) {
if (isSizeAllowed ? (index > size()) : (index >= size()))
throw new IndexOutOfBoundsException("Index " + index + " is out of bounds: size is " + size);
if (index < 0)
throw new IndexOutOfBoundsException("Index " + index + " is out of bounds: index cannot be negative");
if (index >= TILES_PER_FACE)
throw new TileStackIsFullException(
"Index " + index + " is out of bounds: maximum tile stack size is " + TILES_PER_FACE);
}
private void report(TileData previous, TileData current) {
ChunkData.this.getListeners().forEach(l -> {
if (previous != null) {
l.onChunkTilesChanged(ChunkData.this, blockInChunk, face, previous, false);
}
if (current != null) {
l.onChunkTilesChanged(ChunkData.this, blockInChunk, face, current, true);
}
l.onChunkChanged(ChunkData.this);
});
}
private boolean checkConsistency() {
int index;
for (index = 0; index < size(); ++index) {
if (get(index) == null)
throw new AssertionError("get(index) is null");
if (references[index] != null) {
TileReference ref = getReference(index);
if (ref == null)
throw new AssertionError("references[index] is not null but getReference(index) is");
if (!ref.isValid())
throw new AssertionError("Reference is not valid");
if (ref.get() != get(index))
throw new AssertionError("Reference points to " + (ref.get() == null ? "null" : "wrong tile"));
if (ref.getIndex() != index)
throw new AssertionError("Reference has invalid index");
if (ref.getStack() != this)
throw new AssertionError("Reference has invalid TDS");
}
if (index != indicesByTag[tagsByIndex[index]])
throw new AssertionError("Tag mapping is inconsistent");
if (index != getIndexByTag(getTagByIndex(index)))
throw new AssertionError("Tag methods are inconsistent with tag mapping");
}
for (; index < tiles.length; ++index) {
if (tiles[index] != null)
throw new AssertionError("Leftover tile detected");
if (references[index] != null)
throw new AssertionError("Leftover reference detected");
if (tagsByIndex[index] != -1)
throw new AssertionError("Leftover tags detected");
}
return true;
}
}
public interface ChunkData
extends ChunkDataRO, ChunkGenericWO<BlockData, TileData, TileDataStack, TileDataReference, ChunkData> {
// currently empty
}

View File

@ -15,81 +15,78 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.common.world;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.block.BlockData;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.rels.RelFace;
import ru.windcorp.progressia.common.world.tile.TileData;
public interface ChunkDataListener {
/**
* Invoked after a block has changed in a chunk. This is not triggered when
* a change is caused by chunk loading or unloading.
* Invoked after a block has changed in a chunk.
* This is not triggered when a change is caused by chunk loading or
* unloading.
*
* @param chunk
* the chunk that has changed
* @param blockInChunk
* the {@linkplain Coordinates#blockInChunk chunk coordinates} of
* the change
* @param previous
* the previous occupant of {@code blockInChunk}
* @param current
* the current (new) occupant of {@code blockInChunk}
* @param chunk the chunk that has changed
* @param blockInChunk the {@linkplain Coordinates#blockInChunk chunk
* coordinates} of the change
* @param previous the previous occupant of {@code blockInChunk}
* @param current the current (new) occupant of {@code blockInChunk}
*/
default void onChunkBlockChanged(ChunkData chunk, Vec3i blockInChunk, BlockData previous, BlockData current) {
default void onChunkBlockChanged(DefaultChunkData chunk, Vec3i blockInChunk, BlockData previous, BlockData current) {
}
/**
* Invoked after a tile has been added or removed from a chunk. This is not
* triggered when a change is caused by chunk loading or unloading.
* Invoked after a tile has been added or removed from a chunk.
* This is not triggered when a change is caused by chunk loading or
* unloading.
*
* @param chunk
* the chunk that has changed
* @param blockInChunk
* the {@linkplain Coordinates#blockInChunk chunk coordinates} of
* the change
* @param face
* the face that the changed tile belongs or belonged to
* @param tile
* the tile that has been added or removed
* @param wasAdded
* {@code true} iff the tile has been added, {@code false} iff
* the tile has been removed
* @param chunk the chunk that has changed
* @param blockInChunk the {@linkplain Coordinates#blockInChunk chunk
* coordinates} of the change
* @param face the face that the changed tile belongs or belonged to
* @param tile the tile that has been added or removed
* @param wasAdded {@code true} iff the tile has been added,
* {@code false} iff the tile has been removed
*/
default void onChunkTilesChanged(ChunkData chunk, Vec3i blockInChunk, BlockFace face, TileData tile,
boolean wasAdded) {
default void onChunkTilesChanged(
DefaultChunkData chunk,
Vec3i blockInChunk,
RelFace face,
TileData tile,
boolean wasAdded
) {
}
/**
* Invoked whenever a chunk changes, loads or unloads. If some other method
* in this {@code ChunkDataListener} are to be invoked, e.g. is the change
* was caused by a block being removed, this method is called last.
* in this
* {@code ChunkDataListener} are to be invoked, e.g. is the change was
* caused by a
* block being removed, this method is called last.
*
* @param chunk
* the chunk that has changed
* @param chunk the chunk that has changed
*/
default void onChunkChanged(ChunkData chunk) {
default void onChunkChanged(DefaultChunkData chunk) {
}
/**
* Invoked whenever a chunk has been loaded.
*
* @param chunk
* the chunk that has loaded
* @param chunk the chunk that has loaded
*/
default void onChunkLoaded(ChunkData chunk) {
default void onChunkLoaded(DefaultChunkData chunk) {
}
/**
* Invoked whenever a chunk is about to be unloaded.
*
* @param chunk
* the chunk that is going to be loaded
* @param chunk the chunk that is going to be loaded
*/
default void beforeChunkUnloaded(ChunkData chunk) {
default void beforeChunkUnloaded(DefaultChunkData chunk) {
}
}

View File

@ -28,7 +28,7 @@ public class ChunkDataListeners {
public static WorldDataListener createAdder(Supplier<ChunkDataListener> listenerSupplier) {
return new WorldDataListener() {
@Override
public void getChunkListeners(WorldData world, Vec3i chunk, Consumer<ChunkDataListener> chunkListenerSink) {
public void getChunkListeners(DefaultWorldData world, Vec3i chunk, Consumer<ChunkDataListener> chunkListenerSink) {
chunkListenerSink.accept(listenerSupplier.get());
}
};

View File

@ -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
}

View File

@ -18,7 +18,7 @@
package ru.windcorp.progressia.common.world;
import static ru.windcorp.progressia.common.world.ChunkData.BLOCKS_PER_CHUNK;
import static ru.windcorp.progressia.common.world.DefaultChunkData.BLOCKS_PER_CHUNK;
import glm.vec._3.i.Vec3i;

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -26,6 +26,6 @@ public abstract class PacketAffectWorld extends Packet {
super(id);
}
public abstract void apply(WorldData world);
public abstract void apply(DefaultWorldData world);
}

View File

@ -53,9 +53,9 @@ public class PacketRevokeChunk extends PacketAffectChunk {
}
@Override
public void apply(WorldData world) {
public void apply(DefaultWorldData world) {
synchronized (world) {
ChunkData chunk = world.getChunk(position);
DefaultChunkData chunk = world.getChunk(position);
if (chunk != null) {
world.removeChunk(chunk);
}

View File

@ -41,7 +41,7 @@ public class PacketSendChunk extends PacketAffectChunk {
super(id);
}
public void set(ChunkData chunk) {
public void set(DefaultChunkData chunk) {
this.position.set(chunk.getX(), chunk.getY(), chunk.getZ());
try {
@ -67,7 +67,7 @@ public class PacketSendChunk extends PacketAffectChunk {
}
@Override
public void apply(WorldData world) {
public void apply(DefaultWorldData world) {
try {
world.addChunk(ChunkIO.load(world, position, data.getReader(), IOContext.COMMS));
} catch (DecodingException | IOException e) {

View File

@ -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);
}
}
}

View File

@ -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> {
}

View File

@ -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
}

View File

@ -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();
}

View File

@ -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
}

View File

@ -1,204 +1,52 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.common.world;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.function.Consumer;
import glm.vec._3.i.Vec3i;
import gnu.trove.TCollections;
import gnu.trove.map.TLongObjectMap;
import gnu.trove.map.hash.TLongObjectHashMap;
import gnu.trove.set.TLongSet;
import ru.windcorp.progressia.common.collision.CollisionModel;
import ru.windcorp.progressia.common.world.block.BlockData;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.generic.ChunkMap;
import ru.windcorp.progressia.common.world.generic.ChunkSet;
import ru.windcorp.progressia.common.world.generic.GenericWorld;
import ru.windcorp.progressia.common.world.generic.LongBasedChunkMap;
import ru.windcorp.progressia.common.world.generic.WorldGenericWO;
import ru.windcorp.progressia.common.world.rels.BlockFace;
import ru.windcorp.progressia.common.world.tile.TileData;
import ru.windcorp.progressia.common.world.tile.TileDataStack;
public class WorldData implements GenericWorld<BlockData, TileData, TileDataStack, ChunkData, EntityData> {
private final ChunkMap<ChunkData> chunksByPos = new LongBasedChunkMap<>(
TCollections.synchronizedMap(new TLongObjectHashMap<>()));
private final Collection<ChunkData> chunks = Collections.unmodifiableCollection(chunksByPos.values());
private final TLongObjectMap<EntityData> entitiesById = TCollections.synchronizedMap(new TLongObjectHashMap<>());
private final Collection<EntityData> entities = Collections.unmodifiableCollection(entitiesById.valueCollection());
private float time = 0;
private final Collection<WorldDataListener> listeners = Collections.synchronizedCollection(new ArrayList<>());
public WorldData() {
}
public interface WorldData
extends WorldDataRO, WorldGenericWO<BlockData, TileData, TileDataStack, TileDataReference, ChunkData, EntityData> {
@Override
public ChunkData getChunk(Vec3i pos) {
return chunksByPos.get(pos);
default TileDataStack getTiles(Vec3i blockInWorld, BlockFace face) {
return (TileDataStack) WorldDataRO.super.getTiles(blockInWorld, face);
}
/**
* Increases in-game time of this world by {@code change}. Total time is
* decreased when {@code change} is negative.
*
* @param change the amount of time to add to current world time. May be
* negative.
* @see #getTime()
*/
void advanceTime(float change);
/*
* Method specialization
*/
@Override
public Collection<ChunkData> getChunks() {
return chunks;
}
public ChunkSet getLoadedChunks() {
return chunksByPos.keys();
}
ChunkData getChunk(Vec3i pos);
@Override
public Collection<EntityData> getEntities() {
return entities;
}
Collection<? extends ChunkData> getChunks();
// TODO: rename WGRO.forEachChunk -> forEachChunkRO and define WGWO.forEachChunk
@Override
public void forEachEntity(Consumer<? super EntityData> action) {
synchronized (entitiesById) { // TODO HORRIBLY MUTILATE THE CORPSE OF
// TROVE4J so that
// gnu.trove.impl.sync.SynchronizedCollection.forEach
// is synchronized
getEntities().forEach(action);
}
default ChunkData getChunkByBlock(Vec3i blockInWorld) {
return (ChunkData) WorldDataRO.super.getChunkByBlock(blockInWorld);
}
public TLongSet getLoadedEntities() {
return entitiesById.keySet();
}
private void addChunkListeners(ChunkData chunk) {
getListeners().forEach(l -> l.getChunkListeners(this, chunk.getPosition(), chunk::addListener));
}
public synchronized void addChunk(ChunkData chunk) {
addChunkListeners(chunk);
ChunkData previous = chunksByPos.get(chunk);
if (previous != null) {
throw new IllegalArgumentException(String.format("Chunk at (%d; %d; %d) already exists",
chunk.getPosition().x, chunk.getPosition().y, chunk.getPosition().z));
}
chunksByPos.put(chunk, chunk);
chunk.onLoaded();
getListeners().forEach(l -> l.onChunkLoaded(this, chunk));
}
public synchronized void removeChunk(ChunkData chunk) {
getListeners().forEach(l -> l.beforeChunkUnloaded(this, chunk));
chunk.beforeUnloaded();
chunksByPos.remove(chunk);
}
public void setBlock(Vec3i blockInWorld, BlockData block, boolean notify) {
ChunkData chunk = getChunkByBlock(blockInWorld);
if (chunk == null)
throw new IllegalCoordinatesException("Coordinates " + "(" + blockInWorld.x + "; " + blockInWorld.y + "; "
+ blockInWorld.z + ") " + "do not belong to a loaded chunk");
chunk.setBlock(Coordinates.convertInWorldToInChunk(blockInWorld, null), block, notify);
}
public EntityData getEntity(long entityId) {
return entitiesById.get(entityId);
}
public void addEntity(EntityData entity) {
Objects.requireNonNull(entity, "entity");
EntityData previous = entitiesById.putIfAbsent(entity.getEntityId(), entity);
if (previous != null) {
String message = "Cannot add entity " + entity + ": ";
if (previous == entity) {
message += "already present";
} else {
message += "entity with the same EntityID already present (" + previous + ")";
}
throw new IllegalStateException(message);
}
getListeners().forEach(l -> l.onEntityAdded(this, entity));
}
public void removeEntity(long entityId) {
synchronized (entitiesById) {
EntityData entity = entitiesById.get(entityId);
if (entity == null) {
throw new IllegalArgumentException(
"Entity with EntityID " + EntityData.formatEntityId(entityId) + " not present");
} else {
removeEntity(entity);
}
}
}
public void removeEntity(EntityData entity) {
Objects.requireNonNull(entity, "entity");
getListeners().forEach(l -> l.beforeEntityRemoved(this, entity));
entitiesById.remove(entity.getEntityId());
}
public float getTime() {
return time;
}
public void advanceTime(float change) {
this.time += change;
}
public CollisionModel getCollisionModelOfBlock(Vec3i blockInWorld) {
ChunkData chunk = getChunkByBlock(blockInWorld);
if (chunk == null)
return null;
BlockData block = chunk.getBlock(Coordinates.convertInWorldToInChunk(blockInWorld, null));
if (block == null)
return null;
return block.getCollisionModel();
}
public Collection<WorldDataListener> getListeners() {
return listeners;
}
public void addListener(WorldDataListener e) {
listeners.add(e);
}
public void removeListener(WorldDataListener o) {
listeners.remove(o);
@Override
default TileDataStack getTilesOrNull(Vec3i blockInWorld, BlockFace face) {
return (TileDataStack) WorldDataRO.super.getTilesOrNull(blockInWorld, face);
}
}

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.common.world;
import java.util.function.Consumer;
@ -26,67 +26,58 @@ import ru.windcorp.progressia.common.world.entity.EntityData;
public interface WorldDataListener {
/**
* Invoked when a new {@link ChunkData} instance is created. This method
* should be used to add {@link ChunkDataListener}s to a new chunk. When
* listeners are added with this method, their
* {@link ChunkDataListener#onChunkLoaded(ChunkData) onChunkLoaded} methods
* will be invoked.
* Invoked when a new {@link DefaultChunkData} instance is created. This method
* should be used to add
* {@link ChunkDataListener}s to a new chunk. When listeners are added with
* this method,
* their {@link ChunkDataListener#onChunkLoaded(DefaultChunkData) onChunkLoaded}
* methods will be invoked.
*
* @param world
* the world instance
* @param chunk
* the {@linkplain Coordinates#chunk coordinates of chunk} of the
* chunk about to load
* @param chunkListenerSink
* a sink for listeners. All listeners passed to its
* {@link Consumer#accept(Object) accept} method will be added to
* the chunk.
* @param world the world instance
* @param chunk the {@linkplain Coordinates#chunk coordinates of
* chunk} of the chunk about to load
* @param chunkListenerSink a sink for listeners. All listeners passed to
* its
* {@link Consumer#accept(Object) accept} method
* will be added to the chunk.
*/
default void getChunkListeners(WorldData world, Vec3i chunk, Consumer<ChunkDataListener> chunkListenerSink) {
default void getChunkListeners(DefaultWorldData world, Vec3i chunk, Consumer<ChunkDataListener> chunkListenerSink) {
}
/**
* Invoked whenever a {@link Chunk} has been loaded.
*
* @param world
* the world instance
* @param chunk
* the chunk that has loaded
* @param world the world instance
* @param chunk the chunk that has loaded
*/
default void onChunkLoaded(WorldData world, ChunkData chunk) {
default void onChunkLoaded(DefaultWorldData world, DefaultChunkData chunk) {
}
/**
* Invoked whenever a {@link Chunk} is about to be unloaded.
*
* @param world
* the world instance
* @param chunk
* the chunk that is going to be unloaded
* @param world the world instance
* @param chunk the chunk that is going to be unloaded
*/
default void beforeChunkUnloaded(WorldData world, ChunkData chunk) {
default void beforeChunkUnloaded(DefaultWorldData world, DefaultChunkData chunk) {
}
/**
* Invoked whenever an {@link EntityData} has been added.
*
* @param world
* the world instance
* @param entity
* the entity that has been added
* @param world the world instance
* @param entity the entity that has been added
*/
default void onEntityAdded(WorldData world, EntityData entity) {
default void onEntityAdded(DefaultWorldData world, EntityData entity) {
}
/**
* Invoked whenever an {@link EntityData} is about to be removed.
*
* @param world
* the world instance
* @param entity
* the entity that is going to be removed
* @param world the world instance
* @param entity the entity that is going to be removed
*/
default void beforeEntityRemoved(WorldData world, EntityData entity) {
default void beforeEntityRemoved(DefaultWorldData world, EntityData entity) {
}
}

View File

@ -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();
}

View File

@ -21,9 +21,9 @@ package ru.windcorp.progressia.common.world.block;
import ru.windcorp.progressia.common.collision.AABB;
import ru.windcorp.progressia.common.collision.CollisionModel;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import ru.windcorp.progressia.common.world.generic.GenericBlock;
import ru.windcorp.progressia.common.world.generic.BlockGeneric;
public class BlockData extends Namespaced implements GenericBlock {
public class BlockData extends Namespaced implements BlockGeneric {
public BlockData(String id) {
super(id);

View File

@ -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();
}
}

View File

@ -24,7 +24,7 @@ import java.io.IOException;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.DecodingException;
import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.common.world.DefaultWorldData;
public class PacketSetBlock extends PacketAffectBlock {
@ -60,7 +60,7 @@ public class PacketSetBlock extends PacketAffectBlock {
}
@Override
public void apply(WorldData world) {
public void apply(DefaultWorldData world) {
BlockData block = BlockDataRegistry.getInstance().get(getBlockId());
world.setBlock(getBlockInWorld(), block, true);
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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