15 Commits

Author SHA1 Message Date
8c7c37c9c0 Code cleanup 2021-07-14 18:23:10 +03:00
3b24ee0531 Documentation for module system and minor changes. 2021-07-10 21:46:15 +03:00
953614a7d8 Now you cannot run tasks second time or if requirements are not met 2021-07-10 20:19:57 +03:00
7f57510ac8 Licensed module system. Minor changes
- Added license commetary to each file of module system
- Added default meta info generation for Module
2021-07-10 08:29:09 +03:00
c7842935ba Loaders are observable. Modules refactoring
- Now it is possible to monitor loaders and tasks they perform
- Now it is NOT possible to add  dublicate tasks and modules
2021-07-09 18:12:30 +03:00
f520d4b2c6 TaskManager bug fixes and code cleanup 2021-07-07 17:02:38 +03:00
5a06788652 Refactored modules system 2021-07-07 13:37:11 +03:00
56e9be727b Removed debug code 2021-07-05 11:32:58 +03:00
e795d76367 Update .gitignore
- added log files to ignore
2021-07-05 11:32:11 +03:00
a9724d9d4c Developed module loader algorithm 2021-06-30 19:59:30 +03:00
3859db5b27 Added TaskManager
- Working on modules system
2021-06-27 18:27:56 +03:00
513feb1093 Merge branch 'master' into moduleSystem 2021-06-12 21:05:15 +03:00
942c665d73 Developing module systme
- Added Module class
- minor changes in Task class
2021-01-10 15:30:05 +03:00
5a521fc131 Refactored Task class
- Now crash reports have stacktrace
- Minor changes
2021-01-09 20:52:19 +03:00
361d795ab1 Added Task class 2021-01-09 19:36:11 +03:00
401 changed files with 5423 additions and 20611 deletions

1
.gitignore vendored
View File

@ -37,3 +37,4 @@ build_packages/NSIS/*
!build_packages/DEB
build_packages/DEB/*
!build_packages/DEB/template
*.log

View File

@ -111,6 +111,8 @@ switch (OperatingSystem.current()) {
}
dependencies {
implementation 'org.jetbrains:annotations:20.1.0'
implementation platform("org.lwjgl:lwjgl-bom:$lwjglVersion")
implementation "org.lwjgl:lwjgl"

View File

@ -42,16 +42,3 @@ Run configurations are used by Intellij IDEA to specify how a project must be ru
9. Click 'Apply' to save changes.
Step 8 is required to specify that the game must run in some directory other than the project root, which is the default in Intellij IDEA.
### Applying formatting templates
Windcorp's Progressia repository is formatted with a style defined for Eclipse IDE (sic) in
`templates_and_presets/eclipse_ide`.
Please apply these templates to the project to automatically format the source in a similar fashion.
1. In project context menu, click 'File->Properties'. (`Ctrl+Alt+S`)
2. In 'Editor' > 'Code Style' > 'Java', press gear icon, then click 'Import Scheme' > 'Eclipse code style'
3. In Scheme select 'Project'
4. Open the file `templates_and_presets/eclipse_ide/FormatterProfile.xml` in 'Select Path'.
5. Inside 'Import Scheme' widow click 'Current Scheme' check box after press OK

View File

@ -18,28 +18,18 @@
package ru.windcorp.progressia;
import ru.windcorp.progressia.client.graphics.GUI;
import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.common.util.crash.analyzers.OutOfMemoryAnalyzer;
import ru.windcorp.progressia.common.util.crash.providers.*;
import ru.windcorp.progressia.test.LayerTitle;
public class ProgressiaLauncher {
public static String[] arguments;
private static Proxy proxy;
public static void launch(String[] args, Proxy proxy) {
arguments = args.clone();
setupCrashReports();
proxy.initialize();
ProgressiaLauncher.proxy = proxy;
GUI.addTopLayer(new LayerTitle("Title"));
}
public static Proxy getProxy() {
return proxy;
}
private static void setupCrashReports() {

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.DefaultWorldData;
import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.common.world.entity.EntityData;
public class Client {
@ -36,7 +36,7 @@ public class Client {
private final ServerCommsChannel comms;
public Client(DefaultWorldData world, ServerCommsChannel comms) {
public Client(WorldData world, ServerCommsChannel comms) {
this.world = new WorldRender(world, this);
this.comms = comms;

View File

@ -28,8 +28,10 @@ import ru.windcorp.progressia.client.graphics.font.Typefaces;
import ru.windcorp.progressia.client.graphics.texture.Atlases;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.localization.Localizer;
import ru.windcorp.progressia.common.modules.TaskManager;
import ru.windcorp.progressia.common.resource.ResourceManager;
import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.server.ServerState;
import ru.windcorp.progressia.test.TestContent;
import ru.windcorp.progressia.test.TestMusicPlayer;
@ -37,9 +39,7 @@ public class ClientProxy implements Proxy {
@Override
public void initialize() {
GraphicsBackend.initialize();
try {
RenderTaskQueue.waitAndInvoke(FlatRenderProgram::init);
RenderTaskQueue.waitAndInvoke(WorldRenderProgram::init);
@ -58,7 +58,13 @@ public class ClientProxy implements Proxy {
Atlases.loadAllAtlases();
AudioSystem.initialize();
TaskManager.getInstance().startLoading();
ServerState.startServer();
ClientState.connectToLocalServer();
TestMusicPlayer.start();
}
}

View File

@ -20,13 +20,10 @@ 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.Layer;
import ru.windcorp.progressia.client.graphics.world.LayerWorld;
import ru.windcorp.progressia.common.world.DefaultWorldData;
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.server.ServerState;
import ru.windcorp.progressia.test.LayerAbout;
import ru.windcorp.progressia.test.LayerTestText;
import ru.windcorp.progressia.test.LayerTestUI;
import ru.windcorp.progressia.test.TestContent;
@ -44,7 +41,7 @@ public class ClientState {
public static void connectToLocalServer() {
DefaultWorldData world = new DefaultWorldData();
WorldData world = new WorldData();
LocalServerCommsChannel channel = new LocalServerCommsChannel(
ServerState.getInstance()
@ -55,39 +52,11 @@ public class ClientState {
channel.connect(TestContent.PLAYER_LOGIN);
setInstance(client);
displayLoadingScreen();
}
GUI.addBottomLayer(new LayerWorld(client));
GUI.addTopLayer(new LayerTestUI());
GUI.addTopLayer(new LayerAbout());
private static void displayLoadingScreen() {
GUI.addTopLayer(new LayerTestText("Text", new MutableStringLocalized("LayerText.Load"), layer -> {
Client client = ClientState.getInstance();
// TODO refacetor and remove
if (client != null) {
client.getComms().processPackets();
}
if (client != null && client.getLocalPlayer().hasEntity()) {
GUI.removeLayer(layer);
// TODO refactor, this shouldn't be here
LayerWorld layerWorld = new LayerWorld(client);
LayerTestUI layerUI = new LayerTestUI();
LayerAbout layerAbout = new LayerAbout();
GUI.addBottomLayer(layerWorld);
GUI.addTopLayer(layerUI);
GUI.addTopLayer(layerAbout);
}
}));
}
public static void disconnectFromLocalServer() {
getInstance().getComms().disconnect();
for (Layer layer : GUI.getLayers()) {
GUI.removeLayer(layer);
}
}
private ClientState() {

View File

@ -18,14 +18,28 @@
package ru.windcorp.progressia.client.audio;
import org.apache.logging.log4j.LogManager;
import ru.windcorp.progressia.common.modules.Module;
import ru.windcorp.progressia.common.modules.Task;
import ru.windcorp.progressia.common.modules.TaskManager;
import ru.windcorp.progressia.common.resource.ResourceManager;
public class AudioSystem {
static public void initialize() {
Module audioModule = new Module("AudioModule:System");
AudioManager.initAL();
Thread shutdownHook = new Thread(AudioManager::closeAL, "AL Shutdown Hook");
Runtime.getRuntime().addShutdownHook(shutdownHook);
Task t = new Task("AudioSystem:Initialize") {
@Override
protected void perform() {
loadAudioData();
LogManager.getLogger().info("Audio data is loaded");
}
};
audioModule.addTask(t);
TaskManager.getInstance().registerModule(audioModule);
}
static void loadAudioData() {

View File

@ -21,7 +21,6 @@ package ru.windcorp.progressia.client.comms.localhost;
import ru.windcorp.progressia.client.comms.ServerCommsChannel;
import ru.windcorp.progressia.common.comms.packets.Packet;
import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.ServerState;
public class LocalServerCommsChannel extends ServerCommsChannel {
@ -55,7 +54,7 @@ public class LocalServerCommsChannel extends ServerCommsChannel {
@Override
public void disconnect() {
ServerState.getInstance().getClientManager().disconnectClient(localClient);
// Do nothing
}
}

View File

@ -34,13 +34,7 @@ public class Colors {
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);
DEBUG_YELLOW = toVector(0xFFFFFF00);
public static Vec4 toVector(int argb) {
return toVector(argb, new Vec4());

View File

@ -21,11 +21,9 @@ package ru.windcorp.progressia.client.graphics;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
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;
@ -59,27 +57,15 @@ public class GUI {
}
public static void addBottomLayer(Layer layer) {
Objects.requireNonNull(layer, "layer");
modify(layers -> {
layers.add(layer);
layer.onAdded();
});
modify(layers -> layers.add(layer));
}
public static void addTopLayer(Layer layer) {
Objects.requireNonNull(layer, "layer");
modify(layers -> {
layers.add(0, layer);
layer.onAdded();
});
modify(layers -> layers.add(0, layer));
}
public static void removeLayer(Layer layer) {
Objects.requireNonNull(layer, "layer");
modify(layers -> {
layers.remove(layer);
layer.onRemoved();
});
modify(layers -> layers.remove(layer));
}
private static void modify(LayerStackModification mod) {
@ -92,33 +78,12 @@ public class GUI {
public static void render() {
synchronized (LAYERS) {
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

@ -31,52 +31,15 @@ public abstract class Layer {
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();
@ -116,12 +79,4 @@ public abstract class Layer {
return GraphicsInterface.getFrameHeight();
}
protected void onAdded() {
// Do nothing
}
protected void onRemoved() {
// Do nothing
}
}

View File

@ -19,7 +19,6 @@
package ru.windcorp.progressia.client.graphics.backend;
import glm.vec._2.i.Vec2i;
import org.lwjgl.glfw.GLFWVidMode;
import static org.lwjgl.glfw.GLFW.*;
@ -44,13 +43,6 @@ public class GraphicsBackend {
private static boolean isGLFWInitialized = false;
private static boolean isOpenGLInitialized = false;
private static boolean allowDisablingCursor;
static {
String key = GraphicsBackend.class.getName() + ".allowDisablingCursor";
allowDisablingCursor = Boolean.parseBoolean(System.getProperty(key, "true"));
}
private static boolean forceCursorToCenter = false;
private GraphicsBackend() {
}
@ -122,10 +114,6 @@ public class GraphicsBackend {
frameLength = now - frameStart;
frameStart = now;
}
if (forceCursorToCenter) {
glfwSetCursorPos(windowHandle, FRAME_SIZE.x / 2.0, FRAME_SIZE.y / 2.0);
}
}
static void endFrame() {
@ -204,26 +192,4 @@ public class GraphicsBackend {
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
return vidmode.refreshRate();
}
public static boolean isMouseCaptured() {
if (!allowDisablingCursor) {
return forceCursorToCenter;
}
return glfwGetInputMode(windowHandle, GLFW_CURSOR) == GLFW_CURSOR_DISABLED;
}
public static void setMouseCaptured(boolean capture) {
if (!allowDisablingCursor) {
forceCursorToCenter = capture;
return;
}
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

@ -82,12 +82,4 @@ 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,6 +65,8 @@ 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

@ -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.ShapePart;
import ru.windcorp.progressia.client.graphics.model.ShapeParts;
import ru.windcorp.progressia.client.graphics.model.Face;
import ru.windcorp.progressia.client.graphics.model.Faces;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.texture.Texture;
@ -84,7 +84,7 @@ public class RenderTarget {
private final Deque<TransformedMask> maskStack = new LinkedList<>();
private final Deque<Mat4> transformStack = new LinkedList<>();
private final List<ShapePart> currentClipFaces = new ArrayList<>();
private final List<Face> currentClipFaces = new ArrayList<>();
private int depth = 0;
@ -94,8 +94,8 @@ public class RenderTarget {
protected void assembleCurrentClipFromFaces() {
if (!currentClipFaces.isEmpty()) {
ShapePart[] faces = currentClipFaces.toArray(
new ShapePart[currentClipFaces.size()]
Face[] faces = currentClipFaces.toArray(
new Face[currentClipFaces.size()]
);
currentClipFaces.clear();
@ -189,13 +189,16 @@ public class RenderTarget {
public void addCustomRenderer(Renderable renderable) {
assembleCurrentClipFromFaces();
float depth = this.depth--;
Mat4 transform = new Mat4().translate(0, 0, depth).mul(getTransform());
assembled.add(new Clip(maskStack, transform, renderable));
assembled.add(
new Clip(
maskStack,
getTransform(),
renderable
)
);
}
protected void addFaceToCurrentClip(ShapePart face) {
protected void addFaceToCurrentClip(Face face) {
currentClipFaces.add(face);
}
@ -267,7 +270,7 @@ public class RenderTarget {
fill(Colors.toVector(color));
}
public ShapePart createRectagleFace(
public Face createRectagleFace(
int x,
int y,
int width,
@ -277,7 +280,7 @@ public class RenderTarget {
) {
float depth = this.depth--;
return ShapeParts.createRectangle(
return Faces.createRectangle(
FlatRenderProgram.getDefault(),
texture,
color,
@ -288,7 +291,7 @@ public class RenderTarget {
);
}
public ShapePart createRectagleFace(
public Face createRectagleFace(
int x,
int y,
int width,

View File

@ -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.ShapePart;
import ru.windcorp.progressia.client.graphics.model.ShapeParts;
import ru.windcorp.progressia.client.graphics.model.Face;
import ru.windcorp.progressia.client.graphics.model.Faces;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
@ -144,7 +144,7 @@ public abstract class SpriteTypeface extends Typeface {
return new Shape(
Usage.STATIC,
getProgram(),
ShapeParts.createRectangle(
Faces.createRectangle(
getProgram(),
getTexture(c),
Colors.WHITE,
@ -167,7 +167,7 @@ public abstract class SpriteTypeface extends Typeface {
private final Renderable unitLine = new Shape(
Usage.STATIC,
getProgram(),
ShapeParts.createRectangle(
Faces.createRectangle(
getProgram(),
null,
Vectors.UNIT_4,
@ -257,7 +257,7 @@ public abstract class SpriteTypeface extends Typeface {
private class SDWorkspace extends SpriteTypeface.Workspace {
private final Collection<ShapePart> faces = new ArrayList<>();
private final Collection<Face> faces = new ArrayList<>();
private final Vec3 origin = new Vec3();
private final Vec3 width = new Vec3();
@ -298,7 +298,7 @@ public abstract class SpriteTypeface extends Typeface {
workspace.height.sub(workspace.origin);
workspace.faces.add(
ShapeParts.createRectangle(
Faces.createRectangle(
getProgram(),
texture,
color,
@ -314,7 +314,7 @@ public abstract class SpriteTypeface extends Typeface {
return new Shape(
Usage.STATIC,
getProgram(),
workspace.faces.toArray(new ShapePart[workspace.faces.size()])
workspace.faces.toArray(new Face[workspace.faces.size()])
);
}

View File

@ -1,156 +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.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, Label label) {
super(name);
this.label = 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, Font labelFont) {
this(name, new Label(name + ".Label", labelFont, label));
}
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;
requestReassembly();
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

@ -1,83 +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.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, Label label) {
super(name, label);
}
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

@ -1,149 +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.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,23 +19,18 @@
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;
@ -67,8 +62,6 @@ 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;
@ -292,31 +285,10 @@ 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;
return this;
@ -365,7 +337,7 @@ public class Component extends Named {
return;
}
if (component.canGainFocusNow()) {
if (component.isFocusable()) {
setFocused(false);
component.setFocused(true);
return;
@ -407,7 +379,7 @@ public class Component extends Named {
return;
}
if (component.canGainFocusNow()) {
if (component.isFocusable()) {
setFocused(false);
component.setFocused(true);
return;
@ -461,51 +433,12 @@ 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 && isEnabled()) {
if (this.isHovered != isHovered) {
this.isHovered = isHovered;
if (!isHovered && !getChildren().isEmpty()) {
@ -569,7 +502,7 @@ public class Component extends Named {
}
protected void handleInput(Input input) {
if (inputBus != null && isEnabled()) {
if (inputBus != null) {
inputBus.dispatch(input);
}
}
@ -665,17 +598,6 @@ 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
}
@ -716,135 +638,6 @@ public class Component extends Named {
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(triggerObject);
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.
// * @return a {@link Aligner} initialized to center this component

View File

@ -83,10 +83,6 @@ public class Label extends Component {
return font;
}
public void setFont(Font font) {
this.font = font;
}
public String getCurrentText() {
return currentText;
}
@ -100,7 +96,11 @@ public class Label extends Component {
float startX = getX() + font.getAlign() * (getWidth() - currentSize.x);
target.pushTransform(
new Mat4().identity().translate(startX, getY(), 0).scale(2)
new Mat4().identity().translate(startX, getY(), -1000) // TODO wtf
// is this
// magic
// <---
.scale(2)
);
target.addCustomRenderer(font.assemble(currentText, maxWidth));

View File

@ -15,66 +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;
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 class Panel extends Component {
public Panel(String name, Layout 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);
}
super(name);
setLayout(layout);
}
}

View File

@ -1,208 +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.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

@ -1,119 +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.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

@ -1,60 +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.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

@ -1,11 +0,0 @@
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

@ -1,78 +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.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

@ -98,26 +98,15 @@ public class LayoutGrid implements Layout {
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() + parent.getHeight() - (rows[row] + height),
parent.getY() + rows[row],
width,
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]))
);
}
}
@ -143,9 +132,10 @@ public class LayoutGrid implements Layout {
GridDimensions grid = calculateGrid(c);
grid.sum();
int[] coords;
for (Component child : c.getChildren()) {
Vec2i coords = (Vec2i) child.getLayoutHint();
grid.setBounds(coords.x, coords.y, child, c);
coords = (int[]) child.getLayoutHint();
grid.setBounds(coords[0], coords[1], child, c);
}
}
}
@ -159,10 +149,11 @@ public class LayoutGrid implements Layout {
private GridDimensions calculateGrid(Component parent) {
GridDimensions result = new GridDimensions();
int[] coords;
for (Component child : parent.getChildren()) {
Vec2i coords = (Vec2i) child.getLayoutHint();
result.add(coords.x, coords.y, child.getPreferredSize());
coords = (int[]) child.getLayoutHint();
result.add(coords[0], coords[1], child.getPreferredSize());
}
return result;

View File

@ -1,117 +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.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

@ -18,23 +18,23 @@
package ru.windcorp.progressia.client.graphics.model;
import static ru.windcorp.progressia.common.world.rels.AbsFace.*;
import static ru.windcorp.progressia.common.world.block.BlockFace.*;
import com.google.common.collect.ImmutableMap;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.common.world.rels.AbsFace;
import ru.windcorp.progressia.common.world.block.BlockFace;
class BlockFaceVectors {
private static BlockFaceVectors createInner(BlockFaceVectors outer) {
ImmutableMap.Builder<AbsFace, Vec3> originBuilder = ImmutableMap.builder();
ImmutableMap.Builder<BlockFace, Vec3> originBuilder = ImmutableMap.builder();
ImmutableMap.Builder<AbsFace, Vec3> widthBuilder = ImmutableMap.builder();
ImmutableMap.Builder<BlockFace, Vec3> widthBuilder = ImmutableMap.builder();
ImmutableMap.Builder<AbsFace, Vec3> heightBuilder = ImmutableMap.builder();
ImmutableMap.Builder<BlockFace, Vec3> heightBuilder = ImmutableMap.builder();
for (AbsFace face : getFaces()) {
for (BlockFace face : getFaces()) {
Vec3 width = outer.getWidth(face);
Vec3 height = outer.getHeight(face);
@ -59,36 +59,36 @@ class BlockFaceVectors {
static {
OUTER = new BlockFaceVectors(
ImmutableMap.<AbsFace, Vec3>builder()
ImmutableMap.<BlockFace, Vec3>builder()
.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))
.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))
.build(),
ImmutableMap.<AbsFace, Vec3>builder()
ImmutableMap.<BlockFace, Vec3>builder()
.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))
.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))
.build(),
ImmutableMap.<AbsFace, Vec3>builder()
ImmutableMap.<BlockFace, Vec3>builder()
.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))
.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))
.build()
);
@ -100,29 +100,29 @@ class BlockFaceVectors {
return inner ? INNER : OUTER;
}
private final ImmutableMap<AbsFace, Vec3> origins;
private final ImmutableMap<AbsFace, Vec3> widths;
private final ImmutableMap<AbsFace, Vec3> heights;
private final ImmutableMap<BlockFace, Vec3> origins;
private final ImmutableMap<BlockFace, Vec3> widths;
private final ImmutableMap<BlockFace, Vec3> heights;
public BlockFaceVectors(
ImmutableMap<AbsFace, Vec3> origins,
ImmutableMap<AbsFace, Vec3> widths,
ImmutableMap<AbsFace, Vec3> heights
ImmutableMap<BlockFace, Vec3> origins,
ImmutableMap<BlockFace, Vec3> widths,
ImmutableMap<BlockFace, Vec3> heights
) {
this.origins = origins;
this.widths = widths;
this.heights = heights;
}
public Vec3 getOrigin(AbsFace face) {
public Vec3 getOrigin(BlockFace face) {
return origins.get(face);
}
public Vec3 getWidth(AbsFace face) {
public Vec3 getWidth(BlockFace face) {
return widths.get(face);
}
public Vec3 getHeight(AbsFace face) {
public Vec3 getHeight(BlockFace face) {
return heights.get(face);
}
}

View File

@ -24,7 +24,7 @@ import java.util.Objects;
import ru.windcorp.progressia.client.graphics.texture.Texture;
public class ShapePart implements Comparable<ShapePart> {
public class Face implements Comparable<Face> {
private static final ShortBuffer GENERATE_SUCCESSIVE_LATER = null;
@ -40,7 +40,7 @@ public class ShapePart implements Comparable<ShapePart> {
private ShortBuffer userIndices;
private boolean userIndicesUpdated = true;
public ShapePart(
public Face(
Texture texture,
ByteBuffer vertices,
ShortBuffer indices
@ -50,7 +50,7 @@ public class ShapePart implements Comparable<ShapePart> {
setIndices(indices);
}
public ShapePart(
public Face(
Texture texture,
ByteBuffer vertices
) {
@ -155,7 +155,7 @@ public class ShapePart implements Comparable<ShapePart> {
return vertices;
}
public ShapePart setVertices(ByteBuffer vertices) {
public Face setVertices(ByteBuffer vertices) {
this.vertices = Objects.requireNonNull(vertices, "vertices");
markForVertexUpdate();
return this;
@ -202,7 +202,7 @@ public class ShapePart implements Comparable<ShapePart> {
return userIndices.remaining();
}
public ShapePart setIndices(ShortBuffer indices) {
public Face setIndices(ShortBuffer indices) {
if (indices == null) {
indices = GENERATE_SUCCESSIVE_LATER;
}
@ -245,7 +245,7 @@ public class ShapePart implements Comparable<ShapePart> {
}
@Override
public int compareTo(ShapePart o) {
public int compareTo(Face 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 ShapePartGroup {
public class FaceGroup {
private final TexturePrimitive texture;
private final int indexCount;
private final int byteOffsetOfIndices;
ShapePartGroup(ShapePart[] faces, int start, int end) {
FaceGroup(Face[] faces, int start, int end) {
Texture t = faces[start].getTexture();
this.texture = t == null ? null : t.getSprite().getPrimitive();
@ -36,7 +36,7 @@ public class ShapePartGroup {
int indexCount = 0;
for (int i = start; i < end; ++i) {
ShapePart face = faces[i];
Face face = faces[i];
assert this.texture == null
? (face.getTexture() == null)

View File

@ -25,14 +25,14 @@ 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.rels.AbsFace;
import ru.windcorp.progressia.common.world.block.BlockFace;
public class ShapeParts {
public class Faces {
private ShapeParts() {
private Faces() {
}
public static ShapePart createRectangle(
public static Face createRectangle(
ShapeRenderProgram program,
Texture texture,
Vec4 colorMultiplier,
@ -82,19 +82,19 @@ public class ShapeParts {
}
);
return new ShapePart(
return new Face(
texture,
builder.assemble(),
buffer
);
}
public static ShapePart createBlockFace(
public static Face createBlockFace(
ShapeRenderProgram program,
Texture texture,
Vec4 colorMultiplier,
Vec3 blockCenter,
AbsFace face,
BlockFace face,
boolean inner
) {
BlockFaceVectors vectors = BlockFaceVectors.get(inner);

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

View File

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

View File

@ -20,13 +20,11 @@ package ru.windcorp.progressia.client.graphics.model;
import java.util.Map;
import glm.mat._4.Mat4;
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.util.VectorUtil;
import ru.windcorp.progressia.common.world.rels.AbsFace;
import ru.windcorp.progressia.common.world.block.BlockFace;
public class Shapes {
@ -52,7 +50,7 @@ public class Shapes {
boolean flip
) {
ShapePart top = ShapeParts.createRectangle(
Face top = Faces.createRectangle(
program,
topTexture,
colorMultiplier,
@ -62,7 +60,7 @@ public class Shapes {
flip
);
ShapePart bottom = ShapeParts.createRectangle(
Face bottom = Faces.createRectangle(
program,
bottomTexture,
colorMultiplier,
@ -72,7 +70,7 @@ public class Shapes {
flip
);
ShapePart north = ShapeParts.createRectangle(
Face north = Faces.createRectangle(
program,
northTexture,
colorMultiplier,
@ -82,7 +80,7 @@ public class Shapes {
flip
);
ShapePart south = ShapeParts.createRectangle(
Face south = Faces.createRectangle(
program,
southTexture,
colorMultiplier,
@ -92,7 +90,7 @@ public class Shapes {
flip
);
ShapePart east = ShapeParts.createRectangle(
Face east = Faces.createRectangle(
program,
eastTexture,
colorMultiplier,
@ -102,7 +100,7 @@ public class Shapes {
flip
);
ShapePart west = ShapeParts.createRectangle(
Face west = Faces.createRectangle(
program,
westTexture,
colorMultiplier,
@ -167,16 +165,16 @@ public class Shapes {
public PppBuilder(
ShapeRenderProgram program,
Map<AbsFace, Texture> textureMap
Map<BlockFace, 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)
textureMap.get(BlockFace.TOP),
textureMap.get(BlockFace.BOTTOM),
textureMap.get(BlockFace.NORTH),
textureMap.get(BlockFace.SOUTH),
textureMap.get(BlockFace.EAST),
textureMap.get(BlockFace.WEST)
);
}
@ -262,34 +260,6 @@ public class Shapes {
return this.setSize(size, size, size);
}
public PppBuilder centerAt(float x, float y, float z) {
origin.set(x, y, z);
origin.mul(2);
origin.sub(width);
origin.sub(height);
origin.sub(depth);
origin.div(2);
return this;
}
public PppBuilder apply(Mat4 transform) {
VectorUtil.applyMat4(origin, transform);
VectorUtil.rotateOnly(width, transform);
VectorUtil.rotateOnly(height, transform);
VectorUtil.rotateOnly(depth, transform);
return this;
}
public PppBuilder scale(float factor) {
origin.mul(factor);
width.mul(factor);
height.mul(factor);
depth.mul(factor);
return this;
}
public PppBuilder flip() {
this.flip = true;
return this;

View File

@ -21,7 +21,7 @@ package ru.windcorp.progressia.client.graphics.texture;
import java.util.Map;
import glm.vec._2.Vec2;
import ru.windcorp.progressia.common.world.rels.AbsFace;
import ru.windcorp.progressia.common.world.block.BlockFace;
public class ComplexTexture {
@ -54,14 +54,14 @@ public class ComplexTexture {
);
}
public Map<AbsFace, Texture> getCuboidTextures(
public Map<BlockFace, Texture> getCuboidTextures(
int x,
int y,
int width,
int height,
int depth
) {
return AbsFace.mapToFaces(
return BlockFace.mapToFaces(
get(
x + depth + width,
y + height + depth,
@ -86,7 +86,7 @@ public class ComplexTexture {
);
}
public Map<AbsFace, Texture> getCuboidTextures(
public Map<BlockFace, Texture> getCuboidTextures(
int x,
int y,
int size

View File

@ -29,9 +29,6 @@ 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 {
@ -63,13 +60,13 @@ public class Camera {
}
}
Vec3 getCameraPosition(Vec3 output);
void getCameraPosition(Vec3 output);
Vec3 getCameraVelocity(Vec3 output);
void getCameraVelocity(Vec3 output);
Vec3 getLookingAt(Vec3 output);
float getCameraYaw();
Vec3 getUpVector(Vec3 output);
float getCameraPitch();
Collection<Mode> getCameraModes();
@ -87,11 +84,14 @@ public class Camera {
*/
private final Vec3 lastAnchorPosition = new Vec3();
private final Vec3 lastAnchorLookingAt = new Vec3();
private final Vec3 lastAnchorUpVector = new Vec3();
private float lastAnchorYaw;
private float lastAnchorPitch;
private final Mat4 lastCameraMatrix = new Mat4();
private final Vec3 lastAnchorLookingAt = new Vec3();
private final Vec3 lastAnchorUp = new Vec3();
{
invalidateCache();
}
@ -108,9 +108,6 @@ public class Camera {
*/
public void apply(WorldRenderHelper helper) {
if (NPedModel.flag) {
// System.out.println("Camera.apply()");
}
applyPerspective(helper);
rotateCoordinateSystem(helper);
@ -152,34 +149,26 @@ public class Camera {
}
private void applyDirection(WorldRenderHelper helper) {
anchor.getLookingAt(lastAnchorLookingAt);
anchor.getUpVector(lastAnchorUpVector);
float pitch = anchor.getCameraPitch();
float yaw = anchor.getCameraYaw();
lookAt(helper.pushViewTransform());
}
helper.pushViewTransform()
.rotateY(-pitch)
.rotateZ(-yaw);
private void lookAt(Mat4 result) {
Vec3 f = this.lastAnchorLookingAt;
Vec3 s = Vectors.grab3();
Vec3 u = Vectors.grab3();
this.lastAnchorYaw = yaw;
this.lastAnchorPitch = pitch;
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
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)
);
result.mul(workspace);
Matrices.release(workspace);
Vectors.release(s);
Vectors.release(u);
}
private void applyPosition(WorldRenderHelper helper) {
@ -258,6 +247,8 @@ public class Camera {
private void invalidateCache() {
this.lastAnchorPosition.set(Float.NaN);
this.lastAnchorYaw = Float.NaN;
this.lastAnchorPitch = Float.NaN;
this.lastCameraMatrix.set(
Float.NaN,
@ -279,7 +270,7 @@ public class Camera {
);
this.lastAnchorLookingAt.set(Float.NaN);
this.lastAnchorUpVector.set(Float.NaN);
this.lastAnchorUp.set(Float.NaN);
}
public Anchor.Mode getMode() {
@ -298,6 +289,14 @@ public class Camera {
return currentModeIndex;
}
public float getLastAnchorYaw() {
return lastAnchorYaw;
}
public float getLastAnchorPitch() {
return lastAnchorPitch;
}
public Vec3 getLastAnchorPosition() {
return lastAnchorPosition;
}
@ -311,7 +310,7 @@ public class Camera {
}
public Vec3 getLastAnchorUp() {
return lastAnchorUpVector;
return lastAnchorUp;
}
}

View File

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

View File

@ -41,8 +41,6 @@ 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;
@ -59,8 +57,6 @@ public class LayerWorld extends Layer {
super("World");
this.client = client;
this.inputBasedControls = new InputBasedControls(client);
setCursorPolicy(CursorPolicy.FORBID);
}
@Override
@ -76,8 +72,6 @@ public class LayerWorld extends Layer {
@Override
protected void doRender() {
client.getComms().processPackets();
Camera camera = client.getCamera();
if (camera.hasAnchor()) {
renderWorld();
@ -203,25 +197,16 @@ 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;
}
Vec3 gravitationalAcceleration = Vectors.grab3();
gm.getGravity(entity.getPosition(), gravitationalAcceleration);
gravitationalAcceleration.mul(tickLength);
entity.getVelocity().add(gravitationalAcceleration);
Vectors.release(gravitationalAcceleration);
final float gravitationalAcceleration = tmp_testControls.useMinecraftGravity() ? MC_g : IRL_g;
entity.getVelocity().add(0, 0, -gravitationalAcceleration * tickLength);
}
@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 AbsFace surface = null;
private BlockFace surface = null;
private final Vec2 pointOnSurface = new Vec2(0.5f, 0.5f);
private final Vec3 point = new Vec3();
@ -38,9 +38,10 @@ public class Selection {
private BlockRay ray = new BlockRay();
public void update(WorldRender world, EntityData player) {
Vec3 direction = new Vec3();
Vec3 start = new Vec3();
Vec3 direction = player.getLookingAt();
player.getLookingAtVector(direction);
world.getEntityRenderable(player).getViewPoint(start);
start.add(player.getPosition());
@ -70,7 +71,7 @@ public class Selection {
return exists ? point : null;
}
public AbsFace getSurface() {
public BlockFace 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.ShapePart;
import ru.windcorp.progressia.client.graphics.model.Face;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
@ -138,12 +138,12 @@ public class WorldRenderProgram extends ShapeRenderProgram {
public void preprocess(Shape shape) {
super.preprocess(shape);
for (ShapePart face : shape.getParts()) {
for (Face face : shape.getFaces()) {
computeNormals(face);
}
}
private void computeNormals(ShapePart face) {
private void computeNormals(Face face) {
Vec3 a = Vectors.grab3();
Vec3 b = Vectors.grab3();
Vec3 c = Vectors.grab3();
@ -183,7 +183,7 @@ public class WorldRenderProgram extends ShapeRenderProgram {
normal.normalize();
}
private void loadVertexPosition(ShapePart face, int index, Vec3 result) {
private void loadVertexPosition(Face face, int index, Vec3 result) {
ByteBuffer vertices = face.getVertices();
int offset = vertices.position() + index * getBytesPerVertex();
@ -194,7 +194,7 @@ public class WorldRenderProgram extends ShapeRenderProgram {
);
}
private void saveVertexNormal(ShapePart face, int index, Vec3 normal) {
private void saveVertexNormal(Face face, int index, Vec3 normal) {
ByteBuffer vertices = face.getVertices();
int offset = vertices.position() + index * getBytesPerVertex() + (3 * Float.BYTES +
4 * Float.BYTES +

View File

@ -27,29 +27,25 @@ 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.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;
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;
public class ChunkRender
implements ChunkGenericRO<BlockRender, TileRender, TileRenderStack, TileRenderReference, ChunkRender> {
implements GenericChunk<ChunkRender, BlockRender, TileRender, TileRenderStack> {
private final WorldRender world;
private final DefaultChunkData data;
private final ChunkData data;
private final ChunkRenderModel model;
private final Map<TileDataStack, TileRenderStackImpl> tileRenderLists = Collections
.synchronizedMap(new WeakHashMap<>());
public ChunkRender(WorldRender world, DefaultChunkData data) {
public ChunkRender(WorldRender world, ChunkData data) {
this.world = world;
this.data = data;
this.model = new ChunkRenderModel(this);
@ -60,11 +56,6 @@ public class ChunkRender
return getData().getPosition();
}
@Override
public AbsFace getUp() {
return getData().getUp();
}
@Override
public BlockRender getBlock(Vec3i posInChunk) {
return BlockRenderRegistry.getInstance().get(
@ -93,11 +84,11 @@ public class ChunkRender
return world;
}
public DefaultChunkData getData() {
public ChunkData getData() {
return data;
}
public void markForUpdate() {
public synchronized void markForUpdate() {
getWorld().markChunkForUpdate(getPosition());
}
@ -110,28 +101,6 @@ public class ChunkRender
}
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;
@ -150,25 +119,10 @@ public class ChunkRender
}
@Override
public RelFace getFace() {
public BlockFace 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) {
return TileRenderRegistry.getInstance().get(parent.get(index).getId());

View File

@ -35,10 +35,8 @@ 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.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;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.block.BlockFace;
public class ChunkRenderModel implements Renderable {
@ -55,15 +53,11 @@ public class ChunkRenderModel implements Renderable {
public void render(ShapeRenderHelper renderer) {
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);
chunk.getX() * ChunkData.BLOCKS_PER_CHUNK,
chunk.getY() * ChunkData.BLOCKS_PER_CHUNK,
chunk.getZ() * ChunkData.BLOCKS_PER_CHUNK
);
model.render(renderer);
@ -77,8 +71,8 @@ public class ChunkRenderModel implements Renderable {
optimizers.forEach(ChunkRenderOptimizer::startRender);
GenericChunks.forEachBiC(relBlockInChunk -> {
processBlockAndTiles(relBlockInChunk, sink);
chunk.forEachBiC(blockInChunk -> {
processBlockAndTiles(blockInChunk, sink);
});
for (ChunkRenderOptimizer optimizer : optimizers) {
@ -102,16 +96,16 @@ public class ChunkRenderModel implements Renderable {
}
}
private void processBlockAndTiles(Vec3i relBlockInChunk, Builder sink) {
processBlock(relBlockInChunk, sink);
private void processBlockAndTiles(Vec3i blockInChunk, Builder sink) {
processBlock(blockInChunk, sink);
for (RelFace face : RelFace.getFaces()) {
processTileStack(relBlockInChunk, face, sink);
for (BlockFace face : BlockFace.getFaces()) {
processTileStack(blockInChunk, face, sink);
}
}
private void processBlock(Vec3i relBlockInChunk, Builder sink) {
BlockRender block = chunk.getBlockRel(relBlockInChunk);
private void processBlock(Vec3i blockInChunk, Builder sink) {
BlockRender block = chunk.getBlock(blockInChunk);
if (block instanceof BlockRenderNone) {
return;
@ -119,48 +113,48 @@ public class ChunkRenderModel implements Renderable {
if (block.needsOwnRenderable()) {
sink.addPart(
block.createRenderable(chunk.getData(), relBlockInChunk),
new Mat4().identity().translate(relBlockInChunk.x, relBlockInChunk.y, relBlockInChunk.z)
block.createRenderable(chunk.getData(), blockInChunk),
new Mat4().identity().translate(blockInChunk.x, blockInChunk.y, blockInChunk.z)
);
}
processBlockWithCROs(block, relBlockInChunk);
processBlockWithCROs(block, blockInChunk);
}
private void processBlockWithCROs(BlockRender block, Vec3i relBlockInChunk) {
private void processBlockWithCROs(BlockRender block, Vec3i blockInChunk) {
for (ChunkRenderOptimizer optimizer : optimizers) {
optimizer.addBlock(block, relBlockInChunk);
optimizer.addBlock(block, blockInChunk);
}
}
private void processTileStack(Vec3i relBlockInChunk, RelFace face, Builder sink) {
TileRenderStack trs = chunk.getTilesOrNullRel(relBlockInChunk, face);
private void processTileStack(Vec3i blockInChunk, BlockFace face, Builder sink) {
TileRenderStack trs = chunk.getTilesOrNull(blockInChunk, face);
if (trs == null || trs.isEmpty()) {
return;
}
trs.forEach(tile -> processTile(tile, relBlockInChunk, face, sink));
trs.forEach(tile -> processTile(tile, blockInChunk, face, sink));
}
private void processTile(TileRender tile, Vec3i relBlockInChunk, RelFace face, Builder sink) {
private void processTile(TileRender tile, Vec3i blockInChunk, BlockFace face, Builder sink) {
if (tile instanceof TileRenderNone) {
return;
}
if (tile.needsOwnRenderable()) {
sink.addPart(
tile.createRenderable(chunk.getData(), relBlockInChunk, face),
new Mat4().identity().translate(relBlockInChunk.x, relBlockInChunk.y, relBlockInChunk.z)
tile.createRenderable(chunk.getData(), blockInChunk, face),
new Mat4().identity().translate(blockInChunk.x, blockInChunk.y, blockInChunk.z)
);
}
processTileWithCROs(tile, relBlockInChunk, face);
processTileWithCROs(tile, blockInChunk, face);
}
private void processTileWithCROs(TileRender tile, Vec3i relBlockInChunk, RelFace face) {
private void processTileWithCROs(TileRender tile, Vec3i blockInChunk, BlockFace face) {
for (ChunkRenderOptimizer optimizer : optimizers) {
optimizer.addTile(tile, relBlockInChunk, face);
optimizer.addTile(tile, blockInChunk, face);
}
}

View File

@ -21,11 +21,10 @@ 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.DefaultChunkData;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.ChunkDataListener;
import ru.windcorp.progressia.common.world.block.BlockData;
import ru.windcorp.progressia.common.world.rels.AbsFace;
import ru.windcorp.progressia.common.world.rels.RelFace;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.tile.TileData;
class ChunkUpdateListener implements ChunkDataListener {
@ -37,14 +36,14 @@ class ChunkUpdateListener implements ChunkDataListener {
}
@Override
public void onChunkChanged(DefaultChunkData chunk) {
public void onChunkChanged(ChunkData chunk) {
world.getChunk(chunk).markForUpdate();
}
@Override
public void onChunkLoaded(DefaultChunkData chunk) {
public void onChunkLoaded(ChunkData chunk) {
Vec3i cursor = new Vec3i();
for (AbsFace face : AbsFace.getFaces()) {
for (BlockFace face : BlockFace.getFaces()) {
cursor.set(chunk.getX(), chunk.getY(), chunk.getZ());
cursor.add(face.getVector());
world.markChunkForUpdate(cursor);
@ -52,22 +51,22 @@ class ChunkUpdateListener implements ChunkDataListener {
}
@Override
public void onChunkBlockChanged(DefaultChunkData chunk, Vec3i blockInChunk, BlockData previous, BlockData current) {
public void onChunkBlockChanged(ChunkData chunk, Vec3i blockInChunk, BlockData previous, BlockData current) {
onLocationChanged(chunk, blockInChunk);
}
@Override
public void onChunkTilesChanged(
DefaultChunkData chunk,
ChunkData chunk,
Vec3i blockInChunk,
RelFace face,
BlockFace face,
TileData tile,
boolean wasAdded
) {
onLocationChanged(chunk, blockInChunk);
}
private void onLocationChanged(DefaultChunkData chunk, Vec3i blockInChunk) {
private void onLocationChanged(ChunkData chunk, Vec3i blockInChunk) {
Vec3i chunkPos = Vectors.grab3i().set(chunk.getX(), chunk.getY(), chunk.getZ());
checkCoordinate(blockInChunk, chunkPos, VectorUtil.Axis.X);
@ -83,7 +82,7 @@ class ChunkUpdateListener implements ChunkDataListener {
if (block == 0) {
diff = -1;
} else if (block == DefaultChunkData.BLOCKS_PER_CHUNK - 1) {
} else if (block == ChunkData.BLOCKS_PER_CHUNK - 1) {
diff = +1;
} else {
return;

View File

@ -34,58 +34,57 @@ 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.DefaultChunkData;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.ChunkDataListeners;
import ru.windcorp.progressia.common.world.DefaultWorldData;
import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.common.world.WorldDataListener;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.generic.ChunkSet;
import ru.windcorp.progressia.common.world.generic.ChunkSets;
import ru.windcorp.progressia.common.world.generic.WorldGenericRO;
import ru.windcorp.progressia.common.world.generic.GenericWorld;
public class WorldRender
implements WorldGenericRO<BlockRender, TileRender, TileRenderStack, TileRenderReference, ChunkRender, EntityRenderable> {
implements GenericWorld<BlockRender, TileRender, TileRenderStack, ChunkRender, EntityRenderable> {
private final DefaultWorldData data;
private final WorldData data;
private final Client client;
private final Map<DefaultChunkData, ChunkRender> chunks = Collections.synchronizedMap(new HashMap<>());
private final Map<ChunkData, ChunkRender> chunks = Collections.synchronizedMap(new HashMap<>());
private final Map<EntityData, EntityRenderable> entityModels = Collections.synchronizedMap(new WeakHashMap<>());
private final ChunkSet chunksToUpdate = ChunkSets.newSyncHashSet();
public WorldRender(DefaultWorldData data, Client client) {
public WorldRender(WorldData data, Client client) {
this.data = data;
this.client = client;
data.addListener(ChunkDataListeners.createAdder(new ChunkUpdateListener(this)));
data.addListener(new WorldDataListener() {
@Override
public void onChunkLoaded(DefaultWorldData world, DefaultChunkData chunk) {
public void onChunkLoaded(WorldData world, ChunkData chunk) {
addChunk(chunk);
}
@Override
public void beforeChunkUnloaded(DefaultWorldData world, DefaultChunkData chunk) {
public void beforeChunkUnloaded(WorldData world, ChunkData chunk) {
removeChunk(chunk);
}
});
}
protected void addChunk(DefaultChunkData chunk) {
protected void addChunk(ChunkData chunk) {
chunks.put(chunk, new ChunkRender(WorldRender.this, chunk));
markChunkForUpdate(chunk.getPosition());
}
protected void removeChunk(DefaultChunkData chunk) {
protected void removeChunk(ChunkData chunk) {
chunks.remove(chunk);
}
public DefaultWorldData getData() {
public WorldData getData() {
return data;
}
@ -93,7 +92,7 @@ public class WorldRender
return client;
}
public ChunkRender getChunk(DefaultChunkData chunkData) {
public ChunkRender getChunk(ChunkData chunkData) {
return chunks.get(chunkData);
}
@ -112,13 +111,6 @@ public class WorldRender
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();

View File

@ -18,19 +18,26 @@
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.DefaultChunkData;
import ru.windcorp.progressia.common.world.generic.BlockGeneric;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.generic.GenericBlock;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.model.Renderable;
public abstract class BlockRender extends Namespaced implements BlockGeneric {
public abstract class BlockRender extends Namespaced implements GenericBlock {
public BlockRender(String id) {
super(id);
}
public Renderable createRenderable(DefaultChunkData chunk, Vec3i relBlockInChunk) {
public void render(ShapeRenderHelper renderer) {
throw new UnsupportedOperationException(
"BlockRender.render() not implemented in " + this
);
}
public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk) {
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.DefaultChunkData;
import ru.windcorp.progressia.common.world.ChunkData;
public class BlockRenderNone extends BlockRender {
@ -30,7 +30,7 @@ public class BlockRenderNone extends BlockRender {
}
@Override
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk) {
public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk) {
return EmptyModel.getInstance();
}

View File

@ -19,7 +19,7 @@
package ru.windcorp.progressia.client.world.block;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.common.world.rels.RelFace;
import ru.windcorp.progressia.common.world.block.BlockFace;
public class BlockRenderOpaqueCube extends BlockRenderTexturedCube {
@ -29,8 +29,8 @@ public class BlockRenderOpaqueCube extends BlockRenderTexturedCube {
Texture bottomTexture,
Texture northTexture,
Texture southTexture,
Texture westTexture,
Texture eastTexture
Texture eastTexture,
Texture westTexture
) {
super(
id,
@ -38,8 +38,8 @@ public class BlockRenderOpaqueCube extends BlockRenderTexturedCube {
bottomTexture,
northTexture,
southTexture,
westTexture,
eastTexture
eastTexture,
westTexture
);
}
@ -55,20 +55,8 @@ public class BlockRenderOpaqueCube extends BlockRenderTexturedCube {
);
}
public BlockRenderOpaqueCube(String id, Texture topTexture, Texture bottomTexture, Texture sideTexture) {
this(
id,
topTexture,
bottomTexture,
sideTexture,
sideTexture,
sideTexture,
sideTexture
);
}
@Override
public boolean isOpaque(RelFace face) {
public boolean isOpaque(BlockFace face) {
return true;
}

View File

@ -18,8 +18,9 @@
package ru.windcorp.progressia.client.world.block;
import static ru.windcorp.progressia.common.world.rels.AbsFace.*;
import static ru.windcorp.progressia.common.world.block.BlockFace.*;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
@ -28,23 +29,22 @@ 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.ShapePart;
import ru.windcorp.progressia.client.graphics.model.ShapeParts;
import ru.windcorp.progressia.client.graphics.model.Face;
import ru.windcorp.progressia.client.graphics.model.Faces;
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.DefaultChunkData;
import ru.windcorp.progressia.common.world.rels.AbsFace;
import ru.windcorp.progressia.common.world.rels.RelFace;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.block.BlockFace;
public abstract class BlockRenderTexturedCube
extends BlockRender
implements BlockOptimizedSurface {
private final Map<RelFace, Texture> textures;
private final Map<BlockFace, Texture> textures = new HashMap<>();
public BlockRenderTexturedCube(
String id,
@ -52,59 +52,65 @@ public abstract class BlockRenderTexturedCube
Texture bottomTexture,
Texture northTexture,
Texture southTexture,
Texture westTexture,
Texture eastTexture
Texture eastTexture,
Texture westTexture
) {
super(id);
this.textures = RelFace.mapToFaces(topTexture, bottomTexture, northTexture, southTexture, westTexture, eastTexture);
textures.put(TOP, topTexture);
textures.put(BOTTOM, bottomTexture);
textures.put(NORTH, northTexture);
textures.put(SOUTH, southTexture);
textures.put(EAST, eastTexture);
textures.put(WEST, westTexture);
}
public Texture getTexture(RelFace blockFace) {
public Texture getTexture(BlockFace blockFace) {
return textures.get(blockFace);
}
public Vec4 getColorMultiplier(RelFace blockFace) {
public Vec4 getColorMultiplier(BlockFace blockFace) {
return Colors.WHITE;
}
@Override
public final void getShapeParts(
DefaultChunkData chunk, Vec3i blockInChunk, RelFace blockFace,
public final void getFaces(
ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace,
boolean inner,
Consumer<ShapePart> output,
Consumer<Face> output,
Vec3 offset
) {
output.accept(createFace(chunk, blockInChunk, blockFace, inner, offset));
}
private ShapePart createFace(
DefaultChunkData chunk, Vec3i blockInChunk, RelFace blockFace,
private Face createFace(
ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace,
boolean inner,
Vec3 offset
) {
return ShapeParts.createBlockFace(
return Faces.createBlockFace(
WorldRenderProgram.getDefault(),
getTexture(blockFace),
getColorMultiplier(blockFace),
offset,
blockFace.resolve(AbsFace.POS_Z),
blockFace,
inner
);
}
@Override
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk) {
public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk) {
boolean opaque = isBlockOpaque();
ShapePart[] faces = new ShapePart[BLOCK_FACE_COUNT + (opaque ? BLOCK_FACE_COUNT : 0)];
Face[] faces = new Face[BLOCK_FACE_COUNT + (opaque ? BLOCK_FACE_COUNT : 0)];
for (int i = 0; i < BLOCK_FACE_COUNT; ++i) {
faces[i] = createFace(chunk, blockInChunk, RelFace.getFaces().get(i), false, Vectors.ZERO_3);
faces[i] = createFace(chunk, blockInChunk, BlockFace.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, RelFace.getFaces().get(i), true, Vectors.ZERO_3);
faces[i + BLOCK_FACE_COUNT] = createFace(chunk, blockInChunk, BlockFace.getFaces().get(i), true, Vectors.ZERO_3);
}
}

View File

@ -19,7 +19,7 @@
package ru.windcorp.progressia.client.world.block;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.common.world.rels.RelFace;
import ru.windcorp.progressia.common.world.block.BlockFace;
public class BlockRenderTransparentCube extends BlockRenderTexturedCube {
@ -29,8 +29,8 @@ public class BlockRenderTransparentCube extends BlockRenderTexturedCube {
Texture bottomTexture,
Texture northTexture,
Texture southTexture,
Texture westTexture,
Texture eastTexture
Texture eastTexture,
Texture westTexture
) {
super(
id,
@ -38,8 +38,8 @@ public class BlockRenderTransparentCube extends BlockRenderTexturedCube {
bottomTexture,
northTexture,
southTexture,
westTexture,
eastTexture
eastTexture,
westTexture
);
}
@ -55,20 +55,8 @@ public class BlockRenderTransparentCube extends BlockRenderTexturedCube {
);
}
public BlockRenderTransparentCube(String id, Texture topTexture, Texture bottomTexture, Texture sideTexture) {
this(
id,
topTexture,
bottomTexture,
sideTexture,
sideTexture,
sideTexture,
sideTexture
);
}
@Override
public boolean isOpaque(RelFace face) {
public boolean isOpaque(BlockFace 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.rels.RelFace;
import ru.windcorp.progressia.common.world.block.BlockFace;
/**
* Chunk render optimizer (CRO) is an object that produces optimized models for
@ -35,12 +35,6 @@ import ru.windcorp.progressia.common.world.rels.RelFace;
* 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.
* <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
@ -50,7 +44,7 @@ import ru.windcorp.progressia.common.world.rels.RelFace;
* instance.</li>
* <li>{@link #startRender()} is invoked. The CRO must reset its state.</li>
* <li>{@link #addBlock(BlockRender, Vec3i)} and
* {@link #addTile(TileRender, Vec3i, RelFace)} are invoked for each block and
* {@link #addTile(TileRender, Vec3i, BlockFace)} 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
@ -104,13 +98,12 @@ 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.
* @param block a {@link BlockRender} instance describing the block.
* It corresponds to
* {@code getChunk().getBlock(blockInChunk)}.
* @param relBlockInChunk the relative position of the block
* @param blockInChunk the position of the block
*/
public abstract void addBlock(BlockRender block, Vec3i relBlockInChunk);
public abstract void addBlock(BlockRender block, Vec3i blockInChunk);
/**
* Requests that this CRO processes the provided tile. This method may only
@ -120,11 +113,10 @@ public abstract class ChunkRenderOptimizer extends Namespaced {
* this method is invoked for lower tiles first.
*
* @param tile a {@link BlockRender} instance describing the tile
* @param relBlockInChunk the relative position of the block that the tile
* belongs to
* @param blockInChunk the 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 relBlockInChunk, RelFace blockFace);
public abstract void addTile(TileRender tile, Vec3i blockInChunk, BlockFace blockFace);
/**
* Requests that the CRO assembles and outputs its model. This method may

View File

@ -1,99 +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.client.world.cro;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Consumer;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.backend.Usage;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.model.ShapePart;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.world.block.BlockRender;
import ru.windcorp.progressia.client.world.tile.TileRender;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.rels.RelFace;
public class ChunkRenderOptimizerSimple extends ChunkRenderOptimizer {
public interface BlockOptimizedSimple {
void getShapeParts(
DefaultChunkData chunk,
Vec3i relBlockInChunk,
Consumer<ShapePart> output
);
}
public interface TileOptimizedCustom {
void getShapeParts(
DefaultChunkData chunk,
Vec3i relBlockInChunk,
RelFace blockFace,
Consumer<ShapePart> output
);
}
private final Collection<ShapePart> parts = new ArrayList<>();
private final Consumer<ShapePart> partAdder = parts::add;
public ChunkRenderOptimizerSimple(String id) {
super(id);
}
@Override
public void startRender() {
parts.clear();
}
@Override
public void addBlock(BlockRender block, Vec3i relBlockInChunk) {
if (block instanceof BlockOptimizedSimple) {
((BlockOptimizedSimple) block).getShapeParts(chunk.getData(), relBlockInChunk, partAdder);
}
}
@Override
public void addTile(TileRender tile, Vec3i relBlockInChunk, RelFace blockFace) {
if (tile instanceof TileOptimizedCustom) {
((TileOptimizedCustom) tile).getShapeParts(chunk.getData(), relBlockInChunk, blockFace, partAdder);
}
}
@Override
public Renderable endRender() {
if (parts.isEmpty()) {
return null;
}
return new Shape(
Usage.STATIC,
WorldRenderProgram.getDefault(),
parts.toArray(new ShapePart[parts.size()])
);
}
}

View File

@ -18,9 +18,9 @@
package ru.windcorp.progressia.client.world.cro;
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 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 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.ShapePart;
import ru.windcorp.progressia.client.graphics.model.Face;
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,9 +37,8 @@ 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.DefaultChunkData;
import ru.windcorp.progressia.common.world.generic.GenericChunks;
import ru.windcorp.progressia.common.world.rels.RelFace;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.block.BlockFace;
public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
@ -53,42 +52,39 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
private static interface OptimizedSurface {
/**
* 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.
* Creates and outputs a set of faces 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 relBlockInChunk the relative block in chunk
* @param blockInChunk the block in chunk
* @param blockFace the requested face
* @param inner whether this face should be visible from
* inside
* @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
* @param output a consumer that the created faces must be given
* to
* @param offset an additional offset that must be applied to all
* vertices
*/
void getShapeParts(
DefaultChunkData chunk,
Vec3i relBlockInChunk,
RelFace blockFace,
void getFaces(
ChunkData chunk,
Vec3i blockInChunk,
BlockFace blockFace,
boolean inner,
Consumer<ShapePart> output,
Consumer<Face> output,
Vec3 offset /* kostyl 156% */
);
/**
* Returns the opacity of the surface identified by the provided
* {@link RelFace}.
* {@link BlockFace}.
* Opaque surfaces prevent surfaces behind them from being included in
* chunk models.
*
* @param blockFace the face to query
* @return {@code true} iff the surface is opaque.
*/
boolean isOpaque(RelFace blockFace);
boolean isOpaque(BlockFace blockFace);
}
/**
@ -163,29 +159,29 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
}
@Override
public void addBlock(BlockRender block, Vec3i relBlockInChunk) {
public void addBlock(BlockRender block, Vec3i pos) {
if (!(block instanceof BlockOptimizedSurface))
return;
BlockOptimizedSurface bos = (BlockOptimizedSurface) block;
addBlock(relBlockInChunk, bos);
addBlock(pos, bos);
}
@Override
public void addTile(TileRender tile, Vec3i relBlockInChunk, RelFace face) {
public void addTile(TileRender tile, Vec3i pos, BlockFace face) {
if (!(tile instanceof TileOptimizedSurface))
return;
TileOptimizedSurface tos = (TileOptimizedSurface) tile;
addTile(relBlockInChunk, face, tos);
addTile(pos, face, tos);
}
private void addBlock(Vec3i relBlockInChunk, BlockOptimizedSurface block) {
getBlock(relBlockInChunk).block = block;
protected void addBlock(Vec3i pos, BlockOptimizedSurface block) {
getBlock(pos).block = block;
}
private void addTile(Vec3i relBlockInChunk, RelFace face, TileOptimizedSurface tile) {
FaceInfo faceInfo = getFace(relBlockInChunk, face);
private void addTile(Vec3i pos, BlockFace face, TileOptimizedSurface tile) {
FaceInfo faceInfo = getFace(pos, face);
int index = faceInfo.tileCount;
faceInfo.tileCount++;
@ -201,58 +197,63 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
}
}
protected BlockInfo getBlock(Vec3i relBlockInChunk) {
return data[relBlockInChunk.x][relBlockInChunk.y][relBlockInChunk.z];
protected BlockInfo getBlock(Vec3i cursor) {
return data[cursor.x][cursor.y][cursor.z];
}
protected FaceInfo getFace(Vec3i relBlockInChunk, RelFace face) {
return getBlock(relBlockInChunk).faces[face.getId()];
protected FaceInfo getFace(Vec3i cursor, BlockFace face) {
return getBlock(cursor).faces[face.getId()];
}
@Override
public Renderable endRender() {
Collection<ShapePart> shapeParts = new ArrayList<>(
Collection<Face> shapeFaces = new ArrayList<>(
BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * 3
);
Consumer<ShapePart> consumer = shapeParts::add;
Vec3i cursor = new Vec3i();
Consumer<Face> consumer = shapeFaces::add;
GenericChunks.forEachBiC(relBlockInChunk -> {
processInnerFaces(relBlockInChunk, consumer);
processOuterFaces(relBlockInChunk, consumer);
});
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);
}
}
}
if (shapeParts.isEmpty()) {
if (shapeFaces.isEmpty()) {
return null;
}
return new Shape(
Usage.STATIC,
WorldRenderProgram.getDefault(),
shapeParts.toArray(new ShapePart[shapeParts.size()])
shapeFaces.toArray(new Face[shapeFaces.size()])
);
}
private void processOuterFaces(
Vec3i relBlockInChunk,
Consumer<ShapePart> output
Vec3i blockInChunk,
Consumer<Face> output
) {
for (RelFace blockFace : RelFace.getFaces()) {
processOuterFace(relBlockInChunk, blockFace, output);
for (BlockFace blockFace : BlockFace.getFaces()) {
processOuterFace(blockInChunk, blockFace, output);
}
}
private void processOuterFace(Vec3i relBlockInChunk, RelFace blockFace, Consumer<ShapePart> output) {
if (!shouldRenderOuterFace(relBlockInChunk, blockFace))
private void processOuterFace(Vec3i blockInChunk, BlockFace blockFace, Consumer<Face> output) {
if (!shouldRenderOuterFace(blockInChunk, blockFace))
return;
FaceInfo info = getFace(relBlockInChunk, blockFace);
FaceInfo info = getFace(blockInChunk, blockFace);
if (info.tileCount == 0 && info.block.block == null)
return;
Vec3 faceOrigin = new Vec3(relBlockInChunk.x, relBlockInChunk.y, relBlockInChunk.z);
Vec3 offset = new Vec3(blockFace.getRelFloatVector()).mul(OVERLAY_OFFSET);
Vec3 faceOrigin = new Vec3(blockInChunk.x, blockInChunk.y, blockInChunk.z);
Vec3 offset = new Vec3(blockFace.getFloatVector()).mul(OVERLAY_OFFSET);
for (
int layer = info.topOpaqueSurface;
@ -263,29 +264,32 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
if (surface == null)
continue; // layer may be BLOCK_LAYER, then block may be null
surface.getShapeParts(chunk.getData(), relBlockInChunk, blockFace, false, output, faceOrigin);
surface.getFaces(chunk.getData(), blockInChunk, blockFace, false, output, faceOrigin);
faceOrigin.add(offset);
}
}
private void processInnerFaces(Vec3i relBlockInChunk, Consumer<ShapePart> output) {
for (RelFace blockFace : RelFace.getFaces()) {
processInnerFace(relBlockInChunk, blockFace, output);
private void processInnerFaces(
Vec3i blockInChunk,
Consumer<Face> output
) {
for (BlockFace blockFace : BlockFace.getFaces()) {
processInnerFace(blockInChunk, blockFace, output);
}
}
private void processInnerFace(Vec3i relBlockInChunk, RelFace blockFace, Consumer<ShapePart> output) {
if (!shouldRenderInnerFace(relBlockInChunk, blockFace))
private void processInnerFace(Vec3i blockInChunk, BlockFace blockFace, Consumer<Face> output) {
if (!shouldRenderInnerFace(blockInChunk, blockFace))
return;
FaceInfo info = getFace(relBlockInChunk, blockFace);
FaceInfo info = getFace(blockInChunk, blockFace);
if (info.tileCount == 0 && info.block.block == null)
return;
Vec3 faceOrigin = new Vec3(relBlockInChunk.x, relBlockInChunk.y, relBlockInChunk.z);
Vec3 offset = new Vec3(blockFace.getRelFloatVector()).mul(OVERLAY_OFFSET);
Vec3 faceOrigin = new Vec3(blockInChunk.x, blockInChunk.y, blockInChunk.z);
Vec3 offset = new Vec3(blockFace.getFloatVector()).mul(OVERLAY_OFFSET);
for (
int layer = FaceInfo.BLOCK_LAYER;
@ -296,35 +300,35 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
if (surface == null)
continue; // layer may be BLOCK_LAYER, then block may be null
surface.getShapeParts(chunk.getData(), relBlockInChunk, blockFace, true, output, faceOrigin);
surface.getFaces(chunk.getData(), blockInChunk, blockFace, true, output, faceOrigin);
faceOrigin.add(offset);
}
}
private boolean shouldRenderOuterFace(Vec3i relBlockInChunk, RelFace face) {
relBlockInChunk.add(face.getRelVector());
private boolean shouldRenderOuterFace(Vec3i blockInChunk, BlockFace face) {
blockInChunk.add(face.getVector());
try {
return shouldRenderWhenFacing(relBlockInChunk, face);
return shouldRenderWhenFacing(blockInChunk, face);
} finally {
relBlockInChunk.sub(face.getRelVector());
blockInChunk.sub(face.getVector());
}
}
private boolean shouldRenderInnerFace(Vec3i relBlockInChunk, RelFace face) {
return shouldRenderWhenFacing(relBlockInChunk, face);
private boolean shouldRenderInnerFace(Vec3i blockInChunk, BlockFace face) {
return shouldRenderWhenFacing(blockInChunk, face);
}
private boolean shouldRenderWhenFacing(Vec3i relBlockInChunk, RelFace face) {
if (GenericChunks.containsBiC(relBlockInChunk)) {
return shouldRenderWhenFacingLocal(relBlockInChunk, face);
private boolean shouldRenderWhenFacing(Vec3i blockInChunk, BlockFace face) {
if (chunk.containsBiC(blockInChunk)) {
return shouldRenderWhenFacingLocal(blockInChunk, face);
} else {
return shouldRenderWhenFacingNeighbor(relBlockInChunk, face);
return shouldRenderWhenFacingNeighbor(blockInChunk, face);
}
}
private boolean shouldRenderWhenFacingLocal(Vec3i relBlockInChunk, RelFace face) {
BlockOptimizedSurface block = getBlock(relBlockInChunk).block;
private boolean shouldRenderWhenFacingLocal(Vec3i blockInChunk, BlockFace face) {
BlockOptimizedSurface block = getBlock(blockInChunk).block;
if (block == null) {
return true;
@ -336,37 +340,36 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
return true;
}
private boolean shouldRenderWhenFacingNeighbor(Vec3i relBlockInLocalChunk, RelFace face) {
Vec3i blockInChunk = Vectors.grab3i();
chunk.resolve(relBlockInLocalChunk, blockInChunk);
private boolean shouldRenderWhenFacingNeighbor(Vec3i blockInLocalChunk, BlockFace face) {
Vec3i blockInChunk = Vectors.grab3i().set(blockInLocalChunk.x, blockInLocalChunk.y, blockInLocalChunk.z);
Vec3i chunkPos = Vectors.grab3i().set(chunk.getX(), chunk.getY(), chunk.getZ());
try {
// Determine blockInChunk and chunkPos
if (blockInChunk.x == -1) {
if (blockInLocalChunk.x == -1) {
blockInChunk.x = BLOCKS_PER_CHUNK - 1;
chunkPos.x -= 1;
} else if (blockInChunk.x == BLOCKS_PER_CHUNK) {
} else if (blockInLocalChunk.x == BLOCKS_PER_CHUNK) {
blockInChunk.x = 0;
chunkPos.x += 1;
} else if (blockInChunk.y == -1) {
} else if (blockInLocalChunk.y == -1) {
blockInChunk.y = BLOCKS_PER_CHUNK - 1;
chunkPos.y -= 1;
} else if (blockInChunk.y == BLOCKS_PER_CHUNK) {
} else if (blockInLocalChunk.y == BLOCKS_PER_CHUNK) {
blockInChunk.y = 0;
chunkPos.y += 1;
} else if (blockInChunk.z == -1) {
} else if (blockInLocalChunk.z == -1) {
blockInChunk.z = BLOCKS_PER_CHUNK - 1;
chunkPos.z -= 1;
} else if (blockInChunk.z == BLOCKS_PER_CHUNK) {
} else if (blockInLocalChunk.z == BLOCKS_PER_CHUNK) {
blockInChunk.z = 0;
chunkPos.z += 1;
} else {
throw new AssertionError(
"Requested incorrent neighbor ("
+ relBlockInLocalChunk.x + "; "
+ relBlockInLocalChunk.y + "; "
+ relBlockInLocalChunk.z + ")"
+ blockInLocalChunk.x + "; "
+ blockInLocalChunk.y + "; "
+ blockInLocalChunk.z + ")"
);
}
@ -379,11 +382,8 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
return true;
BlockOptimizedSurface bos = (BlockOptimizedSurface) block;
RelFace rotatedFace = face.rotate(this.chunk.getUp(), chunk.getUp());
if (!bos.isOpaque(rotatedFace)) {
if (!bos.isOpaque(face))
return true;
}
return false;

View File

@ -19,45 +19,18 @@
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.EntityGeneric;
import ru.windcorp.progressia.common.world.generic.GenericEntity;
public abstract class EntityRenderable implements Renderable, EntityGeneric {
public abstract class EntityRenderable implements Renderable, GenericEntity {
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;
}
@ -72,41 +45,7 @@ public abstract class EntityRenderable implements Renderable, EntityGeneric {
return getData().getId();
}
@Override
public long getEntityId() {
return getData().getEntityId();
}
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) {
public void getViewPoint(Vec3 output) {
output.set(0, 0, 0);
}

View File

@ -43,7 +43,6 @@ 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

@ -18,8 +18,11 @@
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;
@ -29,9 +32,6 @@ 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 {
@ -51,30 +51,29 @@ public abstract class NPedModel extends EntityRenderable {
ShapeRenderHelper renderer,
NPedModel model
) {
renderer.pushTransform().translate(translation);
applyTransform(renderer.pushTransform(), model);
renderable.render(renderer);
renderer.popTransform();
renderer.popTransform();
}
protected void applyTransform(Mat4 mat, NPedModel model) {
mat.translate(getTranslation());
}
protected abstract void applyTransform(Mat4 mat, NPedModel model);
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 {
@ -98,8 +97,7 @@ public abstract class NPedModel extends EntityRenderable {
@Override
protected void applyTransform(Mat4 mat, NPedModel model) {
super.applyTransform(mat, model);
mat.rotateZ(-model.getHeadYaw()).rotateY(-model.getHeadPitch());
mat.rotateZ(model.getHeadYaw()).rotateY(model.getHeadPitch());
}
public Vec3 getViewPoint() {
@ -107,8 +105,6 @@ public abstract class NPedModel extends EntityRenderable {
}
}
public static boolean flag;
protected final Body body;
protected final Head head;
@ -132,9 +128,7 @@ public abstract class NPedModel extends EntityRenderable {
private float walkingFrequency;
private final Vec3 bodyLookingAt = new Vec3().set(0);
private final Mat4 bodyTransform = new Mat4();
private float bodyYaw = Float.NaN;
private float headYaw;
private float headPitch;
@ -144,14 +138,17 @@ public abstract class NPedModel extends EntityRenderable {
this.head = head;
this.scale = scale;
computeRotations();
evaluateAngles();
}
@Override
protected void doRender(ShapeRenderHelper renderer) {
renderer.pushTransform().scale(scale).mul(bodyTransform);
public void render(ShapeRenderHelper renderer) {
renderer.pushTransform().scale(scale).rotateZ(bodyYaw);
renderBodyParts(renderer);
renderer.popTransform();
accountForVelocity();
evaluateAngles();
}
protected void renderBodyParts(ShapeRenderHelper renderer) {
@ -159,106 +156,50 @@ public abstract class NPedModel extends EntityRenderable {
head.render(renderer, this);
}
@Override
protected void update() {
advanceTime();
computeRotations();
}
private void evaluateAngles() {
float globalYaw = normalizeAngle(getData().getYaw());
private void computeRotations() {
if (!bodyLookingAt.any()) {
getData().getForwardVector(bodyLookingAt);
if (Float.isNaN(bodyYaw)) {
bodyYaw = globalYaw;
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;
headYaw = normalizeAngle(globalYaw - bodyYaw);
if (headYaw > +head.maxYaw) {
bodyYawChange = headYaw - +head.maxYaw;
bodyYaw += headYaw - +head.maxYaw;
headYaw = +head.maxYaw;
} else if (headYaw < -head.maxYaw) {
bodyYawChange = headYaw - -head.maxYaw;
bodyYaw += 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();
bodyYaw = normalizeAngle(bodyYaw);
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
headPitch = Glm.clamp(
getData().getPitch(),
-head.maxPitch,
head.maxPitch
);
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());
private void accountForVelocity() {
Vec3 horizontal = new Vec3(getData().getVelocity());
horizontal.z = 0;
velocity = horizontal.length();
computeVelocityParameter();
evaluateVelocityCoeff();
// TODO switch to world time
walkingParameter += velocity * GraphicsInterface.getFrameLength() * 1000;
rotateBodyWithMovement(horizontal);
bodyYaw += velocityParameter * normalizeAngle(
(float) (atan2(horizontal.y, horizontal.x) - bodyYaw)
) * min(1, GraphicsInterface.getFrameLength() * 10);
}
private void computeVelocityParameter() {
private void evaluateVelocityCoeff() {
if (velocity > maxEffectiveVelocity) {
velocityParameter = 1;
} else {
@ -266,39 +207,17 @@ 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
protected void doGetViewPoint(Vec3 output) {
public void getViewPoint(Vec3 output) {
Mat4 m = new Mat4();
Vec4 v = new Vec4();
m.identity()
.scale(scale)
.mul(bodyTransform);
head.getTransform(m, this);
.rotateZ(bodyYaw)
.translate(head.getTranslation())
.rotateZ(headYaw)
.rotateY(headPitch);
v.set(head.getViewPoint(), 1);
m.mul(v);
@ -314,8 +233,8 @@ public abstract class NPedModel extends EntityRenderable {
return head;
}
public Vec3 getBodyLookingAt() {
return bodyLookingAt;
public float getBodyYaw() {
return bodyYaw;
}
public float getHeadYaw() {

View File

@ -44,7 +44,6 @@ 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

@ -18,21 +18,28 @@
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.DefaultChunkData;
import ru.windcorp.progressia.common.world.generic.TileGeneric;
import ru.windcorp.progressia.common.world.rels.RelFace;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.generic.GenericTile;
public class TileRender extends Namespaced implements TileGeneric {
public class TileRender extends Namespaced implements GenericTile {
public TileRender(String id) {
super(id);
}
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk, RelFace face) {
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) {
return null;
}

View File

@ -1,170 +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.client.world.tile;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Consumer;
import glm.mat._3.Mat3;
import glm.mat._4.Mat4;
import glm.vec._3.Vec3;
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.Renderable;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.model.ShapePart;
import ru.windcorp.progressia.client.graphics.model.ShapeParts;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSimple.TileOptimizedCustom;
import ru.windcorp.progressia.common.util.Matrices;
import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.rels.AbsFace;
import ru.windcorp.progressia.common.world.rels.AxisRotations;
import ru.windcorp.progressia.common.world.rels.RelFace;
public class TileRenderCross extends TileRender implements TileOptimizedCustom {
private static final float SQRT_2_OVER_2 = (float) Math.sqrt(2) / 2;
private static final float[] ONE_AND_NEGATIVE_ONE = new float[] { 1, -1 };
private final Texture texture;
private final float width;
public TileRenderCross(String id, Texture texture, boolean allowStretching) {
super(id);
this.texture = texture;
this.width = allowStretching ? 1 : SQRT_2_OVER_2;
}
public Texture getTexture(RelFace blockFace) {
return texture;
}
public Vec4 getColorMultiplier(RelFace blockFace) {
return Colors.WHITE;
}
@Override
public void getShapeParts(
DefaultChunkData chunk,
Vec3i bic,
RelFace blockFace,
Consumer<ShapePart> output
) {
Mat4 transform = Matrices.grab4();
Vec3 origin = Vectors.grab3();
Vec3 width = Vectors.grab3();
Vec3 height = Vectors.grab3();
Mat3 resolutionMatrix = AxisRotations.getResolutionMatrix3(blockFace.resolve(AbsFace.POS_Z));
Vec4 color = getColorMultiplier(blockFace);
Texture texture = getTexture(blockFace);
float originOffset = (1 - this.width) / 2;
WorldRenderProgram program = WorldRenderProgram.getDefault();
for (int i = 0; getTransform(chunk, bic, blockFace, i, transform); i++) {
for (float flip : ONE_AND_NEGATIVE_ONE) {
origin.set(flip * (originOffset - 0.5f), originOffset - 0.5f, 0);
width.set(flip * this.width, this.width, 0);
height.set(0, 0, 1);
VectorUtil.applyMat4(origin, transform);
VectorUtil.rotateOnly(width, transform);
VectorUtil.rotateOnly(height, transform);
origin.z += 1 - 0.5f;
if (blockFace != RelFace.UP) {
resolutionMatrix.mul(origin);
resolutionMatrix.mul(width);
resolutionMatrix.mul(height);
}
origin.add(bic.x, bic.y, bic.z);
output.accept(
ShapeParts.createRectangle(
program,
texture,
color,
origin,
width,
height,
false
)
);
output.accept(
ShapeParts.createRectangle(
program,
texture,
color,
origin,
width,
height,
true
)
);
}
}
Matrices.release(transform);
Vectors.release(origin);
Vectors.release(width);
Vectors.release(height);
}
protected boolean getTransform(
DefaultChunkData chunk,
Vec3i relBlockInChunk,
RelFace blockFace,
int count,
Mat4 output
) {
output.identity();
return count == 0;
}
@Override
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk, RelFace blockFace) {
Collection<ShapePart> parts = new ArrayList<>(4);
getShapeParts(chunk, blockInChunk, blockFace, parts::add);
return new Shape(
Usage.STATIC,
WorldRenderProgram.getDefault(),
parts.toArray(new ShapePart[parts.size()])
);
}
@Override
public boolean needsOwnRenderable() {
return false;
}
}

View File

@ -16,38 +16,34 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test;
package ru.windcorp.progressia.client.world.tile;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.world.tile.TileRenderSurface;
import ru.windcorp.progressia.common.world.rels.RelFace;
import ru.windcorp.progressia.common.world.block.BlockFace;
public class TestTileRenderGrass extends TileRenderSurface {
public class TileRenderGrass extends TileRenderSurface {
private final Texture topTexture;
private final Texture sideTexture;
private final boolean isOpaque;
public TestTileRenderGrass(
public TileRenderGrass(
String id,
Texture top,
Texture side,
boolean isOpaque
Texture side
) {
super(id);
this.topTexture = top;
this.sideTexture = side;
this.isOpaque = isOpaque;
}
@Override
public Texture getTexture(RelFace face) {
return (face == RelFace.UP) ? topTexture : sideTexture;
public Texture getTexture(BlockFace face) {
return (face == BlockFace.TOP) ? topTexture : sideTexture;
}
@Override
public boolean isOpaque(RelFace face) {
return isOpaque && face == RelFace.UP;
public boolean isOpaque(BlockFace face) {
return face == BlockFace.TOP;
}
}

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.DefaultChunkData;
import ru.windcorp.progressia.common.world.rels.RelFace;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.block.BlockFace;
public class TileRenderNone extends TileRender {
@ -30,7 +30,7 @@ public class TileRenderNone extends TileRender {
}
@Override
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk, RelFace face) {
public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk, BlockFace 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.rels.RelFace;
import ru.windcorp.progressia.common.world.block.BlockFace;
public class TileRenderOpaqueSurface extends TileRenderSurface {
@ -28,7 +28,7 @@ public class TileRenderOpaqueSurface extends TileRenderSurface {
}
@Override
public boolean isOpaque(RelFace face) {
public boolean isOpaque(BlockFace face) {
return true;
}

View File

@ -1,27 +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.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

@ -18,15 +18,12 @@
package ru.windcorp.progressia.client.world.tile;
import java.util.AbstractList;
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;
import ru.windcorp.progressia.common.world.generic.GenericTileStack;
import ru.windcorp.progressia.common.world.tile.TileDataStack;
public abstract class TileRenderStack
extends AbstractList<TileRender>
implements TileGenericStackRO<BlockRender, TileRender, TileRenderStack, TileRenderReference, ChunkRender> {
extends GenericTileStack<TileRenderStack, TileRender, ChunkRender> {
public abstract TileDataStack getData();

View File

@ -25,17 +25,16 @@ 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.ShapePart;
import ru.windcorp.progressia.client.graphics.model.ShapeParts;
import ru.windcorp.progressia.client.graphics.model.Face;
import ru.windcorp.progressia.client.graphics.model.Faces;
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.DefaultChunkData;
import ru.windcorp.progressia.common.world.rels.AbsFace;
import ru.windcorp.progressia.common.world.rels.RelFace;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.block.BlockFace;
public abstract class TileRenderSurface extends TileRender implements TileOptimizedSurface {
@ -50,41 +49,41 @@ public abstract class TileRenderSurface extends TileRender implements TileOptimi
this(id, null);
}
public Texture getTexture(RelFace blockFace) {
public Texture getTexture(BlockFace blockFace) {
return texture;
}
public Vec4 getColorMultiplier(RelFace blockFace) {
public Vec4 getColorMultiplier(BlockFace blockFace) {
return Colors.WHITE;
}
@Override
public final void getShapeParts(
DefaultChunkData chunk, Vec3i relBlockInChunk, RelFace blockFace,
public final void getFaces(
ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace,
boolean inner,
Consumer<ShapePart> output,
Consumer<Face> output,
Vec3 offset
) {
output.accept(createFace(chunk, relBlockInChunk, blockFace, inner, offset));
output.accept(createFace(chunk, blockInChunk, blockFace, inner, offset));
}
private ShapePart createFace(
DefaultChunkData chunk, Vec3i blockInChunk, RelFace blockFace,
private Face createFace(
ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace,
boolean inner,
Vec3 offset
) {
return ShapeParts.createBlockFace(
return Faces.createBlockFace(
WorldRenderProgram.getDefault(),
getTexture(blockFace),
getColorMultiplier(blockFace),
offset,
blockFace.resolve(AbsFace.POS_Z),
blockFace,
inner
);
}
@Override
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk, RelFace blockFace) {
public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace) {
return new Shape(
Usage.STATIC,
WorldRenderProgram.getDefault(),

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.rels.RelFace;
import ru.windcorp.progressia.common.world.block.BlockFace;
public class TileRenderTransparentSurface extends TileRenderSurface {
@ -28,7 +28,7 @@ public class TileRenderTransparentSurface extends TileRenderSurface {
}
@Override
public boolean isOpaque(RelFace face) {
public boolean isOpaque(BlockFace face) {
return false;
}

View File

@ -1,134 +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.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.rels.AbsFace;
import ru.windcorp.progressia.common.world.block.BlockFace;
public interface AABBoid extends CollisionModel {
@ -27,7 +27,7 @@ public interface AABBoid extends CollisionModel {
void getSize(Vec3 output);
default Wall getWall(AbsFace face) {
default Wall getWall(BlockFace 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.rels.AbsFace;
import ru.windcorp.progressia.common.world.block.BlockFace;
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[AbsFace.BLOCK_FACE_COUNT];
private final TranslatedAABBWall[] walls = new TranslatedAABBWall[BlockFace.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.DefaultWorldData;
import ru.windcorp.progressia.common.world.WorldData;
public class WorldCollisionHelper {
@ -79,7 +79,7 @@ public class WorldCollisionHelper {
* checked against
* @param maxTime maximum collision time
*/
public void tuneToCollideable(DefaultWorldData world, Collideable collideable, float maxTime) {
public void tuneToCollideable(WorldData world, Collideable collideable, float maxTime) {
activeBlockModels.forEach(blockModelCache::release);
activeBlockModels.clear();
CollisionPathComputer.forEveryBlockInCollisionPath(

View File

@ -25,7 +25,7 @@ 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.rels.AbsFace;
import ru.windcorp.progressia.common.world.block.BlockFace;
class AABBoidCollider {
@ -50,7 +50,7 @@ class AABBoidCollider {
computeCollisionVelocity(collisionVelocity, obstacleBody, colliderBody);
// For every wall of collision space
for (int i = 0; i < AbsFace.BLOCK_FACE_COUNT; ++i) {
for (int i = 0; i < BlockFace.BLOCK_FACE_COUNT; ++i) {
Wall wall = originCollisionSpace.getWall(i);
Collision collision = computeWallCollision(
@ -122,51 +122,46 @@ 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.
*
* 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 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).
*
* 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 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.
*/
// @formatter:on
private static Collision computeWallCollision(
Wall obstacleWall,
AABBoid colliderModel,

View File

@ -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.DefaultWorldData;
import ru.windcorp.progressia.common.world.WorldData;
public class Collider {
@ -36,7 +36,7 @@ public class Collider {
/**
* Dear Princess Celestia,
* <p>
* When {@linkplain #advanceTime(Collection, Collision, DefaultWorldData, float)
* When {@linkplain #advanceTime(Collection, Collision, WorldData, float)
* advancing time},
* time step for all entities <em>except</em> currently colliding bodies is
* the current
@ -61,7 +61,7 @@ public class Collider {
public static void performCollisions(
List<? extends Collideable> colls,
DefaultWorldData world,
WorldData world,
float tickLength,
ColliderWorkspace workspace
) {
@ -96,7 +96,7 @@ public class Collider {
private static Collision getFirstCollision(
List<? extends Collideable> colls,
float tickLength,
DefaultWorldData world,
WorldData world,
ColliderWorkspace workspace
) {
Collision result = null;
@ -126,7 +126,7 @@ public class Collider {
private static void tuneWorldCollisionHelper(
Collideable coll,
float tickLength,
DefaultWorldData world,
WorldData world,
ColliderWorkspace workspace
) {
WorldCollisionHelper wch = workspace.worldCollisionHelper;
@ -194,7 +194,7 @@ public class Collider {
Collision collision,
Collection<? extends Collideable> colls,
DefaultWorldData world,
WorldData world,
float tickLength,
ColliderWorkspace workspace
) {
@ -212,10 +212,8 @@ 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
@ -224,13 +222,12 @@ public class Collider {
* 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.
*
* 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
* Two spherical (sic) bodies, a and b, experience a perfectly inelastic
* collision
* along a unit vector
* _ _ _ _ _
* n = (w h) / (|w h|),
@ -241,43 +238,40 @@ public class Collider {
* ___ ___
* After the collision desired velocities are u_a and u_b, respectively.
* _
* (Notation convention: suffix 'n' denotes a vector projection onto vector n,
* (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:
* 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;
* 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}.
* 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();
@ -361,7 +355,7 @@ public class Collider {
private static void advanceTime(
Collection<? extends Collideable> colls,
Collision exceptions,
DefaultWorldData world,
WorldData world,
float step
) {
world.advanceTime(step);

View File

@ -22,7 +22,6 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import ru.windcorp.progressia.common.comms.packets.Packet;
@ -55,8 +54,6 @@ public abstract class CommsChannel {
private final Collection<CommsListener> listeners = Collections.synchronizedCollection(new ArrayList<>());
private final List<Packet> pendingPackets = Collections.synchronizedList(new ArrayList<>());
protected abstract void doSendPacket(Packet packet) throws IOException;
private synchronized void sendPacket(
@ -104,20 +101,9 @@ public abstract class CommsChannel {
public abstract void disconnect();
protected void onPacketReceived(Packet packet) {
pendingPackets.add(packet);
}
protected void forwardPacketToListeners(Packet packet) {
listeners.forEach(l -> l.onPacketReceived(packet));
}
public void processPackets() {
synchronized (pendingPackets) {
pendingPackets.forEach(this::forwardPacketToListeners);
pendingPackets.clear();
}
}
public void addListener(CommsListener listener) {
listeners.add(listener);
}

View File

@ -29,14 +29,15 @@ import com.google.common.util.concurrent.MoreExecutors;
import ru.windcorp.progressia.common.util.crash.CrashReports;
/**
* 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.
* This class had to be written because there is not 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.
*
* @author javapony
*/

View File

@ -15,32 +15,45 @@
* 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.generation.surface;
import java.util.List;
package ru.windcorp.progressia.common.modules;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import ru.windcorp.progressia.server.world.context.ServerContext;
import ru.windcorp.progressia.server.world.generation.surface.context.SurfaceWorldContext;
public abstract class SurfaceFeature extends Namespaced {
import java.util.*;
public SurfaceFeature(String id) {
public class Module extends Namespaced {
private final Set<Task> tasks = new HashSet<>();
private final Map<String, String> meta = new HashMap<>();
/**
* @param id the identifier of a task object.
* Its format is restricted by {@link Namespaced}.
* @see Namespaced#Namespaced
*/
public Module(String id) {
super(id);
meta.put("id", id);
}
public abstract void process(SurfaceWorldContext context);
protected static double randomDouble(ServerContext context, double from, double to) {
return from + (to - from) * context.getRandom().nextDouble();
/**
* @return meta information of the module as {@link Map}.
*/
public Map<String, String> getMeta() {
return Collections.unmodifiableMap(meta);
}
protected static double stretch(double t, double from, double to) {
return from + (to - from) * t;
public Set<Task> getTasks() {
return Collections.unmodifiableSet(tasks);
}
protected static <T> T pickRandom(ServerContext context, List<T> from) {
return from.get(context.getRandom().nextInt(from.size()));
/**
* @param task that will be attached to the module.
* A task can't be added to any module second time.
*/
public void addTask(Task task) {
task.setOwner(this);
tasks.add(task);
}
}

View File

@ -15,10 +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.common.util.math;
package ru.windcorp.progressia.common.modules;
public interface FloatFunction3D {
public class ModuleBuilder {
private final Module module;
float apply(float x, float y, float z);
public ModuleBuilder(String id) {
module = new Module(id);
}
public ModuleBuilder AddTask(Task task) {
module.addTask(task);
return this;
}
}

View File

@ -0,0 +1,130 @@
/*
* 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.modules;
import ru.windcorp.jputil.chars.StringUtil;
import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
public abstract class Task
extends Namespaced
implements Runnable {
private final Set<Task> requiredTasks = new HashSet<>();
private final AtomicBoolean isDone = new AtomicBoolean(false);
private final AtomicBoolean isActive = new AtomicBoolean(false);
private Module owner;
/**
* @param id the identifier of a task object.
* Its format is restricted by {@link Namespaced}.
* @see Namespaced#Namespaced
*/
protected Task(String id) {
super(id);
}
@Override
public void run() {
if (!canRun()) {
List<Task> undoneTasks = new ArrayList<>();
for (Iterator<Task> iterator = requiredTasks.iterator(); iterator.hasNext(); ) {
Task t = iterator.next();
if (!t.isDone()) {
undoneTasks.add(t);
}
}
throw CrashReports.report(new Throwable(),
"The following required Tasks are not done:\n%s",
StringUtil.iterableToString(undoneTasks, "\n"));
} else if (isDone()) {
throw CrashReports.report(new Throwable(),
"The task cannot be performed second time");
} else {
isActive.set(true);
perform();
isActive.set(false);
isDone.set(true);
}
}
/**
* The method is to be invoked in run().
* @see Task#run()
*/
protected abstract void perform();
public boolean isDone() {
return isDone.get();
}
/**
* @return if the {@link Task#run()} method is being invoked at the moment or not.
*/
public boolean isActive() {
return isActive.get();
}
/**
*
* @return true - the method is not done and not active
* and all requirement tasks are done, false - otherwise.
*/
public boolean canRun() {
if (this.isActive.get() || isDone.get()) return false;
for (Iterator<Task> iterator = requiredTasks.iterator(); iterator.hasNext(); ) {
Task reqTask = iterator.next();
if (!reqTask.isDone()) return false;
}
return true;
}
public Set<Task> getRequiredTasks() {
return Collections.unmodifiableSet(requiredTasks);
}
public void addRequiredTask(Task task) {
requiredTasks.add(task);
}
/**
* @return the module the task is attached to.
*/
public Module getOwner() {
return owner;
}
/**
* @param module to which the task will be attached.
* Only one module can be the owner of the task.
*/
public void setOwner(Module module) {
if (owner != null) {
throw CrashReports.crash(
null
, "Could not set %s as owner of %s, because %s is already owner of it.",
module.getId(), this.getId(), this.owner.getId());
} else {
owner = module;
}
}
}

View File

@ -0,0 +1,173 @@
/*
* 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.modules;
import org.apache.logging.log4j.LogManager;
import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import static java.util.concurrent.Executors.newFixedThreadPool;
public final class TaskManager {
private static final TaskManager INSTANCE = new TaskManager();
private final Set<Task> tasks = new HashSet<>();
private final Set<Module> modules = new HashSet<>();
private final ExecutorService executorService;
final AtomicBoolean loadingDone;
final AtomicInteger activeThreadsCount;
private final Map<Thread, Namespaced> loadersMonitorMap;
private TaskManager() {
loadingDone = new AtomicBoolean(false);
activeThreadsCount = new AtomicInteger(0);
executorService = newFixedThreadPool(
Runtime.getRuntime().availableProcessors(), Thread::new);
loadersMonitorMap = new HashMap<>(Runtime.getRuntime().availableProcessors());
}
public static TaskManager getInstance() {
return TaskManager.INSTANCE;
}
/**
* Registers the module and its tasks that are
* to be performed by {@link TaskManager#startLoading()}.
*
* @param module from where to register tasks for loading.
*/
public void registerModule(Module module) {
tasks.addAll(module.getTasks());
modules.add(module);
}
/**
* Registers a task that is to be performed
* by {@link TaskManager#startLoading()}.
*
* @param task to register for loading.
*/
public void addTask(Task task) {
tasks.add(task);
}
public boolean isLoadingDone() {
return loadingDone.get();
}
/**
* The method to start loading. It will perform every registered task.
*/
public void startLoading() {
LogManager.getLogger().info("Loading is started");
int procAmount = Runtime.getRuntime().availableProcessors();
for (int i = 0; i < procAmount; i++) {
executorService.submit(loaderTask);
}
waitForLoadingEnd();
if (!tasks.isEmpty()) {
throw CrashReports.crash(null, "Loading is failed");
}
LogManager.getLogger().info("Loading is finished");
executorService.shutdownNow();
}
/**
* @return Task - founded registered task with {@link Task#canRun()} = true;
* null - there is no available task found.
* @see Task#canRun()
*/
synchronized Task getRunnableTask() {
if (!tasks.isEmpty()) {
for (Iterator<Task> iterator = tasks.iterator(); iterator.hasNext(); ) {
Task t = iterator.next();
if (t.canRun()) {
tasks.remove(t);
return t;
}
}
}
return null;
}
/**
* Makes the thread that is performing this method
* to wait until the loading is not done.
*/
private void waitForLoadingEnd() {
synchronized (tasks) {
while (!loadingDone.get()) {
try {
tasks.wait();
} catch (InterruptedException ignored) {}
}
}
}
private void stopLoading() {
loadingDone.set(true);
synchronized (tasks) {
tasks.notifyAll();
}
}
/**
* @return a map where key is a thread making loading
* and where value is a {@link Namespaced} of {@link Task} that is being performed by it
* at the moment.
* @see Namespaced
*/
public Map<Thread, Namespaced> getLoadersMonitorMap() {
return Collections.unmodifiableMap(loadersMonitorMap);
}
private final Runnable loaderTask = new Runnable() {
@Override
public void run() {
while (!loadingDone.get()) {
Task t = getRunnableTask();
if (t != null) {
activeThreadsCount.incrementAndGet();
loadersMonitorMap.put(Thread.currentThread(), t);
t.run();
loadersMonitorMap.put(Thread.currentThread(), null);
activeThreadsCount.decrementAndGet();
synchronized (tasks) {
tasks.notifyAll();
}
} else if (activeThreadsCount.get() > 0) {
try {
synchronized (tasks) {
tasks.wait();
}
} catch (InterruptedException ignored) {}
} else {
stopLoading();
}
}
}
};
}

View File

@ -29,20 +29,6 @@ public abstract class AbstractStatefulObjectLayout
super(objectId);
}
@Override
public StateStorage createStorage() {
StateStorage storage = instantiateStorage();
int fieldCount = getFieldCount();
for (int i = 0; i < fieldCount; ++i) {
getField(i).setDefault(storage);
}
return storage;
}
protected abstract StateStorage instantiateStorage();
protected abstract int getFieldCount();
protected abstract StateField getField(int fieldIndex);

View File

@ -1,64 +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.state;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public interface Encodable {
/**
* Sets the state of this object according to the binary representation read
* from {@code input}. If {@code context == COMMS}, the state of local
* fields is unspecified after this operation.
*
* @param input a {@link DataInput} that a state can be read from
* @param context the context
* @throws IOException if the state is encoded poorly or an error occurs
* in {@code input}
*/
void read(DataInput input, IOContext context) throws IOException;
/**
* Writes the binary representation of the state of this object to the
* {@code output}.
*
* @param output a {@link DataOutput} that a state can be written to
* @param context the context
* @throws IOException if an error occurs in {@code output}
*/
void write(DataOutput output, IOContext context) throws IOException;
/**
* Turns {@code destination} into a deep copy of this object.
* <p>
* Changes the provided object so that:
* <ul>
* <li>the provided object equals this object; and</li>
* <li>the provided object is independent of this object, meaning no change
* to {@code destination} can affect this object.</li>
* </ul>
*
* @param destination the object to copy this object into. Runtime class
* must match this class
* @return {@code destination}
*/
void copy(Encodable destination);
}

View File

@ -19,14 +19,11 @@
package ru.windcorp.progressia.common.state;
import gnu.trove.map.TIntIntMap;
import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.hash.TIntIntHashMap;
import gnu.trove.map.hash.TIntObjectHashMap;
public class HashMapStateStorage extends StateStorage {
private final TIntIntMap ints = new TIntIntHashMap();
private final TIntObjectMap<Object> objects = new TIntObjectHashMap<>();
@Override
public int getInt(int index) {
@ -38,14 +35,4 @@ public class HashMapStateStorage extends StateStorage {
ints.put(index, value);
}
@Override
public Object getObject(int index) {
return objects.get(index);
}
@Override
public void setObject(int index, Object object) {
objects.put(index, object);
}
}

View File

@ -20,9 +20,6 @@ package ru.windcorp.progressia.common.state;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import ru.windcorp.progressia.common.state.codec.ObjectCodec;
public class InspectingStatefulObjectLayout
extends AbstractStatefulObjectLayout {
@ -36,7 +33,7 @@ public class InspectingStatefulObjectLayout
}
@Override
public StateStorage instantiateStorage() {
public StateStorage createStorage() {
return new HashMapStateStorage();
}
@ -85,31 +82,6 @@ public class InspectingStatefulObjectLayout
}
private class Obj<T> implements StateFieldBuilder.Obj<T> {
private final ObjectCodec<T> codec;
private final Supplier<T> defaultValue;
public Obj(ObjectCodec<T> codec, Supplier<T> defaultValue) {
this.codec = codec;
this.defaultValue = defaultValue;
}
@Override
public ObjectStateField<T> build() {
return registerField(
new ObjectStateField<T>(
id,
isLocal,
fieldIndexCounters.getObjectsThenIncrement(),
codec,
defaultValue
)
);
}
}
private final String id;
private boolean isLocal = true;
@ -123,11 +95,6 @@ public class InspectingStatefulObjectLayout
return new Int();
}
@Override
public <T> Obj<T> of(ObjectCodec<T> codec, Supplier<T> defaultValue) {
return new Obj<T>(codec, defaultValue);
}
@Override
public StateFieldBuilder setLocal(boolean isLocal) {
this.isLocal = isLocal;

View File

@ -79,9 +79,4 @@ public class IntStateField extends StateField {
return get(a) == get(b);
}
@Override
public void setDefault(StateStorage storage) {
storage.setInt(getIndex(), 0);
}
}

View File

@ -1,107 +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.state;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.function.Supplier;
import ru.windcorp.progressia.common.state.codec.ObjectCodec;
public class ObjectStateField<T> extends StateField {
private final ObjectCodec<T> codec;
private final Supplier<T> defaultValue;
public ObjectStateField(
String id,
boolean isLocal,
int index,
ObjectCodec<T> codec,
Supplier<T> defaultValue
) {
super(id, isLocal, index);
this.codec = codec;
this.defaultValue = defaultValue;
}
public ObjectCodec<T> getCodec() {
return codec;
}
@SuppressWarnings("unchecked")
public T get(StatefulObject object) {
return (T) object.getStorage().getObject(getIndex());
}
public void setNow(StatefulObject object, T value) {
object.getStorage().setObject(getIndex(), value);
}
public void set(StateChanger changer, T value) {
changer.setObject(this, value);
}
@Override
public void read(
StatefulObject object,
DataInput input,
IOContext context
)
throws IOException {
T previous = get(object);
T result = codec.read(previous, input, context);
object.getStorage().setObject(getIndex(), result);
}
@Override
public void write(
StatefulObject object,
DataOutput output,
IOContext context
)
throws IOException {
codec.write(object.getStorage().getObject(getIndex()), output, context);
}
@Override
public void copy(StatefulObject from, StatefulObject to) {
setNow(to, get(from));
}
@Override
public int computeHashCode(StatefulObject object) {
return codec.computeHashCode(get(object));
}
@Override
public boolean areEqual(StatefulObject a, StatefulObject b) {
return codec.areEqual(get(a), get(b));
}
@Override
public void setDefault(StateStorage storage) {
storage.setObject(getIndex(), defaultValue.get());
}
}

View File

@ -21,11 +21,9 @@ package ru.windcorp.progressia.common.state;
public class OptimizedStateStorage extends StateStorage {
private final int[] ints;
private final Object[] objects;
public OptimizedStateStorage(PrimitiveCounters sizes) {
this.ints = new int[sizes.getInts()];
this.objects = new Object[sizes.getObjects()];
}
@Override
@ -38,14 +36,4 @@ public class OptimizedStateStorage extends StateStorage {
ints[index] = value;
}
@Override
public Object getObject(int index) {
return objects[index];
}
@Override
public void setObject(int index, Object object) {
objects[index] = object;
}
}

View File

@ -19,12 +19,9 @@
package ru.windcorp.progressia.common.state;
import java.util.List;
import java.util.function.Supplier;
import com.google.common.collect.ImmutableList;
import ru.windcorp.progressia.common.state.codec.ObjectCodec;
public class OptimizedStatefulObjectLayout
extends AbstractStatefulObjectLayout {
@ -52,7 +49,7 @@ public class OptimizedStatefulObjectLayout
}
@Override
public StateStorage instantiateStorage() {
public StateStorage createStorage() {
return new OptimizedStateStorage(sizes);
}
@ -75,17 +72,6 @@ public class OptimizedStatefulObjectLayout
};
}
@Override
public <T> Obj<T> of(ObjectCodec<T> codec, Supplier<T> defaultValue) {
return new Obj<T>() {
@SuppressWarnings("unchecked")
@Override
public ObjectStateField<T> build() {
return (ObjectStateField<T>) result;
}
};
}
@Override
public StateFieldBuilder setLocal(boolean isLocal) {
return this;

View File

@ -21,14 +21,12 @@ package ru.windcorp.progressia.common.state;
class PrimitiveCounters {
private int ints = 0;
private int objects = 0;
public PrimitiveCounters() {
}
public PrimitiveCounters(PrimitiveCounters copyFrom) {
this.ints = copyFrom.ints;
this.objects = copyFrom.objects;
}
public int getInts() {
@ -39,12 +37,4 @@ class PrimitiveCounters {
return this.ints++;
}
public int getObjects() {
return objects;
}
public int getObjectsThenIncrement() {
return this.objects++;
}
}

View File

@ -21,6 +21,5 @@ package ru.windcorp.progressia.common.state;
public interface StateChanger {
void setInt(IntStateField field, int value);
<T> void setObject(ObjectStateField<T> field, T value);
}

View File

@ -67,6 +67,4 @@ public abstract class StateField extends Namespaced {
public abstract boolean areEqual(StatefulObject a, StatefulObject b);
public abstract void setDefault(StateStorage storage);
}

View File

@ -18,59 +18,14 @@
package ru.windcorp.progressia.common.state;
import java.util.function.Supplier;
import ru.windcorp.progressia.common.state.codec.ObjectCodec;
import ru.windcorp.progressia.common.state.codec.ObjectCodecRegistry;
public interface StateFieldBuilder {
public static interface Int {
IntStateField build();
}
public static interface Obj<T> {
ObjectStateField<T> build();
}
Int ofInt();
<T> Obj<T> of(ObjectCodec<T> codec, Supplier<T> defaultValue);
default <T> Obj<T> of(Class<T> clazz, Supplier<T> defaultValue) {
ObjectCodec<T> codec = ObjectCodecRegistry.get(clazz);
return of(codec, defaultValue);
}
default <T> Obj<T> of(ObjectCodec<T> codec, T defaultValue) {
return of(codec, (Supplier<T>) () -> codec.copy(defaultValue, null));
}
default <T> Obj<T> of(Class<T> clazz, T defaultValue) {
ObjectCodec<T> codec = ObjectCodecRegistry.get(clazz);
return of(codec, (Supplier<T>) () -> codec.copy(defaultValue, null));
}
default <T> Obj<T> of(ObjectCodec<T> codec) {
return of(codec, (Supplier<T>) () -> null);
}
default <T> Obj<T> of(Class<T> clazz) {
ObjectCodec<T> codec = ObjectCodecRegistry.get(clazz);
return of(codec, (Supplier<T>) () -> null);
}
@SuppressWarnings("unchecked")
default <T> Obj<T> def(Supplier<T> defaultValue) {
Class<T> clazz = (Class<T>) defaultValue.get().getClass();
return of(clazz, defaultValue);
}
@SuppressWarnings("unchecked")
default <T> Obj<T> def(T defaultValue) {
return of((Class<T>) defaultValue.getClass(), defaultValue);
}
StateFieldBuilder setLocal(boolean isLocal);
default StateFieldBuilder setLocal() {

View File

@ -24,8 +24,4 @@ public abstract class StateStorage {
public abstract void setInt(int index, int value);
public abstract Object getObject(int index);
public abstract void setObject(int index, Object object);
}

View File

@ -52,7 +52,7 @@ import ru.windcorp.progressia.common.util.namespaces.Namespaced;
* type.</li>
* </ul>
*/
public abstract class StatefulObject extends Namespaced implements Encodable {
public abstract class StatefulObject extends Namespaced {
private final StatefulObjectLayout layout;
@ -133,7 +133,6 @@ public abstract class StatefulObject extends Namespaced implements Encodable {
* @throws IOException if the state is encoded poorly or an error occurs
* in {@code input}
*/
@Override
public void read(DataInput input, IOContext context) throws IOException {
getLayout().read(this, input, context);
}
@ -146,7 +145,6 @@ public abstract class StatefulObject extends Namespaced implements Encodable {
* @param context the context
* @throws IOException if an error occurs in {@code output}
*/
@Override
public void write(DataOutput output, IOContext context) throws IOException {
getLayout().write(this, output, context);
}
@ -168,8 +166,7 @@ public abstract class StatefulObject extends Namespaced implements Encodable {
*
* @param destination the object to copy this object into.
*/
@Override
public void copy(Encodable destination) {
public StatefulObject copy(StatefulObject destination) {
Objects.requireNonNull(destination, "destination");
if (destination == this) {
@ -185,7 +182,8 @@ public abstract class StatefulObject extends Namespaced implements Encodable {
);
}
getLayout().copy(this, (StatefulObject) destination);
getLayout().copy(this, destination);
return destination;
}
/**

View File

@ -1,52 +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.state.codec;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.Objects;
import ru.windcorp.progressia.common.state.Encodable;
import ru.windcorp.progressia.common.state.IOContext;
public class EncodableCodec extends ReusableObjectCodec<Encodable> {
public EncodableCodec() {
super(Encodable.class);
}
@Override
protected Encodable doRead(Encodable previous, DataInput input, IOContext context) throws IOException {
previous.read(input, context);
return previous;
}
@Override
protected void doWrite(Encodable obj, DataOutput output, IOContext context) throws IOException {
obj.write(output, context);
}
@Override
protected Encodable doCopy(Encodable object, Encodable previous) {
Objects.requireNonNull(previous, "previous");
object.copy(previous);
return previous;
}
}

View File

@ -1,43 +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.state.codec;
import java.io.DataInput;
import java.io.IOException;
import ru.windcorp.progressia.common.state.IOContext;
public abstract class ImmutableObjectCodec<T> extends ObjectCodec<T> {
public ImmutableObjectCodec(Class<T> clazz) {
super(clazz);
}
@Override
public final T copy(T object, T previous) {
return object;
}
@Override
protected final T doRead(T previous, DataInput input, IOContext context) throws IOException {
return doRead(input, context);
}
protected abstract T doRead(DataInput input, IOContext context) throws IOException;
}

View File

@ -1,73 +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.state.codec;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.Objects;
import ru.windcorp.progressia.common.state.IOContext;
public abstract class ObjectCodec<T> {
private final Class<T> clazz;
public ObjectCodec(Class<T> clazz) {
this.clazz = clazz;
}
public Class<T> getDataType() {
return clazz;
}
@SuppressWarnings("unchecked")
public final T read(Object previous, DataInput input, IOContext context) throws IOException {
assert previous == null || clazz.isInstance(previous)
: "Cannot use codec " + this + " on object of type " + previous.getClass();
T result = doRead((T) previous, input, context);
assert result == null || clazz.isInstance(previous)
: "Codec " + this + " read object of type " + previous.getClass();
return result;
}
protected abstract T doRead(T previous, DataInput input, IOContext context) throws IOException;
@SuppressWarnings("unchecked")
public final void write(Object value, DataOutput output, IOContext context) throws IOException {
assert value == null || clazz.isInstance(value)
: "Cannot use codec " + this + " on object of type " + value.getClass();
doWrite((T) value, output, context);
}
protected abstract void doWrite(T obj, DataOutput output, IOContext context) throws IOException;
public abstract T copy(T object, T previous);
public int computeHashCode(T object) {
return Objects.hashCode(object);
}
public boolean areEqual(T a, T b) {
return Objects.equals(a, b);
}
}

View File

@ -1,49 +0,0 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.common.state.codec;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import ru.windcorp.progressia.common.state.Encodable;
public class ObjectCodecRegistry {
private static final Map<Class<?>, ObjectCodec<?>> CODECS = Collections.synchronizedMap(new HashMap<>());
private static final EncodableCodec ENCODABLE_FALLBACK = new EncodableCodec();
public static void register(ObjectCodec<?> codec) {
CODECS.put(codec.getDataType(), codec);
}
@SuppressWarnings("unchecked")
public static <T> ObjectCodec<T> get(Class<T> clazz) {
ObjectCodec<?> codec = CODECS.get(clazz);
if (codec != null) {
return (ObjectCodec<T>) codec;
}
if (Encodable.class.isAssignableFrom(clazz)) {
return (ObjectCodec<T>) ENCODABLE_FALLBACK;
}
throw new IllegalArgumentException("No codec registered for class " + clazz);
}
}

View File

@ -1,42 +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.state.codec;
public abstract class ReusableObjectCodec<T> extends ObjectCodec<T> {
public ReusableObjectCodec(Class<T> clazz) {
super(clazz);
}
@Override
public final T copy(T object, T previous) {
if (object == null) {
return null;
}
T result = doCopy(object, previous);
assert result != null : "copy() returned null";
assert areEqual(object, result) : "copy() does not equal original: " + result + " != " + object;
return result;
}
protected abstract T doCopy(T object, T previous);
}

View File

@ -1,225 +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.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++;
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More