diff --git a/build.gradle b/build.gradle index 1eff0a5..f442d6d 100644 --- a/build.gradle +++ b/build.gradle @@ -18,10 +18,12 @@ dependencies { implementation 'ru.windcorp.fork.io.github.java-graphics:glm:1.0.1' // log4j - compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.13.3' - compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.13.3' + implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.13.3' + implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.13.3' testImplementation 'junit:junit:4.12' + + // See also LWJGL dependencies below } /* @@ -77,3 +79,22 @@ dependencies { } // LWJGL END + +jar { + manifest { + attributes( + "Main-Class": "ru.windcorp.progressia.client.ProgressiaClientMain", + "Class-Path": configurations.runtimeClasspath.collect { "lib/" + it.getName() }.join(' ') + ) + } +} + +/* + * Copies runtime dependencies to a prespecified location so they can be packaged properly. + */ +task copyLibs(type: Copy) { + into "${libsDir}/lib" + from configurations.runtimeClasspath +} + +build.dependsOn(copyLibs) diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/src/main/java/ru/windcorp/jputil/ArrayUtil.java b/src/main/java/ru/windcorp/jputil/ArrayUtil.java index 4c849b7..81d29a3 100644 --- a/src/main/java/ru/windcorp/jputil/ArrayUtil.java +++ b/src/main/java/ru/windcorp/jputil/ArrayUtil.java @@ -66,6 +66,18 @@ public class ArrayUtil { return -1; } + public static boolean isSorted(byte[] array, boolean ascending) { + for (int i = 0; i < array.length - 1; ++i) { + if (array[i] == array[i + 1]) continue; + + if ((array[i] < array[i + 1]) != ascending) { + return false; + } + } + + return true; + } + public static int firstIndexOf(short[] array, short element) { for (int i = 0; i < array.length; ++i) { if (array[i] == element) { @@ -107,6 +119,18 @@ public class ArrayUtil { return -1; } + public static boolean isSorted(short[] array, boolean ascending) { + for (int i = 0; i < array.length - 1; ++i) { + if (array[i] == array[i + 1]) continue; + + if ((array[i] < array[i + 1]) != ascending) { + return false; + } + } + + return true; + } + public static int firstIndexOf(int[] array, int element) { for (int i = 0; i < array.length; ++i) { if (array[i] == element) { @@ -148,6 +172,18 @@ public class ArrayUtil { return -1; } + public static boolean isSorted(int[] array, boolean ascending) { + for (int i = 0; i < array.length - 1; ++i) { + if (array[i] == array[i + 1]) continue; + + if ((array[i] < array[i + 1]) != ascending) { + return false; + } + } + + return true; + } + public static int firstIndexOf(long[] array, long element) { for (int i = 0; i < array.length; ++i) { if (array[i] == element) { @@ -189,6 +225,18 @@ public class ArrayUtil { return -1; } + public static boolean isSorted(long[] array, boolean ascending) { + for (int i = 0; i < array.length - 1; ++i) { + if (array[i] == array[i + 1]) continue; + + if ((array[i] < array[i + 1]) != ascending) { + return false; + } + } + + return true; + } + public static int firstIndexOf(float[] array, float element) { for (int i = 0; i < array.length; ++i) { if (array[i] == element) { @@ -229,6 +277,18 @@ public class ArrayUtil { return -1; } + + public static boolean isSorted(float[] array, boolean ascending) { + for (int i = 0; i < array.length - 1; ++i) { + if (array[i] == array[i + 1]) continue; + + if ((array[i] < array[i + 1]) != ascending) { + return false; + } + } + + return true; + } public static int firstIndexOf(double[] array, double element) { for (int i = 0; i < array.length; ++i) { @@ -271,6 +331,18 @@ public class ArrayUtil { return -1; } + public static boolean isSorted(double[] array, boolean ascending) { + for (int i = 0; i < array.length - 1; ++i) { + if (array[i] == array[i + 1]) continue; + + if ((array[i] < array[i + 1]) != ascending) { + return false; + } + } + + return true; + } + public static int firstIndexOf(boolean[] array, boolean element) { for (int i = 0; i < array.length; ++i) { if (array[i] == element) { @@ -340,6 +412,18 @@ public class ArrayUtil { return -1; } + public static boolean isSorted(char[] array, boolean ascending) { + for (int i = 0; i < array.length - 1; ++i) { + if (array[i] == array[i + 1]) continue; + + if ((array[i] < array[i + 1]) != ascending) { + return false; + } + } + + return true; + } + public static int firstIndexOf(Object[] array, Object element) { for (int i = 0; i < array.length; ++i) { if (array[i] == element) { @@ -422,6 +506,20 @@ public class ArrayUtil { return -1; } + public static > boolean isSorted(T[] array, boolean ascending) { + for (int i = 0; i < array.length - 1; ++i) { + if (array[i] == array[i + 1]) continue; + + int order = array[i].compareTo(array[i + 1]); + + if ((order < 0) != ascending) { + return false; + } + } + + return true; + } + public static long sum(byte[] array, int start, int length) { long s = 0; length += start; diff --git a/src/main/java/ru/windcorp/jputil/chars/StringUtil.java b/src/main/java/ru/windcorp/jputil/chars/StringUtil.java index 9a8933b..de5c37b 100644 --- a/src/main/java/ru/windcorp/jputil/chars/StringUtil.java +++ b/src/main/java/ru/windcorp/jputil/chars/StringUtil.java @@ -26,8 +26,11 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; +import java.util.Objects; import java.util.function.IntFunction; +import ru.windcorp.jputil.ArrayUtil; + public class StringUtil { private StringUtil() {} @@ -368,6 +371,106 @@ public class StringUtil { return result; } + /** + * Splits {@code src} at index {@code at} discarding the character at that index. + *

+ * Indices {@code 0} and {@code src.length() - 1} produce {@code str} excluding + * the specified character and {@code ""}. + *

+ * @param src the String to split + * @param at index to split at + * @throws IllegalArgumentException if the index is out of bounds for {@code src} + * @return an array containing the substrings, in order of encounter in {@code src}. + * Its length is always 2. + */ + public static String[] splitAt(String src, int at) { + Objects.requireNonNull(src, "src"); + + if (at < 0) { + throw new StringIndexOutOfBoundsException(at); + } else if (at >= src.length()) { + throw new StringIndexOutOfBoundsException(at); + } + + if (at == 0) { + return new String[] {"", src.substring(1)}; + } else if (at == src.length()) { + return new String[] {src.substring(0, src.length() - 1), ""}; + } + + return new String[] { + src.substring(0, at), + src.substring(at + 1) + }; + } + + /** + * Splits {@code src} at indices {@code at} discarding characters at those indices. + *

+ * Indices {@code 0} and {@code src.length() - 1} produce extra zero-length outputs. + * Duplicate indices produce extra zero-length outputs. + *

+ * Examples: + *

+	 * splitAt("a.b.c", new int[] {1, 3})    -> {"a", "b", "c"}
+	 * splitAt("a..b",  new int[] {1, 2})    -> {"a", "", "b"}
+	 * splitAt(".b.",   new int[] {0, 2})    -> {"", "b", ""}
+	 * splitAt("a.b",   new int[] {1, 1, 1}) -> {"a", "", "", "b"}
+	 * 
+ * @param src the String to split + * @param at indices to split at, in any order + * @throws IllegalArgumentException if some index is out of bounds for {@code src} + * @return an array containing the substrings, in order of encounter in {@code src}. + * Its length is always {@code at.length + 1}. + */ + public static String[] splitAt(String src, int... at) { + Objects.requireNonNull(src, "src"); + Objects.requireNonNull(at, "at"); + + if (at.length == 0) return new String[] {src}; + if (at.length == 1) return splitAt(src, at[0]); + + int[] indices; // Always sorted + + if (ArrayUtil.isSorted(at, true)) { + indices = at; + } else { + indices = at.clone(); + Arrays.sort(indices); + } + + if (indices[0] < 0) { + throw new StringIndexOutOfBoundsException(indices[0]); + } else if (indices[indices.length - 1] >= src.length()) { + throw new StringIndexOutOfBoundsException(indices[indices.length - 1]); + } + + String[] result = new String[at.length + 1]; + + int start = 0; + int resultIndex = 0; + for (int index : indices) { + int end = index; + + String substring; + + if (end <= start) { + // Duplicate or successive index + substring = ""; + } else { + substring = src.substring(start, end); + } + + result[resultIndex] = substring; + resultIndex++; + start = end + 1; + } + + result[resultIndex] = src.substring(start); + + return result; + } + private static IllegalArgumentException illegalArrayLength(int length) { return new IllegalArgumentException("arrayLength must be non-negative (" + length + ")"); } @@ -775,4 +878,35 @@ public class StringUtil { else return (char) ('A' - 0xA + value); } + public static String replaceAll(String source, String substring, String replacement) { + Objects.requireNonNull(source, "source"); + Objects.requireNonNull(substring, "substring"); + + if (substring.isEmpty()) { + throw new IllegalArgumentException("substring is empty"); + } + + if (!source.contains(substring)) { // also passes if source is empty + return source; + } + + if (substring.equals(replacement)) { // null-safe + return source; + } + + StringBuilder sb = new StringBuilder(2 * source.length()); + + for (int i = 0; i < source.length() - substring.length() + 1; ++i) { + if (source.startsWith(substring, i)) { + if (replacement != null) { + sb.append(replacement); + } + } else { + sb.append(source.charAt(i)); + } + } + + return sb.toString(); + } + } diff --git a/src/main/java/ru/windcorp/progressia/ProgressiaLauncher.java b/src/main/java/ru/windcorp/progressia/ProgressiaLauncher.java index d42217b..f9dbaba 100644 --- a/src/main/java/ru/windcorp/progressia/ProgressiaLauncher.java +++ b/src/main/java/ru/windcorp/progressia/ProgressiaLauncher.java @@ -17,10 +17,23 @@ *******************************************************************************/ package ru.windcorp.progressia; +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.OSContextProvider; + public class ProgressiaLauncher { public static void launch(String[] args, Proxy proxy) { + setupCrashReports(); proxy.initialize(); } + private static void setupCrashReports() { + CrashReports.registerProvider(new OSContextProvider()); + CrashReports.registerAnalyzer(new OutOfMemoryAnalyzer()); + Thread.setDefaultUncaughtExceptionHandler((Thread thread, Throwable t)-> { + CrashReports.report(t,"Uncaught exception in thread %s", thread.getName()); + }); + } + } diff --git a/src/main/java/ru/windcorp/progressia/client/Client.java b/src/main/java/ru/windcorp/progressia/client/Client.java index de6fa54..cd8c5a4 100644 --- a/src/main/java/ru/windcorp/progressia/client/Client.java +++ b/src/main/java/ru/windcorp/progressia/client/Client.java @@ -3,6 +3,7 @@ package ru.windcorp.progressia.client; import ru.windcorp.progressia.client.comms.DefaultClientCommsListener; import ru.windcorp.progressia.client.comms.ServerCommsChannel; import ru.windcorp.progressia.client.graphics.world.Camera; +import ru.windcorp.progressia.client.graphics.world.LocalPlayer; import ru.windcorp.progressia.client.world.WorldRender; import ru.windcorp.progressia.common.world.WorldData; import ru.windcorp.progressia.common.world.entity.EntityData; @@ -10,7 +11,7 @@ import ru.windcorp.progressia.common.world.entity.EntityData; public class Client { private final WorldRender world; - private EntityData localPlayer; + private LocalPlayer localPlayer; private final Camera camera = new Camera((float) Math.toRadians(70)); @@ -27,12 +28,12 @@ public class Client { return world; } - public EntityData getLocalPlayer() { + public LocalPlayer getLocalPlayer() { return localPlayer; } public void setLocalPlayer(EntityData localPlayer) { - this.localPlayer = localPlayer; + this.localPlayer = new LocalPlayer(localPlayer); } public Camera getCamera() { diff --git a/src/main/java/ru/windcorp/progressia/client/ClientProxy.java b/src/main/java/ru/windcorp/progressia/client/ClientProxy.java index 9690642..921fce5 100644 --- a/src/main/java/ru/windcorp/progressia/client/ClientProxy.java +++ b/src/main/java/ru/windcorp/progressia/client/ClientProxy.java @@ -26,6 +26,7 @@ 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.common.resource.ResourceManager; +import ru.windcorp.progressia.common.util.crash.CrashReports; import ru.windcorp.progressia.server.ServerState; import ru.windcorp.progressia.test.TestContent; @@ -39,8 +40,7 @@ public class ClientProxy implements Proxy { RenderTaskQueue.waitAndInvoke(WorldRenderProgram::init); RenderTaskQueue.waitAndInvoke(() -> Typefaces.setDefault(GNUUnifontLoader.load(ResourceManager.getResource("assets/unifont-13.0.03.hex.gz")))); } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + CrashReports.report(e, "ClientProxy failed"); } TestContent.registerContent(); diff --git a/src/main/java/ru/windcorp/progressia/client/ClientState.java b/src/main/java/ru/windcorp/progressia/client/ClientState.java index 84c26a2..60d5ca2 100644 --- a/src/main/java/ru/windcorp/progressia/client/ClientState.java +++ b/src/main/java/ru/windcorp/progressia/client/ClientState.java @@ -3,10 +3,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.flat.LayerTestUI; -import ru.windcorp.progressia.client.graphics.gui.LayerTestGUI; import ru.windcorp.progressia.client.graphics.world.LayerWorld; import ru.windcorp.progressia.common.world.WorldData; import ru.windcorp.progressia.server.ServerState; +import ru.windcorp.progressia.test.LayerTestGUI; public class ClientState { @@ -30,6 +30,8 @@ public class ClientState { Client client = new Client(world, channel); + world.tmp_generate(); + channel.connect(); setInstance(client); diff --git a/src/main/java/ru/windcorp/progressia/client/ProgressiaClientMain.java b/src/main/java/ru/windcorp/progressia/client/ProgressiaClientMain.java index 2cca97f..bf38de9 100644 --- a/src/main/java/ru/windcorp/progressia/client/ProgressiaClientMain.java +++ b/src/main/java/ru/windcorp/progressia/client/ProgressiaClientMain.java @@ -26,5 +26,5 @@ public class ProgressiaClientMain { ALTest.execute(); ProgressiaLauncher.launch(args, new ClientProxy()); } - -} + +} \ No newline at end of file diff --git a/src/main/java/ru/windcorp/progressia/client/comms/DefaultClientCommsListener.java b/src/main/java/ru/windcorp/progressia/client/comms/DefaultClientCommsListener.java index 4292bb9..f2eb76a 100644 --- a/src/main/java/ru/windcorp/progressia/client/comms/DefaultClientCommsListener.java +++ b/src/main/java/ru/windcorp/progressia/client/comms/DefaultClientCommsListener.java @@ -1,19 +1,20 @@ package ru.windcorp.progressia.client.comms; import java.io.IOException; + +import ru.windcorp.jputil.chars.StringUtil; import ru.windcorp.progressia.client.Client; import ru.windcorp.progressia.client.graphics.world.EntityAnchor; -import ru.windcorp.progressia.client.world.ChunkRender; import ru.windcorp.progressia.common.comms.CommsListener; import ru.windcorp.progressia.common.comms.packets.Packet; import ru.windcorp.progressia.common.comms.packets.PacketSetLocalPlayer; import ru.windcorp.progressia.common.comms.packets.PacketWorldChange; +import ru.windcorp.progressia.common.util.crash.CrashReports; import ru.windcorp.progressia.common.world.entity.EntityData; -import ru.windcorp.progressia.common.world.entity.PacketEntityChange; // TODO refactor with no mercy public class DefaultClientCommsListener implements CommsListener { - + private final Client client; public DefaultClientCommsListener(Client client) { @@ -26,10 +27,6 @@ public class DefaultClientCommsListener implements CommsListener { ((PacketWorldChange) packet).apply( getClient().getWorld().getData() ); - - if (!(packet instanceof PacketEntityChange)) { - tmp_reassembleWorld(); - } } else if (packet instanceof PacketSetLocalPlayer) { setLocalPlayer((PacketSetLocalPlayer) packet); } @@ -41,24 +38,24 @@ public class DefaultClientCommsListener implements CommsListener { ); if (entity == null) { - throw new RuntimeException(""); + CrashReports.report( + null, + "Player entity with ID %s not found", + new String(StringUtil.toFullHex(packet.getLocalPlayerEntityId())) + ); } - + getClient().setLocalPlayer(entity); getClient().getCamera().setAnchor(new EntityAnchor( getClient().getWorld().getEntityRenderable(entity) )); } - private void tmp_reassembleWorld() { - getClient().getWorld().getChunks().forEach(ChunkRender::markForUpdate); - } - @Override public void onIOError(IOException reason) { // TODO implement } - + public Client getClient() { return client; } diff --git a/src/main/java/ru/windcorp/progressia/client/comms/controls/ControlTrigger.java b/src/main/java/ru/windcorp/progressia/client/comms/controls/ControlTrigger.java index b755284..3c1ca37 100644 --- a/src/main/java/ru/windcorp/progressia/client/comms/controls/ControlTrigger.java +++ b/src/main/java/ru/windcorp/progressia/client/comms/controls/ControlTrigger.java @@ -1,11 +1,11 @@ package ru.windcorp.progressia.client.comms.controls; -import ru.windcorp.progressia.common.util.Namespaced; +import ru.windcorp.progressia.common.util.namespaces.Namespaced; public abstract class ControlTrigger extends Namespaced { - public ControlTrigger(String namespace, String name) { - super(namespace, name); + public ControlTrigger(String id) { + super(id); } } diff --git a/src/main/java/ru/windcorp/progressia/client/comms/controls/ControlTriggerInputBased.java b/src/main/java/ru/windcorp/progressia/client/comms/controls/ControlTriggerInputBased.java index abdc3a4..eeec0f0 100644 --- a/src/main/java/ru/windcorp/progressia/client/comms/controls/ControlTriggerInputBased.java +++ b/src/main/java/ru/windcorp/progressia/client/comms/controls/ControlTriggerInputBased.java @@ -5,8 +5,8 @@ import ru.windcorp.progressia.common.comms.controls.PacketControl; public abstract class ControlTriggerInputBased extends ControlTrigger { - public ControlTriggerInputBased(String namespace, String name) { - super(namespace, name); + public ControlTriggerInputBased(String id) { + super(id); } public abstract PacketControl onInputEvent(InputEvent event); diff --git a/src/main/java/ru/windcorp/progressia/client/comms/controls/ControlTriggerLambda.java b/src/main/java/ru/windcorp/progressia/client/comms/controls/ControlTriggerLambda.java new file mode 100644 index 0000000..d2a0d69 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/client/comms/controls/ControlTriggerLambda.java @@ -0,0 +1,48 @@ +package ru.windcorp.progressia.client.comms.controls; + +import java.util.function.BiConsumer; +import java.util.function.Predicate; + +import ru.windcorp.progressia.client.graphics.input.InputEvent; +import ru.windcorp.progressia.common.comms.controls.ControlData; +import ru.windcorp.progressia.common.comms.controls.ControlDataRegistry; +import ru.windcorp.progressia.common.comms.controls.PacketControl; +import ru.windcorp.progressia.common.util.namespaces.NamespacedUtil; + +public class ControlTriggerLambda extends ControlTriggerInputBased { + + private final String packetId; + private final Predicate predicate; + private final BiConsumer dataWriter; + + public ControlTriggerLambda( + String id, + Predicate predicate, + BiConsumer dataWriter + ) { + super(id); + + this.packetId = NamespacedUtil.getId( + NamespacedUtil.getNamespace(id), + "ControlKeyPress" + NamespacedUtil.getName(id) + ); + + this.predicate = predicate; + this.dataWriter = dataWriter; + } + + @Override + public PacketControl onInputEvent(InputEvent event) { + if (!predicate.test(event)) return null; + + PacketControl packet = new PacketControl( + packetId, + ControlDataRegistry.getInstance().create(getId()) + ); + + dataWriter.accept(event, packet.getControl()); + + return packet; + } + +} diff --git a/src/main/java/ru/windcorp/progressia/client/comms/controls/ControlTriggerOnKeyPress.java b/src/main/java/ru/windcorp/progressia/client/comms/controls/ControlTriggerOnKeyPress.java deleted file mode 100644 index c830318..0000000 --- a/src/main/java/ru/windcorp/progressia/client/comms/controls/ControlTriggerOnKeyPress.java +++ /dev/null @@ -1,39 +0,0 @@ -package ru.windcorp.progressia.client.comms.controls; - -import java.util.function.Predicate; - -import ru.windcorp.progressia.client.graphics.input.InputEvent; -import ru.windcorp.progressia.client.graphics.input.KeyEvent; -import ru.windcorp.progressia.common.comms.controls.ControlDataRegistry; -import ru.windcorp.progressia.common.comms.controls.PacketControl; - -public class ControlTriggerOnKeyPress extends ControlTriggerInputBased { - - private final Predicate predicate; - private final PacketControl packet; - - public ControlTriggerOnKeyPress( - String namespace, String name, - Predicate predicate - ) { - super(namespace, name); - this.predicate = predicate; - this.packet = new PacketControl( - getNamespace(), "ControlKeyPress" + getName(), - ControlDataRegistry.getInstance().get(getId()) - ); - } - - @Override - public PacketControl onInputEvent(InputEvent event) { - if (!(event instanceof KeyEvent)) return null; - - KeyEvent keyEvent = (KeyEvent) event; - - if (!keyEvent.isPress()) return null; - if (!predicate.test(keyEvent)) return null; - - return packet; - } - -} diff --git a/src/main/java/ru/windcorp/progressia/client/comms/controls/ControlTriggerRegistry.java b/src/main/java/ru/windcorp/progressia/client/comms/controls/ControlTriggerRegistry.java index b695325..65f5d43 100644 --- a/src/main/java/ru/windcorp/progressia/client/comms/controls/ControlTriggerRegistry.java +++ b/src/main/java/ru/windcorp/progressia/client/comms/controls/ControlTriggerRegistry.java @@ -1,8 +1,8 @@ package ru.windcorp.progressia.client.comms.controls; -import ru.windcorp.progressia.common.util.NamespacedRegistry; +import ru.windcorp.progressia.common.util.namespaces.NamespacedInstanceRegistry; -public class ControlTriggerRegistry extends NamespacedRegistry { +public class ControlTriggerRegistry extends NamespacedInstanceRegistry { private static final ControlTriggerRegistry INSTANCE = new ControlTriggerRegistry(); diff --git a/src/main/java/ru/windcorp/progressia/client/comms/controls/ControlTriggers.java b/src/main/java/ru/windcorp/progressia/client/comms/controls/ControlTriggers.java new file mode 100644 index 0000000..5930ee6 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/client/comms/controls/ControlTriggers.java @@ -0,0 +1,176 @@ +package ru.windcorp.progressia.client.comms.controls; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Predicate; + +import ru.windcorp.progressia.client.graphics.input.InputEvent; +import ru.windcorp.progressia.common.comms.controls.ControlData; + +public class ControlTriggers { + + public static ControlTriggerInputBased of( + String id, + BiConsumer dataWriter, + Predicate predicate + ) { + return new ControlTriggerLambda(id, predicate, dataWriter); + } + + public static ControlTriggerInputBased of( + String id, + Consumer dataWriter, + Predicate predicate + ) { + return of( + id, + (input, control) -> dataWriter.accept(control), + predicate + ); + } + + public static ControlTriggerInputBased of( + String id, + Predicate predicate + ) { + return of( + id, + (input, control) -> {}, + predicate + ); + } + + @SafeVarargs + public static ControlTriggerInputBased of( + String id, + Class inputType, + BiConsumer dataWriter, + Predicate... predicates + ) { + return of( + id, + createCheckedDataWriter(inputType, dataWriter), + createCheckedCompoundPredicate(inputType, predicates) + ); + } + + @SafeVarargs + public static ControlTriggerInputBased of( + String id, + Class inputType, + Consumer dataWriter, + Predicate... predicates + ) { + return of( + id, + inputType, + (input, control) -> dataWriter.accept(control), + predicates + ); + } + + @SafeVarargs + public static ControlTriggerInputBased of( + String id, + Class inputType, + Predicate... predicates + ) { + return of( + id, + (input, control) -> {}, + createCheckedCompoundPredicate(inputType, predicates) + ); + } + + @SafeVarargs + public static ControlTriggerInputBased of( + String id, + BiConsumer dataWriter, + Predicate... predicates + ) { + return of( + id, + InputEvent.class, + dataWriter, + predicates + ); + } + + @SafeVarargs + public static ControlTriggerInputBased of( + String id, + Consumer dataWriter, + Predicate... predicates + ) { + return of( + id, + (input, control) -> dataWriter.accept(control), + predicates + ); + } + + @SafeVarargs + public static ControlTriggerInputBased of( + String id, + Predicate... predicates + ) { + return of( + id, + InputEvent.class, + (input, control) -> {}, + predicates + ); + } + + private static + + BiConsumer + createCheckedDataWriter( + Class inputType, + BiConsumer dataWriter + ) { + return (inputEvent, control) -> dataWriter.accept(inputType.cast(inputEvent), control); + } + + private static + + Predicate + createCheckedCompoundPredicate( + Class inputType, + Predicate[] predicates + ) { + return new CompoundCastPredicate<>(inputType, predicates); + } + + private static class CompoundCastPredicate implements Predicate { + + private final Class inputType; + private final Predicate[] predicates; + + public CompoundCastPredicate(Class inputType, Predicate[] predicates) { + this.inputType = inputType; + this.predicates = predicates; + } + + @Override + public boolean test(InputEvent inputEvent) { + if (!inputType.isInstance(inputEvent)) { + return false; + } + + I castEvent = inputType.cast(inputEvent); + + for (Predicate predicate : predicates) { + if (!predicate.test(castEvent)) { + return false; + } + } + + return true; + } + + } + + private ControlTriggers() {} + +} diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/backend/shaders/Program.java b/src/main/java/ru/windcorp/progressia/client/graphics/backend/shaders/Program.java index 6aee18d..2b63885 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/backend/shaders/Program.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/backend/shaders/Program.java @@ -24,37 +24,38 @@ import ru.windcorp.progressia.client.graphics.backend.OpenGLObjectTracker; import ru.windcorp.progressia.client.graphics.backend.OpenGLObjectTracker.OpenGLDeletable; import ru.windcorp.progressia.client.graphics.backend.shaders.attributes.Attribute; import ru.windcorp.progressia.client.graphics.backend.shaders.uniforms.Uniform; +import ru.windcorp.progressia.common.util.crash.CrashReports; public class Program implements OpenGLDeletable { - + private int handle; - + public Program(Shader vertexShader, Shader fragmentShader) { handle = glCreateProgram(); OpenGLObjectTracker.register(this); - + glAttachShader(handle, vertexShader.getHandle()); glAttachShader(handle, fragmentShader.getHandle()); - + glLinkProgram(handle); - + if (glGetProgrami(handle, GL_LINK_STATUS) == GL_FALSE) { - throw new RuntimeException("Bad program:\n" + glGetProgramInfoLog(handle)); + CrashReports.report(null, "Bad program:\n%s", glGetProgramInfoLog(handle)); } } - + public Attribute getAttribute(String name) { return new Attribute(glGetAttribLocation(handle, name), this); } - + public Uniform getUniform(String name) { return new Uniform(glGetUniformLocation(handle, name), this); } - + public void use() { glUseProgram(handle); } - + @Override public void delete() { glDeleteProgram(handle); diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/backend/shaders/Shader.java b/src/main/java/ru/windcorp/progressia/client/graphics/backend/shaders/Shader.java index 7dfef92..27567e7 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/backend/shaders/Shader.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/backend/shaders/Shader.java @@ -26,26 +26,26 @@ import ru.windcorp.progressia.client.graphics.backend.OpenGLObjectTracker; import ru.windcorp.progressia.client.graphics.backend.OpenGLObjectTracker.OpenGLDeletable; import ru.windcorp.progressia.common.resource.Resource; import ru.windcorp.progressia.common.resource.ResourceManager; +import ru.windcorp.progressia.common.util.crash.CrashReports; public class Shader implements OpenGLDeletable { - + public static enum ShaderType { - VERTEX(GL_VERTEX_SHADER), - FRAGMENT(GL_FRAGMENT_SHADER); - + VERTEX(GL_VERTEX_SHADER), FRAGMENT(GL_FRAGMENT_SHADER); + private final int glCode; - + private ShaderType(int glCode) { this.glCode = glCode; } - + public int getGlCode() { return glCode; } - + public static ShaderType guessByResourceName(String resource) { resource = resource.toLowerCase(Locale.ENGLISH); - + if (resource.contains("vertex")) return VERTEX; if (resource.contains("fragment")) return FRAGMENT; if (resource.contains("vsh")) return VERTEX; @@ -57,48 +57,48 @@ public class Shader implements OpenGLDeletable { ); } } - + private static final String SHADER_ASSETS_PREFIX = "assets/shaders/"; - + protected static Resource getShaderResource(String name) { return ResourceManager.getResource(SHADER_ASSETS_PREFIX + name); } - + private final int handle; private final ShaderType type; - + public Shader(ShaderType type, String source) { handle = glCreateShader(type.getGlCode()); OpenGLObjectTracker.register(this); - + this.type = type; - + glShaderSource(handle, source); glCompileShader(handle); - + if (glGetShaderi(handle, GL_COMPILE_STATUS) == GL_FALSE) { System.out.println("***************** ERROR ******************"); System.out.println(source); - throw new RuntimeException("Bad shader:\n" + glGetShaderInfoLog(handle)); + CrashReports.report(null, "Bad shader:\n %s", glGetShaderInfoLog(handle)); } } - + public Shader(String resource) { this( ShaderType.guessByResourceName(resource), getShaderResource(resource).readAsString() ); } - + @Override public void delete() { glDeleteShader(handle); } - + public int getHandle() { return handle; } - + public ShaderType getType() { return type; } diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/backend/shaders/attributes/Attribute.java b/src/main/java/ru/windcorp/progressia/client/graphics/backend/shaders/attributes/Attribute.java index 3773d18..9075085 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/backend/shaders/attributes/Attribute.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/backend/shaders/attributes/Attribute.java @@ -18,29 +18,30 @@ package ru.windcorp.progressia.client.graphics.backend.shaders.attributes; import ru.windcorp.progressia.client.graphics.backend.shaders.Program; +import ru.windcorp.progressia.common.util.crash.CrashReports; public class Attribute { - + protected final int handle; private final Program program; - + public Attribute(int handle, Program program) { if (handle < 0) { - throw new RuntimeException("Bad handle: " + handle); + CrashReports.report(null, "Bad handle: %d", handle); } - + this.handle = handle; this.program = program; } - + public int getHandle() { return handle; } - + public Program getProgram() { return program; } - + public AttributeVertexArray asVertexArray() { return new AttributeVertexArray(handle, program); } diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/backend/shaders/uniforms/Uniform.java b/src/main/java/ru/windcorp/progressia/client/graphics/backend/shaders/uniforms/Uniform.java index be78878..996b1fe 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/backend/shaders/uniforms/Uniform.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/backend/shaders/uniforms/Uniform.java @@ -18,69 +18,70 @@ package ru.windcorp.progressia.client.graphics.backend.shaders.uniforms; import ru.windcorp.progressia.client.graphics.backend.shaders.Program; +import ru.windcorp.progressia.common.util.crash.CrashReports; public class Uniform { - + protected final int handle; private final Program program; - + public Uniform(int handle, Program program) { if (handle < 0) { - throw new RuntimeException("Bad handle: " + handle); + CrashReports.report(null, "Bad handle: %d", handle); } - + this.handle = handle; this.program = program; } - + public int getHandle() { return handle; } - + public Program getProgram() { return program; } - + public Uniform1Float as1Float() { return new Uniform1Float(handle, program); } - + public Uniform1Int as1Int() { return new Uniform1Int(handle, program); } - + public Uniform2Float as2Float() { return new Uniform2Float(handle, program); } - + public Uniform2Int as2Int() { return new Uniform2Int(handle, program); } - + public Uniform3Float as3Float() { return new Uniform3Float(handle, program); } - + public Uniform3Int as3Int() { return new Uniform3Int(handle, program); } - + public Uniform4Float as4Float() { return new Uniform4Float(handle, program); } - + public Uniform4Int as4Int() { return new Uniform4Int(handle, program); } - + public Uniform2Matrix as2Matrix() { return new Uniform2Matrix(handle, program); } - + public Uniform3Matrix as3Matrix() { return new Uniform3Matrix(handle, program); } - + public Uniform4Matrix as4Matrix() { return new Uniform4Matrix(handle, program); } diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/font/GNUUnifontLoader.java b/src/main/java/ru/windcorp/progressia/client/graphics/font/GNUUnifontLoader.java index 681effb..72f359c 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/font/GNUUnifontLoader.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/font/GNUUnifontLoader.java @@ -21,152 +21,142 @@ import ru.windcorp.progressia.client.graphics.texture.Texture; import ru.windcorp.progressia.client.graphics.texture.TextureDataEditor; import ru.windcorp.progressia.client.graphics.texture.TextureSettings; import ru.windcorp.progressia.common.resource.Resource; +import ru.windcorp.progressia.common.util.crash.CrashReports; public class GNUUnifontLoader { - - private static final AtlasGroup ATLAS_GROUP_GNU_UNIFONT = - new AtlasGroup("GNUUnifont", 1 << 12); - - private static final TextureSettings TEXTURE_SETTINGS = - new TextureSettings(false); - + + private static final AtlasGroup ATLAS_GROUP_GNU_UNIFONT = new AtlasGroup("GNUUnifont", 1 << 12); + + private static final TextureSettings TEXTURE_SETTINGS = new TextureSettings(false); + private static final int BITS_PER_HEX_DIGIT = 4; private static final int PREFIX_LENGTH = "0000:".length(); - + private static class ParsedGlyph { final char c; final TextureDataEditor data; - + ParsedGlyph(char c, TextureDataEditor data) { this.c = c; this.data = data; } } - + private static class AtlasGlyph { final char c; final Texture texture; - + AtlasGlyph(char c, Texture texture) { this.c = c; this.texture = texture; } } - + public static GNUUnifont load(Resource resource) { try (BufferedReader reader = createReader(resource)) { - return createStream(reader) - .map(GNUUnifontLoader::parse) - .map(GNUUnifontLoader::addToAtlas) - .collect(Collectors.collectingAndThen( - createMapper(), - GNUUnifont::new - )); + return createStream(reader).map(GNUUnifontLoader::parse).map(GNUUnifontLoader::addToAtlas) + .collect(Collectors.collectingAndThen(createMapper(), GNUUnifont::new)); } catch (IOException | UncheckedIOException e) { - throw new RuntimeException(e); + CrashReports.report(e, "Could not load GNUUnifont"); + return null; } } - - private static BufferedReader createReader(Resource resource) - throws IOException - { + + private static BufferedReader createReader(Resource resource) throws IOException { return new BufferedReader( - new InputStreamReader( - new GZIPInputStream( - resource.getInputStream() - ), - StandardCharsets.UTF_8 - ) - ); + new InputStreamReader(new GZIPInputStream(resource.getInputStream()), StandardCharsets.UTF_8)); } private static Stream createStream(BufferedReader reader) { return reader.lines(); } - + private static ParsedGlyph parse(String declar) { - int width = getWidth(declar); - checkDeclaration(declar, width); + try { + + int width = getWidth(declar); + checkDeclaration(declar, width); - char c = getChar(declar); + char c = getChar(declar); - TextureDataEditor editor = new TextureDataEditor( - width, GNUUnifont.HEIGHT, - width, GNUUnifont.HEIGHT, - TEXTURE_SETTINGS - ); + TextureDataEditor editor = new TextureDataEditor(width, GNUUnifont.HEIGHT, width, GNUUnifont.HEIGHT, + TEXTURE_SETTINGS); - for (int y = 0; y < GNUUnifont.HEIGHT; ++y) { - for (int x = 0; x < width; ++x) { - int bit = x + y * width; - - editor.setPixel( - x, GNUUnifont.HEIGHT - y - 1, - getBit(declar, bit) ? 0xFFFFFFFF : 0x00000000 - ); + for (int y = 0; y < GNUUnifont.HEIGHT; ++y) { + for (int x = 0; x < width; ++x) { + int bit = x + y * width; + + editor.setPixel(x, GNUUnifont.HEIGHT - y - 1, getBit(declar, bit) ? 0xFFFFFFFF : 0x00000000); + } } - } - return new ParsedGlyph(c, editor); + return new ParsedGlyph(c, editor); + + } catch (IOException e) { + CrashReports.report(e, "Could not load GNUUnifont: could not load character \"%s\"", declar); + return null; + } } - + private static char getChar(String declar) { int result = 0; - + for (int i = 0; i < 4; ++i) { - result = - (result << BITS_PER_HEX_DIGIT) | - getHexValue(declar.charAt(i)); + result = (result << BITS_PER_HEX_DIGIT) | getHexValue(declar.charAt(i)); } - + return (char) result; } private static boolean getBit(String declar, int bit) { int character = PREFIX_LENGTH + (bit / BITS_PER_HEX_DIGIT); bit = bit % BITS_PER_HEX_DIGIT; - + char c = declar.charAt(character); int value = getHexValue(c); - + return (value & (1 << (BITS_PER_HEX_DIGIT - bit - 1))) != 0; } - + private static int getWidth(String declar) { int meaningfulChars = declar.length() - PREFIX_LENGTH; final int charsPerColumn = GNUUnifont.HEIGHT / BITS_PER_HEX_DIGIT; return meaningfulChars / charsPerColumn; } - private static void checkDeclaration(String declar, int width) { + private static void checkDeclaration(String declar, int width) throws IOException { if (!GNUUnifont.WIDTHS.contains(width)) { - throw new RuntimeException("Width " + width + " is not supported (in declar \"" + declar + "\")"); + throw new IOException("Width " + width + " is not supported (in declar \"" + declar + "\")"); } if ((declar.length() - PREFIX_LENGTH) % width != 0) { - throw new RuntimeException("Declar \"" + declar + "\" has invalid length"); + throw new IOException("Declar \"" + declar + "\" has invalid length"); } for (int i = 0; i < declar.length(); ++i) { if (i == BITS_PER_HEX_DIGIT) { if (declar.charAt(i) != ':') { - throw new RuntimeException("No colon ':' found in declar \"" + declar + "\" at index 4"); + throw new IOException("No colon ':' found in declar \"" + declar + "\" at index 4"); } } else { char c = declar.charAt(i); if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F'))) { - throw new RuntimeException("Illegal char in declar \"" + declar + "\" at index " + i + "; expected 0-9A-F"); + throw new IOException("Illegal char in declar \"" + declar + "\" at index " + i + "; expected 0-9A-F"); } } } } - + private static int getHexValue(char digit) { - if (digit < '0') throw new NumberFormatException(digit + " is not a hex digit (0-9A-F expected)"); - if (digit <= '9') return digit - '0'; - if (digit < 'A') throw new NumberFormatException(digit + " is not a hex digit (0-9A-F expected)"); - if (digit <= 'F') return digit - 'A' + 0xA; + if (digit < '0') + throw new NumberFormatException(digit + " is not a hex digit (0-9A-F expected)"); + if (digit <= '9') + return digit - '0'; + if (digit < 'A') + throw new NumberFormatException(digit + " is not a hex digit (0-9A-F expected)"); + if (digit <= 'F') + return digit - 'A' + 0xA; throw new NumberFormatException(digit + " is not a hex digit (0-9A-F expected)"); } @@ -174,24 +164,21 @@ public class GNUUnifontLoader { Sprite sprite = Atlases.loadSprite(glyph.data.getData(), ATLAS_GROUP_GNU_UNIFONT); return new AtlasGlyph(glyph.c, new SimpleTexture(sprite)); } - - private static - Collector> - createMapper() { - return Collector.of( - TCharObjectHashMap::new, - + + private static Collector> createMapper() { + return Collector.of(TCharObjectHashMap::new, + (map, glyph) -> map.put(glyph.c, glyph.texture), - + (a, b) -> { a.putAll(b); return a; }, - - Characteristics.UNORDERED - ); + + Characteristics.UNORDERED); } - private GNUUnifontLoader() {} + private GNUUnifontLoader() { + } } diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/gui/Component.java b/src/main/java/ru/windcorp/progressia/client/graphics/gui/Component.java index 9260476..1a1b1f4 100755 --- a/src/main/java/ru/windcorp/progressia/client/graphics/gui/Component.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/gui/Component.java @@ -39,40 +39,40 @@ import ru.windcorp.progressia.client.graphics.input.bus.Input; import ru.windcorp.progressia.client.graphics.input.bus.InputBus; import ru.windcorp.progressia.client.graphics.input.bus.InputListener; import ru.windcorp.progressia.common.util.Named; +import ru.windcorp.progressia.common.util.crash.CrashReports; public class Component extends Named { - private final List children = - Collections.synchronizedList(new CopyOnWriteArrayList<>()); - + private final List children = Collections.synchronizedList(new CopyOnWriteArrayList<>()); + private Component parent = null; - + private EventBus eventBus = null; private InputBus inputBus = null; - + private int x, y; private int width, height; - + private boolean valid = false; - + private Vec2i preferredSize = null; - + private Object layoutHint = null; private Layout layout = null; - + private boolean isFocusable = false; private boolean isFocused = false; - + private boolean isHovered = false; public Component(String name) { super(name); } - + public Component getParent() { return parent; } - + protected void setParent(Component parent) { if (this.parent != parent) { Component previousParent = this.parent; @@ -81,68 +81,71 @@ public class Component extends Named { dispatchEvent(new ParentChangedEvent(this, previousParent, parent)); } } - + public List getChildren() { return children; } - + public Component getChild(int index) { synchronized (getChildren()) { - if (index < 0 || index >= getChildren().size()) return null; + if (index < 0 || index >= getChildren().size()) + return null; return getChildren().get(index); } } - + public int getChildIndex(Component child) { return getChildren().indexOf(child); } - + public int getOwnIndex() { Component parent = getParent(); if (parent != null) { return parent.getChildIndex(this); } - + return -1; } - + public void moveChild(Component child, int newIndex) { - if (newIndex == -1) newIndex = getChildren().size() - 1; - + if (newIndex == -1) + newIndex = getChildren().size() - 1; + if (getChildren().remove(child)) { getChildren().add(newIndex, child); invalidate(); } } - + public void moveSelf(int newIndex) { Component parent = getParent(); if (parent != null) { parent.moveChild(this, newIndex); } } - + public Component addChild(Component child, int index) { - if (index == -1) index = getChildren().size(); + if (index == -1) + index = getChildren().size(); invalidate(); getChildren().add(index, child); child.setParent(this); - + dispatchEvent(new ChildAddedEvent(this, child)); - + return this; } - + public Component addChild(Component child) { return addChild(child, -1); } - + public Component removeChild(Component child) { if (!getChildren().contains(child)) { return this; } - + if (child.isFocused()) { child.focusNext(); } @@ -150,204 +153,205 @@ public class Component extends Named { invalidate(); getChildren().remove(child); child.setParent(null); - + dispatchEvent(new ChildRemovedEvent(this, child)); - + return this; } - + public synchronized int getX() { return x; } - + public synchronized int getY() { return y; } - + public synchronized Component setPosition(int x, int y) { invalidate(); this.x = x; this.y = y; return this; } - + public synchronized int getWidth() { return width; } - + public synchronized int getHeight() { return height; } - + public synchronized Component setSize(int width, int height) { invalidate(); this.width = width; this.height = height; return this; } - + public Component setSize(Vec2i size) { return setSize(size.x, size.y); } - + public synchronized Component setBounds(int x, int y, int width, int height) { setPosition(x, y); setSize(width, height); return this; } - + public Component setBounds(int x, int y, Vec2i size) { return setBounds(x, y, size.x, size.y); } - + public boolean isValid() { return valid; } - + public synchronized void invalidate() { valid = false; getChildren().forEach(child -> child.invalidate()); } - + public synchronized void validate() { Component parent = getParent(); invalidate(); - + if (parent == null) { layoutSelf(); } else { parent.validate(); } } - + protected synchronized void layoutSelf() { try { if (getLayout() != null) { getLayout().layout(this); } - + getChildren().forEach(child -> { child.layoutSelf(); }); - + valid = true; } catch (Exception e) { - throw new RuntimeException(e); + CrashReports.report(e, "Could not layout Component %s", this); } } - + public synchronized Vec2i getPreferredSize() { if (preferredSize != null) { return preferredSize; } - + if (getLayout() != null) { try { return getLayout().calculatePreferredSize(this); } catch (Exception e) { - throw new RuntimeException(e); + CrashReports.report(e, "Could not calculate preferred size for Component %s", this); } } - + return new Vec2i(0, 0); } - + public synchronized Component setPreferredSize(Vec2i preferredSize) { this.preferredSize = preferredSize; return this; } - + public Component setPreferredSize(int width, int height) { return setPreferredSize(new Vec2i(width, height)); } - + public Layout getLayout() { return layout; } - + public synchronized Component setLayout(Layout layout) { invalidate(); this.layout = layout; return this; } - + public Object getLayoutHint() { return layoutHint; } - + public Component setLayoutHint(Object hint) { this.layoutHint = hint; return this; } - + public boolean isFocusable() { return isFocusable; } - + public Component setFocusable(boolean focusable) { this.isFocusable = focusable; return this; } - + public boolean isFocused() { return isFocused; } - + protected synchronized void setFocused(boolean focus) { if (focus != this.isFocused) { dispatchEvent(new FocusEvent(this, focus)); this.isFocused = focus; } } - + public Component takeFocus() { if (isFocused()) { return this; } - + Component comp = this; Component focused = null; - + while (comp != null) { if ((focused = comp.findFocused()) != null) { focused.setFocused(false); setFocused(true); return this; } - + comp = comp.getParent(); } - + setFocused(true); return this; } - + public void focusNext() { Component component = this; - + while (true) { - + component = component.getNextFocusCandidate(true); if (component == this) { return; } - + if (component.isFocusable()) { setFocused(false); component.setFocused(true); return; } - + } } - + private Component getNextFocusCandidate(boolean canUseChildren) { - if (canUseChildren) synchronized (getChildren()) { - if (!getChildren().isEmpty()) { - return getChild(0); + if (canUseChildren) + synchronized (getChildren()) { + if (!getChildren().isEmpty()) { + return getChild(0); + } } - } - + Component parent = getParent(); if (parent != null) { synchronized (parent.getChildren()) { @@ -356,32 +360,32 @@ public class Component extends Named { return parent.getChild(ownIndex + 1); } } - + return parent.getNextFocusCandidate(false); } - + return this; } - + public void focusPrevious() { Component component = this; - + while (true) { - + component = component.getPreviousFocusCandidate(); if (component == this) { return; } - + if (component.isFocusable()) { setFocused(false); component.setFocused(true); return; } - + } } - + private Component getPreviousFocusCandidate() { Component parent = getParent(); if (parent != null) { @@ -391,30 +395,30 @@ public class Component extends Named { return parent.getChild(ownIndex - 1).getLastDeepChild(); } } - + return parent; } - + return getLastDeepChild(); } - + private Component getLastDeepChild() { synchronized (getChildren()) { if (!getChildren().isEmpty()) { return getChild(getChildren().size() - 1).getLastDeepChild(); } - + return this; } } - + public synchronized Component findFocused() { if (isFocused()) { return this; } - + Component result; - + synchronized (getChildren()) { for (Component c : getChildren()) { result = c.findFocused(); @@ -423,10 +427,10 @@ public class Component extends Named { } } } - + return null; } - + public boolean isHovered() { return isHovered; } @@ -434,9 +438,9 @@ public class Component extends Named { protected void setHovered(boolean isHovered) { if (this.isHovered != isHovered) { this.isHovered = isHovered; - + if (!isHovered && !getChildren().isEmpty()) { - + getChildren().forEach(child -> { if (child.isHovered()) { child.setHovered(false); @@ -444,7 +448,7 @@ public class Component extends Named { } }); } - + dispatchEvent(new HoverEvent(this, isHovered)); } } @@ -453,40 +457,36 @@ public class Component extends Named { if (eventBus == null) { eventBus = new EventBus(getName()); } - + eventBus.register(listener); } - + public void removeListener(Object listener) { - if (eventBus == null) return; + if (eventBus == null) + return; eventBus.unregister(listener); } - + public void dispatchEvent(Object event) { - if (eventBus == null) return; + if (eventBus == null) + return; eventBus.post(event); } - - public void addListener( - Class type, - boolean handlesConsumed, - InputListener listener - ) { + + public void addListener(Class type, boolean handlesConsumed, + InputListener listener) { if (inputBus == null) { inputBus = new InputBus(); } - + inputBus.register(type, handlesConsumed, listener); } - public void addListener( - Class type, - InputListener listener - ) { + public void addListener(Class type, InputListener listener) { if (inputBus == null) { inputBus = new InputBus(); } - + inputBus.register(type, listener); } @@ -501,43 +501,45 @@ public class Component extends Named { inputBus.dispatch(input); } } - + public void dispatchInput(Input input) { try { switch (input.getTarget()) { - case FOCUSED: - dispatchInputToFocused(input); - break; - case HOVERED: - dispatchInputToHovered(input); - break; - case ALL: - default: - dispatchInputToAll(input); - break; + case FOCUSED: + dispatchInputToFocused(input); + break; + case HOVERED: + dispatchInputToHovered(input); + break; + case ALL: + default: + dispatchInputToAll(input); + break; } } catch (Exception e) { - throw new RuntimeException(e); + CrashReports.report(e, "Could not dispatch input to Component %s", this); } } private void dispatchInputToFocused(Input input) { Component c = findFocused(); - - if (c == null) return; - if (attemptFocusTransfer(input, c)) return; - + + if (c == null) + return; + if (attemptFocusTransfer(input, c)) + return; + while (c != null) { c.handleInput(input); c = c.getParent(); } } - + private void dispatchInputToHovered(Input input) { getChildren().forEach(child -> { if (child.containsCursor()) { child.setHovered(true); - + if (!input.isConsumed()) { child.dispatchInput(input); } @@ -555,11 +557,13 @@ public class Component extends Named { } private boolean attemptFocusTransfer(Input input, Component focused) { - if (input.isConsumed()) return false; - if (!(input.getEvent() instanceof KeyEvent)) return false; - + if (input.isConsumed()) + return false; + if (!(input.getEvent() instanceof KeyEvent)) + return false; + KeyEvent keyInput = (KeyEvent) input.getEvent(); - + if (keyInput.getKey() == GLFW.GLFW_KEY_TAB && !keyInput.isRelease()) { input.consume(); if (keyInput.hasShift()) { @@ -569,23 +573,18 @@ public class Component extends Named { } return true; } - + return false; } - + public synchronized boolean contains(int x, int y) { - return - x >= getX() && x < getX() + getWidth() && - y >= getY() && y < getY() + getHeight(); + return x >= getX() && x < getX() + getWidth() && y >= getY() && y < getY() + getHeight(); } - + public boolean containsCursor() { - return contains( - (int) InputTracker.getCursorX(), - (int) InputTracker.getCursorY() - ); + return contains((int) InputTracker.getCursorX(), (int) InputTracker.getCursorY()); } - + public void requestReassembly() { if (parent != null) { parent.requestReassembly(); @@ -602,60 +601,60 @@ public class Component extends Named { if (width == 0 || height == 0) { return; } - + if (!isValid()) { validate(); } - + try { assembleSelf(target); } catch (Exception e) { - throw new RuntimeException(e); + CrashReports.report(e, "Could not assemble Component %s", this); } - + assembleChildren(target); - + try { postAssembleSelf(target); } catch (Exception e) { - throw new RuntimeException(e); + CrashReports.report(e, "Post-assembly failed for Component %s", this); } } - + protected void assembleSelf(RenderTarget target) { // To be overridden } - + protected void postAssembleSelf(RenderTarget target) { // To be overridden } - + protected void assembleChildren(RenderTarget target) { getChildren().forEach(child -> child.assemble(target)); } - -// /** -// * Returns a component that displays this component in its center. -// * @return a {@link Aligner} initialized to center this component -// */ -// public Component center() { -// return new Aligner(this); -// } -// -// /** -// * Returns a component that aligns this component. -// * @return a {@link Aligner} initialized with this component -// */ -// public Component align(double x, double y) { -// return new Aligner(this, x, y); -// } -// -// /** -// * Returns a component that allows scrolling this component -// * @return a {@link Scroller} initialized with this component -// */ -// public Component scroller() { -// return new Scroller(this); -// } + + // /** + // * Returns a component that displays this component in its center. + // * @return a {@link Aligner} initialized to center this component + // */ + // public Component center() { + // return new Aligner(this); + // } + // + // /** + // * Returns a component that aligns this component. + // * @return a {@link Aligner} initialized with this component + // */ + // public Component align(double x, double y) { + // return new Aligner(this, x, y); + // } + // + // /** + // * Returns a component that allows scrolling this component + // * @return a {@link Scroller} initialized with this component + // */ + // public Component scroller() { + // return new Scroller(this); + // } } \ No newline at end of file diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/gui/LayerTestGUI.java b/src/main/java/ru/windcorp/progressia/client/graphics/gui/LayerTestGUI.java deleted file mode 100755 index 6a74d96..0000000 --- a/src/main/java/ru/windcorp/progressia/client/graphics/gui/LayerTestGUI.java +++ /dev/null @@ -1,125 +0,0 @@ -/******************************************************************************* - * Progressia - * Copyright (C) 2020 Wind Corporation - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - *******************************************************************************/ -package ru.windcorp.progressia.client.graphics.gui; - -import com.google.common.eventbus.Subscribe; -import glm.vec._2.i.Vec2i; -import org.lwjgl.glfw.GLFW; -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.gui.event.HoverEvent; -import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign; -import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical; -import ru.windcorp.progressia.client.graphics.input.KeyEvent; -import ru.windcorp.progressia.client.localization.Localizer; -import ru.windcorp.progressia.client.localization.MutableString; -import ru.windcorp.progressia.client.localization.MutableStringLocalized; - -public class LayerTestGUI extends GUILayer { - - private static class DebugComponent extends Component { - private final int color; - - public DebugComponent(String name, Vec2i size, int color) { - super(name); - this.color = color; - - setPreferredSize(size); - - addListener(new Object() { - @Subscribe - public void onHoverChanged(HoverEvent e) { - requestReassembly(); - } - }); - - addListener(KeyEvent.class, this::onClicked); - } - - private boolean onClicked(KeyEvent event) { - if (!event.isMouse()) { - return false; - } else if (event.isPress() && event.isLeftMouseButton()) { - System.out.println("You pressed a Component!"); - } - return true; - } - - @Override - protected void assembleSelf(RenderTarget target) { - target.fill(getX(), getY(), getWidth(), getHeight(), Colors.BLACK); - - target.fill( - getX() + 2, getY() + 2, - getWidth() - 4, getHeight() - 4, - isHovered() ? Colors.DEBUG_YELLOW : color - ); - } - } - - public LayerTestGUI() { - super("LayerTestGui", new LayoutAlign(1, 0.75, 5)); - - Panel panel = new Panel("Alex", new LayoutVertical(5)); - - panel.addChild(new DebugComponent("Bravo", new Vec2i(200, 100), 0x44FF44)); - - Component charlie = new DebugComponent("Charlie", null, 0x222222); - charlie.setLayout(new LayoutVertical(5)); - - //Debug - Localizer.getInstance().setLanguage("ru-RU"); - MutableString epsilon = new MutableStringLocalized("Epsilon") - .addListener(() -> ((Label)charlie.getChild(0)).update()).format(34, "thirty-four"); - // These two are swapped in code due to a bug in layouts, fixing ATM - charlie.addChild( - new Label( - "Delta", - new Font().withColor(0xCCBB44).deriveShadow().deriveBold(), - "Пре-альфа!" - ) - ); - charlie.addChild( - new Label( - "Epsilon", - new Font().withColor(0x4444BB).deriveItalic(), - () -> epsilon.get().concat("\u269b") - ) - ); - panel.addChild(charlie); - - - charlie.addListener(KeyEvent.class, e -> { - if(e.isPress() && e.getKey() == GLFW.GLFW_KEY_L) { - Localizer localizer = Localizer.getInstance(); - if (localizer.getLanguage().equals("ru-RU")) { - localizer.setLanguage("en-US"); - } else { - localizer.setLanguage("ru-RU"); - } - return true; - } return false; - }); - charlie.setFocusable(true); - charlie.takeFocus(); - - getRoot().addChild(panel); - } - -} diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/input/KeyMatcher.java b/src/main/java/ru/windcorp/progressia/client/graphics/input/KeyMatcher.java index 4943b96..df6c7a8 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/input/KeyMatcher.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/input/KeyMatcher.java @@ -17,46 +17,24 @@ *******************************************************************************/ package ru.windcorp.progressia.client.graphics.input; -import gnu.trove.set.TIntSet; -import ru.windcorp.progressia.client.graphics.backend.InputTracker; +import java.util.function.Predicate; + +import org.lwjgl.glfw.GLFW; public class KeyMatcher { - + private final int key; - private final int[] additionalKeys; private final int mods; - public KeyMatcher(int key, int[] additionalKeys, int mods) { + protected KeyMatcher(int key, int mods) { this.key = key; - this.additionalKeys = additionalKeys; this.mods = mods; } - - public KeyMatcher(KeyEvent template, int... additionalKeys) { - this(template.getKey(), additionalKeys, template.getMods()); - } - - public static KeyMatcher createKeyMatcher(KeyEvent template) { - return new KeyMatcher( - template, - InputTracker.getPressedKeys().toArray() - ); - } public boolean matches(KeyEvent event) { if (!event.isPress()) return false; if (event.getKey() != getKey()) return false; - if (event.getMods() != getMods()) return false; - - TIntSet pressedKeys = InputTracker.getPressedKeys(); - - if (pressedKeys.size() != additionalKeys.length) return false; - - for (int additionalKey : additionalKeys) { - if (!pressedKeys.contains(additionalKey)) { - return false; - } - } + if ((event.getMods() & getMods()) != getMods()) return false; return true; } @@ -65,12 +43,52 @@ public class KeyMatcher { return key; } - public int[] getAdditionalKeys() { - return additionalKeys; - } - public int getMods() { return mods; } + + public static KeyMatcher.Builder of(int key) { + return new KeyMatcher.Builder(key); + } + + public static class Builder { + + private final int key; + private int mods = 0; + + public Builder(int key) { + this.key = key; + } + + public Builder with(int modifier) { + this.mods += modifier; + return this; + } + + public Builder withShift() { + return with(GLFW.GLFW_MOD_SHIFT); + } + + public Builder withCtrl() { + return with(GLFW.GLFW_MOD_CONTROL); + } + + public Builder withAlt() { + return with(GLFW.GLFW_MOD_ALT); + } + + public Builder withSuper() { + return with(GLFW.GLFW_MOD_SUPER); + } + + public KeyMatcher build() { + return new KeyMatcher(key, mods); + } + + public Predicate matcher() { + return build()::matches; + } + + } } diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/input/Keys.java b/src/main/java/ru/windcorp/progressia/client/graphics/input/Keys.java index c080192..f97c4b0 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/input/Keys.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/input/Keys.java @@ -31,108 +31,101 @@ import gnu.trove.map.hash.TIntObjectHashMap; import gnu.trove.map.hash.TObjectIntHashMap; import gnu.trove.set.TIntSet; import gnu.trove.set.hash.TIntHashSet; +import ru.windcorp.progressia.common.util.crash.CrashReports; public class Keys { - - private static final TIntObjectMap CODES_TO_NAMES = - new TIntObjectHashMap<>(); - - private static final TObjectIntMap NAMES_TO_CODES = - new TObjectIntHashMap<>(); - + + private static final TIntObjectMap CODES_TO_NAMES = new TIntObjectHashMap<>(); + + private static final TObjectIntMap NAMES_TO_CODES = new TObjectIntHashMap<>(); + private static final TIntSet MOUSE_BUTTONS = new TIntHashSet(); - + private static final String KEY_PREFIX = "GLFW_KEY_"; private static final String MOUSE_BUTTON_PREFIX = "GLFW_MOUSE_BUTTON_"; - - private static final Set IGNORE_FIELDS = - new HashSet<>(Arrays.asList( - "GLFW_KEY_UNKNOWN", - "GLFW_KEY_LAST", - "GLFW_MOUSE_BUTTON_LAST", - "GLFW_MOUSE_BUTTON_1", // Alias for LEFT + + private static final Set IGNORE_FIELDS = new HashSet<>( + Arrays.asList("GLFW_KEY_UNKNOWN", "GLFW_KEY_LAST", "GLFW_MOUSE_BUTTON_LAST", "GLFW_MOUSE_BUTTON_1", // Alias + // for + // LEFT "GLFW_MOUSE_BUTTON_2", // Alias for RIGHT - "GLFW_MOUSE_BUTTON_3" // Alias for MIDDLE + "GLFW_MOUSE_BUTTON_3" // Alias for MIDDLE )); - + static { initializeDictionary(); } private static void initializeDictionary() { try { - + for (Field field : GLFW.class.getFields()) { - if (!Modifier.isStatic(field.getModifiers())) continue; - if (!Modifier.isFinal(field.getModifiers())) continue; - + if (!Modifier.isStatic(field.getModifiers())) + continue; + if (!Modifier.isFinal(field.getModifiers())) + continue; + String name = field.getName(); - - if ( - !name.startsWith(KEY_PREFIX) && - !name.startsWith(MOUSE_BUTTON_PREFIX) - ) continue; - - if (IGNORE_FIELDS.contains(name)) continue; - + + if (!name.startsWith(KEY_PREFIX) && !name.startsWith(MOUSE_BUTTON_PREFIX)) + continue; + + if (IGNORE_FIELDS.contains(name)) + continue; + addToDictionary(field); } - + } catch (IllegalAccessException e) { - throw new RuntimeException(e); + CrashReports.report(e, "Cannot access GLFW constants"); } } - private static void addToDictionary(Field field) - throws IllegalAccessException { - + private static void addToDictionary(Field field) throws IllegalAccessException { + int value = field.getInt(null); String name = field.getName(); - + if (name.startsWith(KEY_PREFIX)) { name = name.substring(KEY_PREFIX.length()); } else if (name.startsWith(MOUSE_BUTTON_PREFIX)) { name = "MOUSE_" + name.substring(MOUSE_BUTTON_PREFIX.length()); MOUSE_BUTTONS.add(value); } - + if (CODES_TO_NAMES.containsKey(value)) { - throw new RuntimeException( - "Duplicate keys: " + CODES_TO_NAMES.get(value) + - " and " + name + " both map to " + value + - "(0x" + Integer.toHexString(value) + ")" - ); + CrashReports.report(null, "Duplicate keys: %s and %s both map to %d(0x%s)", + CODES_TO_NAMES.get(value), name, value, Integer.toHexString(value)); } - + CODES_TO_NAMES.put(value, name); NAMES_TO_CODES.put(name, value); } - + public static String getInternalName(int code) { String result = CODES_TO_NAMES.get(code); - + if (result == null) { return "UNKNOWN"; } - + return result; } - + public static String getDisplayName(int code) { String name = getInternalName(code); - + if (name.startsWith("KP_")) { name = "KEYPAD_" + name.substring("KP_".length()); } - - name = Character.toTitleCase(name.charAt(0)) + - name.substring(1).toLowerCase(); - + + name = Character.toTitleCase(name.charAt(0)) + name.substring(1).toLowerCase(); + name = name.replace('_', ' '); - + return name; } - + public static int getCode(String internalName) { if (NAMES_TO_CODES.containsKey(internalName)) { return -1; @@ -140,7 +133,7 @@ public class Keys { return NAMES_TO_CODES.get(internalName); } } - + public static boolean isMouse(int code) { return MOUSE_BUTTONS.contains(code); } diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/model/LambdaModel.java b/src/main/java/ru/windcorp/progressia/client/graphics/model/LambdaModel.java index c6799c5..00d47b1 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/model/LambdaModel.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/model/LambdaModel.java @@ -121,5 +121,14 @@ public class LambdaModel extends DynamicModel { } } + + public static LambdaModel animate(Renderable model, TransformGetter transform) { + return new LambdaModel( + new Renderable[] { model }, + new Mat4[] { new Mat4() }, + new boolean[] { true }, + new TransformGetter[] { transform } + ); + } } diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/texture/Atlases.java b/src/main/java/ru/windcorp/progressia/client/graphics/texture/Atlases.java index 0d7c435..0ce64d4 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/texture/Atlases.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/texture/Atlases.java @@ -13,72 +13,67 @@ import ru.windcorp.progressia.client.graphics.backend.RenderTaskQueue; import ru.windcorp.progressia.common.resource.Resource; import ru.windcorp.progressia.common.util.BinUtil; import ru.windcorp.progressia.common.util.Named; +import ru.windcorp.progressia.common.util.crash.CrashReports; public class Atlases { - + public static class AtlasGroup extends Named { private final int atlasSize; - + public AtlasGroup(String name, int atlasSize) { super(name); this.atlasSize = atlasSize; - + if (!BinUtil.isPowerOf2(atlasSize)) { - throw new IllegalArgumentException( - "Atlas size " + atlasSize - + " is not a power of 2" - ); + throw new IllegalArgumentException("Atlas size " + atlasSize + " is not a power of 2"); } } - + public int getAtlasSize() { return atlasSize; } - + @Override public int hashCode() { return super.hashCode(); } - + @Override public boolean equals(Object obj) { return super.equals(obj); } } - + public static class Atlas { private final AtlasGroup group; - + private final TextureDataEditor editor; private int nextX, nextY; private int rowHeight; - + private final TexturePrimitive primitive; - + public Atlas(AtlasGroup group) { this.group = group; int size = group.getAtlasSize(); - + this.editor = new TextureDataEditor(size, size, size, size, SETTINGS); this.primitive = new TexturePrimitive(editor.getData()); } - + public Sprite addSprite(TextureData data) { int width = data.getContentWidth(); int height = data.getContentHeight(); - + selectPosition(width, height); - + editor.draw(data, nextX, nextY); - - Sprite result = new Sprite( - getPrimitive(), - toPrimitiveCoords(nextX, nextY), - toPrimitiveCoords(width, height) - ); - + + Sprite result = new Sprite(getPrimitive(), toPrimitiveCoords(nextX, nextY), + toPrimitiveCoords(width, height)); + nextX += width; - + return result; } @@ -87,10 +82,10 @@ public class Atlases { // Wrapping nextY += rowHeight; // Move to next row rowHeight = height; // Next row is at least 'height' high - nextX = 0; // Start the row over + nextX = 0; // Start the row over } else { // Not wrapping - + // Update rowHeight if necessary if (rowHeight < height) { rowHeight = height; @@ -99,10 +94,7 @@ public class Atlases { } private Vec2 toPrimitiveCoords(int x, int y) { - return new Vec2( - toPrimitiveCoord(x), - toPrimitiveCoord(y) - ); + return new Vec2(toPrimitiveCoord(x), toPrimitiveCoord(y)); } private float toPrimitiveCoord(int c) { @@ -112,87 +104,82 @@ public class Atlases { public boolean canAddSprite(TextureData data) { int width = data.getContentWidth(); int height = data.getContentHeight(); - + // Try to fit without wrapping - + if (nextY + height > getSize()) // Does not fit vertically return false; - + if (nextX + width <= getSize()) // Can place at (nextX; nextY) return true; - + // Try wrapping - + if (width > getSize()) // GTFO. We couldn't fit if if we tried return false; - + if (nextY + rowHeight + height > getSize()) // Does not fit vertically when wrapped return false; - + // Can place at (0; nextY + rowHeight) return true; } - + public AtlasGroup getGroup() { return group; } - + public TexturePrimitive getPrimitive() { return primitive; } - + public int getSize() { return editor.getBufferWidth(); } } - + private static final TextureSettings SETTINGS = new TextureSettings(false); - - private static final Map LOADED = - new HashMap<>(); - private static final Multimap ATLASES = - MultimapBuilder.hashKeys().arrayListValues().build(); - + + private static final Map LOADED = new HashMap<>(); + private static final Multimap ATLASES = MultimapBuilder.hashKeys().arrayListValues().build(); + public static Sprite getSprite(Resource resource, AtlasGroup group) { return LOADED.computeIfAbsent(resource, k -> loadSprite(k, group)); } - + private static Sprite loadSprite(Resource resource, AtlasGroup group) { try { - TextureDataEditor data = - TextureLoader.loadPixels(resource, SETTINGS); - + TextureDataEditor data = TextureLoader.loadPixels(resource, SETTINGS); + return loadSprite(data.getData(), group); } catch (IOException e) { - throw new RuntimeException(e); + CrashReports.report(e, "Could not load sprite %s into atlas group %s", resource, group); + return null; } } - + public static Sprite loadSprite(TextureData data, AtlasGroup group) { Atlas atlas = getReadyAtlas(group, data); return atlas.addSprite(data); } - + private static Atlas getReadyAtlas(AtlasGroup group, TextureData data) { List atlases = (List) ATLASES.get(group); - - if ( - atlases.isEmpty() || - !(atlases.get(atlases.size() - 1).canAddSprite(data)) - ) { + + if (atlases.isEmpty() || !(atlases.get(atlases.size() - 1).canAddSprite(data))) { Atlas newAtlas = new Atlas(group); - + if (!newAtlas.canAddSprite(data)) { - throw new RuntimeException("Could not fit tex into atlas of size " + newAtlas.getSize()); + CrashReports.report(null, "Could not fit texture into atlas of size %d", newAtlas.getSize()); } - + atlases.add(newAtlas); } - + return atlases.get(atlases.size() - 1); } @@ -201,7 +188,8 @@ public class Atlases { RenderTaskQueue.invokeLater(atlas.getPrimitive()::load); }); } - - private Atlases() {} + + private Atlases() { + } } diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/texture/ComplexTexture.java b/src/main/java/ru/windcorp/progressia/client/graphics/texture/ComplexTexture.java index d8f4c18..8aaf4d4 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/texture/ComplexTexture.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/texture/ComplexTexture.java @@ -19,10 +19,10 @@ public class ComplexTexture { this.primitive = primitive; this.assumedWidth = abstractWidth - * primitive.getWidth() / (float) primitive.getBufferWidth(); + / (float) primitive.getWidth() * primitive.getBufferWidth(); this.assumedHeight = abstractHeight - * primitive.getHeight() / (float) primitive.getBufferHeight(); + / (float) primitive.getHeight() * primitive.getBufferHeight(); } public Texture get(int x, int y, int width, int height) { diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/texture/SimpleTextures.java b/src/main/java/ru/windcorp/progressia/client/graphics/texture/SimpleTextures.java index e453ff7..51a1235 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/texture/SimpleTextures.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/texture/SimpleTextures.java @@ -6,21 +6,22 @@ import java.util.Map; import ru.windcorp.progressia.common.resource.Resource; import ru.windcorp.progressia.common.resource.ResourceManager; +import ru.windcorp.progressia.common.util.crash.CrashReports; public class SimpleTextures { - + private static final TextureSettings SETTINGS = new TextureSettings(false); - + private static final Map TEXTURES = new HashMap<>(); - + public static Texture get(Resource resource) { return TEXTURES.computeIfAbsent(resource, SimpleTextures::load); } - + public static Texture get(String textureName) { return get(ResourceManager.getTextureResource(textureName)); } - + private static Texture load(Resource resource) { try { TextureDataEditor data = @@ -30,9 +31,10 @@ public class SimpleTextures { new Sprite(new TexturePrimitive(data.getData())) ); } catch (IOException e) { - throw new RuntimeException(e); + CrashReports.report(e, "Could not load texture %s", resource); + return null; } - + } private SimpleTextures() {} diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/texture/TexturePrimitive.java b/src/main/java/ru/windcorp/progressia/client/graphics/texture/TexturePrimitive.java index c59405e..b4df4fb 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/texture/TexturePrimitive.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/texture/TexturePrimitive.java @@ -24,27 +24,28 @@ import java.util.Arrays; import ru.windcorp.progressia.client.graphics.backend.OpenGLObjectTracker; import ru.windcorp.progressia.client.graphics.backend.OpenGLObjectTracker.OpenGLDeletable; +import ru.windcorp.progressia.common.util.crash.CrashReports; public class TexturePrimitive implements OpenGLDeletable { - + private static final int NOT_LOADED = -1; - + private static int[] currentlyBound = new int[32]; static { Arrays.fill(currentlyBound, NOT_LOADED); } - + private int handle = NOT_LOADED; private TextureData pixels; public TexturePrimitive(TextureData pixels) { this.pixels = pixels; } - + public TextureData getData() { return pixels; } - + public int getBufferWidth() { return pixels.getBufferWidth(); } @@ -64,21 +65,21 @@ public class TexturePrimitive implements OpenGLDeletable { public boolean isLoaded() { return handle != NOT_LOADED; } - + public void bind(int slot) { if (!isLoaded()) { load(); } - + if (currentlyBound[slot] == handle) { return; } - + int code = GL_TEXTURE0 + slot; - + glActiveTexture(code); glBindTexture(GL_TEXTURE_2D, handle); - + currentlyBound[slot] = handle; } @@ -87,9 +88,9 @@ public class TexturePrimitive implements OpenGLDeletable { handle = pixels.load(); OpenGLObjectTracker.register(this); - + if (handle < 0) { - throw new RuntimeException("oops"); + CrashReports.report(null, "Failed to allocate texture"); } } diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/world/Camera.java b/src/main/java/ru/windcorp/progressia/client/graphics/world/Camera.java index ab99777..56748f9 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/world/Camera.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/world/Camera.java @@ -271,6 +271,10 @@ public class Camera { currentModeIndex++; } } + + public int getCurrentModeIndex() { + return currentModeIndex; + } public float getLastAnchorYaw() { return lastAnchorYaw; diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/world/LayerWorld.java b/src/main/java/ru/windcorp/progressia/client/graphics/world/LayerWorld.java index 114e1ed..1533246 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/world/LayerWorld.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/world/LayerWorld.java @@ -20,45 +20,36 @@ package ru.windcorp.progressia.client.graphics.world; import java.util.ArrayList; import java.util.List; -import org.lwjgl.glfw.GLFW; - -import glm.Glm; -import glm.mat._3.Mat3; -import glm.vec._2.Vec2; +import glm.mat._4.Mat4; import glm.vec._3.Vec3; +import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.client.Client; +import ru.windcorp.progressia.client.ClientState; import ru.windcorp.progressia.client.comms.controls.InputBasedControls; import ru.windcorp.progressia.client.graphics.Layer; import ru.windcorp.progressia.client.graphics.backend.FaceCulling; -import ru.windcorp.progressia.client.graphics.backend.GraphicsBackend; import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface; -import ru.windcorp.progressia.client.graphics.input.CursorMoveEvent; -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.common.collision.AABB; +import ru.windcorp.progressia.client.graphics.model.Renderable; +import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram; +import ru.windcorp.progressia.client.graphics.model.Shapes.PppBuilder; +import ru.windcorp.progressia.client.graphics.model.StaticModel; +import ru.windcorp.progressia.client.graphics.texture.Texture; +import ru.windcorp.progressia.common.Units; import ru.windcorp.progressia.common.collision.Collideable; -import ru.windcorp.progressia.common.collision.CollisionClock; -import ru.windcorp.progressia.common.collision.CollisionModel; -import ru.windcorp.progressia.common.collision.CompoundCollisionModel; import ru.windcorp.progressia.common.collision.colliders.Collider; import ru.windcorp.progressia.common.util.FloatMathUtils; -import ru.windcorp.progressia.common.util.Vectors; import ru.windcorp.progressia.common.world.entity.EntityData; -import ru.windcorp.progressia.test.AABBRenderer; +import ru.windcorp.progressia.test.CollisionModelRenderer; +import ru.windcorp.progressia.test.TestPlayerControls; public class LayerWorld extends Layer { - private final Mat3 angMat = new Mat3(); - - private int movementForward = 0; - private int movementRight = 0; - private int movementUp = 0; - private final WorldRenderHelper helper = new WorldRenderHelper(); private final Client client; private final InputBasedControls inputBasedControls; + private final TestPlayerControls tmp_testControls = TestPlayerControls.getInstance(); public LayerWorld(Client client) { super("World"); @@ -79,38 +70,14 @@ public class LayerWorld extends Layer { @Override protected void doRender() { - if (client.getLocalPlayer() != null) { - tmp_handleControls(); - } - Camera camera = client.getCamera(); if (camera.hasAnchor()) { renderWorld(); } - } - - private void tmp_handleControls() { - EntityData player = client.getLocalPlayer(); - angMat.identity().rotateZ(player.getYaw()); - - Vec3 movement = Vectors.grab3(); - - // Horizontal and vertical max control speed - final float movementSpeed = 0.1f * 60.0f; - // (0; 1], 1 is instant change, 0 is no control authority - final float controlAuthority = 0.1f; - - movement.set(movementForward, -movementRight, 0); - if (movementForward != 0 && movementRight != 0) movement.normalize(); - angMat.mul_(movement); // bug in jglm, .mul() and mul_() are swapped - movement.z = movementUp; - movement.mul(movementSpeed); - movement.sub(player.getVelocity()); - movement.mul(controlAuthority); - player.getVelocity().add(movement); - - Vectors.release(movement); + if (client.getLocalPlayer() != null) { + client.getLocalPlayer().update(client.getWorld()); + } } private void renderWorld() { @@ -128,150 +95,117 @@ public class LayerWorld extends Layer { private final Collider.ColliderWorkspace tmp_colliderWorkspace = new Collider.ColliderWorkspace(); private final List tmp_collideableList = new ArrayList<>(); - private static final boolean RENDER_AABBS = true; + private static final boolean RENDER_COLLISION_MODELS = false; private void tmp_doEveryFrame() { + float tickLength = (float) GraphicsInterface.getFrameLength(); + try { - if (RENDER_AABBS) { - for (EntityData data : this.client.getWorld().getData().getEntities()) { - CollisionModel model = data.getCollisionModel(); - if (model instanceof AABB) { - AABBRenderer.renderAABB((AABB) model, helper); - } else if (model instanceof CompoundCollisionModel) { - AABBRenderer.renderAABBsInCompound((CompoundCollisionModel) model, helper); - } - } - } + tmp_performCollisions(tickLength); + tmp_drawSelectionBox(); - tmp_collideableList.clear(); - tmp_collideableList.addAll(this.client.getWorld().getData().getEntities()); - - Collider.performCollisions( - tmp_collideableList, - new CollisionClock() { - private float t = 0; - @Override - public float getTime() { - return t; - } - - @Override - public void advanceTime(float change) { - t += change; - } - }, - (float) GraphicsInterface.getFrameLength(), - tmp_colliderWorkspace - ); - - final float frictionCoeff = 1 - 1e-2f; + tmp_testControls.applyPlayerControls(); for (EntityData data : this.client.getWorld().getData().getEntities()) { - data.getVelocity().mul(frictionCoeff); + tmp_applyFriction(data); + tmp_applyGravity(data, tickLength); + tmp_renderCollisionModel(data); } - } catch (Exception e) { + } catch (Throwable e) { + e.printStackTrace(); + System.err.println("OLEGSHA is to blame. Tell him he vry stupiDD!!"); System.exit(31337); } } + private void tmp_renderCollisionModel(EntityData entity) { + if (RENDER_COLLISION_MODELS) { + CollisionModelRenderer.renderCollisionModel(entity.getCollisionModel(), helper); + } + } + + private void tmp_performCollisions(float tickLength) { + tmp_collideableList.clear(); + tmp_collideableList.addAll(this.client.getWorld().getData().getEntities()); + + Collider.performCollisions( + tmp_collideableList, + this.client.getWorld().getData(), + tickLength, + tmp_colliderWorkspace + ); + } + + private static final Renderable SELECTION_BOX = tmp_createSelectionBox(); + + private void tmp_drawSelectionBox() { + LocalPlayer player = client.getLocalPlayer(); + if (player == null) return; + + Vec3i selection = player.getSelection().getBlock(); + if (selection == null) return; + + helper.pushTransform().translate(selection.x, selection.y, selection.z); + SELECTION_BOX.render(helper); + helper.popTransform(); + } + + private static Renderable tmp_createSelectionBox() { + StaticModel.Builder b = StaticModel.builder(); + ShapeRenderProgram p = WorldRenderProgram.getDefault(); + + final float f = 1e-2f; + final float scale = 1 - f/2; + final Vec3 color = new Vec3(1, 1, 1).mul(0); + + for (float phi = 0; phi < 2*FloatMathUtils.PI_F; phi += FloatMathUtils.PI_F/2) { + Mat4 rot = new Mat4().identity().rotateZ(phi).scale(scale); + + b.addPart(new PppBuilder(p, (Texture) null).setOrigin( + new Vec3(-f - 0.5f, -f - 0.5f, -f - 0.5f) + ).setSize(f, f, 2*f + 1).setColorMultiplier(color).create(), rot); + + b.addPart(new PppBuilder(p, (Texture) null).setOrigin( + new Vec3(-f - 0.5f, - 0.5f, -f - 0.5f) + ).setSize(f, 1, f).setColorMultiplier(color).create(), rot); + + b.addPart(new PppBuilder(p, (Texture) null).setOrigin( + new Vec3(-f - 0.5f, - 0.5f, + 0.5f) + ).setSize(f, 1, f).setColorMultiplier(color).create(), rot); + } + + return new StaticModel(b); + } + + private void tmp_applyFriction(EntityData entity) { + final float frictionCoeff = 1 - 1e-5f; + entity.getVelocity().mul(frictionCoeff); + } + + private void tmp_applyGravity(EntityData entity, float tickLength) { + if (ClientState.getInstance().getLocalPlayer().getEntity() == entity && tmp_testControls.isFlying()) { + return; + } + + final float gravitationalAcceleration; + + if (tmp_testControls.useMinecraftGravity()) { + gravitationalAcceleration = 32f * Units.METERS_PER_SECOND_SQUARED; // plz dont sue me M$ + } else { + gravitationalAcceleration = 9.81f * Units.METERS_PER_SECOND_SQUARED; + } + entity.getVelocity().add(0, 0, -gravitationalAcceleration * tickLength); + } + @Override protected void handleInput(Input input) { if (input.isConsumed()) return; - InputEvent event = input.getEvent(); - - if (event instanceof KeyEvent) { - if (onKeyEvent((KeyEvent) event)) { - input.consume(); - } - } else if (event instanceof CursorMoveEvent) { - onMouseMoved((CursorMoveEvent) event); - input.consume(); - } + tmp_testControls.handleInput(input); if (!input.isConsumed()) { inputBasedControls.handleInput(input); } } - - private boolean flag = true; - - private boolean onKeyEvent(KeyEvent event) { - if (event.isRepeat()) return false; - - int multiplier = event.isPress() ? 1 : -1; - - switch (event.getKey()) { - case GLFW.GLFW_KEY_W: - movementForward += +1 * multiplier; - break; - case GLFW.GLFW_KEY_S: - movementForward += -1 * multiplier; - break; - case GLFW.GLFW_KEY_A: - movementRight += -1 * multiplier; - break; - case GLFW.GLFW_KEY_D: - movementRight += +1 * multiplier; - break; - case GLFW.GLFW_KEY_SPACE: - movementUp += +1 * multiplier; - break; - case GLFW.GLFW_KEY_LEFT_SHIFT: - movementUp += -1 * multiplier; - break; - - case GLFW.GLFW_KEY_ESCAPE: - if (!event.isPress()) return false; - - if (flag) { - GLFW.glfwSetInputMode(GraphicsBackend.getWindowHandle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_NORMAL); - } else { - GLFW.glfwSetInputMode(GraphicsBackend.getWindowHandle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED); - } - - flag = !flag; - break; - - case GLFW.GLFW_KEY_F5: - if (!event.isPress()) return false; - - if (client.getCamera().hasAnchor()) { - client.getCamera().selectNextMode(); - } - break; - - default: - return false; - } - - return true; - } - - private void onMouseMoved(CursorMoveEvent event) { - if (!flag) return; - - final float yawScale = -0.002f; - final float pitchScale = yawScale; - - EntityData player = client.getLocalPlayer(); - - if (player != null) { - normalizeAngles(player.getDirection().add( - (float) (event.getChangeX() * yawScale), - (float) (event.getChangeY() * pitchScale) - )); - } - } - - private void normalizeAngles(Vec2 dir) { - // Normalize yaw - dir.x = FloatMathUtils.normalizeAngle(dir.x); - - // Clamp pitch - dir.y = Glm.clamp( - dir.y, -FloatMathUtils.PI_F/2, +FloatMathUtils.PI_F/2 - ); - } } diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/world/LocalPlayer.java b/src/main/java/ru/windcorp/progressia/client/graphics/world/LocalPlayer.java new file mode 100644 index 0000000..9179bc4 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/client/graphics/world/LocalPlayer.java @@ -0,0 +1,28 @@ +package ru.windcorp.progressia.client.graphics.world; + +import ru.windcorp.progressia.client.world.WorldRender; +import ru.windcorp.progressia.client.world.entity.EntityRenderable; +import ru.windcorp.progressia.common.world.Player; +import ru.windcorp.progressia.common.world.entity.EntityData; + +public class LocalPlayer extends Player { + + private final Selection selection = new Selection(); + + public LocalPlayer(EntityData entity) { + super(entity); + } + + public Selection getSelection() { + return selection; + } + + public void update(WorldRender world) { + getSelection().update(world, getEntity()); + } + + public EntityRenderable getRenderable(WorldRender world) { + return world.getEntityRenderable(getEntity()); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/world/Selection.java b/src/main/java/ru/windcorp/progressia/client/graphics/world/Selection.java new file mode 100644 index 0000000..3627e3c --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/client/graphics/world/Selection.java @@ -0,0 +1,72 @@ +package ru.windcorp.progressia.client.graphics.world; + +import glm.vec._2.Vec2; +import glm.vec._3.Vec3; +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.client.world.WorldRender; +import ru.windcorp.progressia.common.util.Vectors; +import ru.windcorp.progressia.common.world.BlockRay; +import ru.windcorp.progressia.common.world.block.BlockFace; +import ru.windcorp.progressia.common.world.entity.EntityData; + +public class Selection { + + private final Vec3i block = new Vec3i(); + private BlockFace surface = null; + private final Vec2 pointOnSurface = new Vec2(0.5f, 0.5f); + private final Vec3 point = new Vec3(); + + private boolean exists = false; + + private BlockRay ray = new BlockRay(); + + public void update(WorldRender world, EntityData player) { + Vec3 direction = Vectors.grab3(); + Vec3 start = Vectors.grab3(); + + player.getLookingAtVector(direction); + world.getEntityRenderable(player).getViewPoint(start); + start.add(player.getPosition()); + + exists = false; + + for (ray.start(start, direction); ray.getDistance() < 6; ray.next()) { + Vec3i blockInWorld = ray.current(); + + if (world.getData().getCollisionModelOfBlock(blockInWorld) != null) { + exists = true; + block.set(blockInWorld.x, blockInWorld.y, blockInWorld.z); + ray.getPoint(point); + surface = ray.getCurrentFace(); + // TODO selectedPointOnSurface + break; + } + } + + ray.end(); + + Vectors.release(direction); + Vectors.release(start); + } + + public Vec3i getBlock() { + return exists ? block : null; + } + + public Vec3 getPoint() { + return exists ? point : null; + } + + public BlockFace getSurface() { + return exists ? surface : null; + } + + public Vec2 getPointOnSurface() { + return exists ? pointOnSurface : null; + } + + public boolean exists() { + return exists; + } + +} diff --git a/src/main/java/ru/windcorp/progressia/client/localization/Localizer.java b/src/main/java/ru/windcorp/progressia/client/localization/Localizer.java index 413ddab..16f7b05 100644 --- a/src/main/java/ru/windcorp/progressia/client/localization/Localizer.java +++ b/src/main/java/ru/windcorp/progressia/client/localization/Localizer.java @@ -3,6 +3,8 @@ package ru.windcorp.progressia.client.localization; import java.lang.ref.WeakReference; import java.util.*; +import ru.windcorp.progressia.common.util.crash.CrashReports; + public class Localizer { private static final Localizer INSTANCE = new Localizer("assets/languages/", "en-US"); @@ -17,7 +19,7 @@ public class Localizer { private final Collection> listeners = Collections.synchronizedCollection(new LinkedList<>()); - //lang list must be in the same folder as .lang files + // lang list must be in the same folder as .lang files public Localizer(String langFolder) { this.langFolder = langFolder; this.langList = new Parser(langFolder + "lang_list.txt").parse(); @@ -41,7 +43,7 @@ public class Localizer { data = new Parser(langFolder + this.language + ".lang").parse(); pokeListeners(language); } else { - throw new RuntimeException("Language not found: " + language); + CrashReports.report(null, "Language not found: %s", language); } } @@ -64,7 +66,7 @@ public class Localizer { } private void pokeListeners(String newLanguage) { - //TODO extract as weak bus listener class + // TODO extract as weak bus listener class synchronized (listeners) { Iterator> iterator = listeners.iterator(); while (iterator.hasNext()) { diff --git a/src/main/java/ru/windcorp/progressia/client/localization/Parser.java b/src/main/java/ru/windcorp/progressia/client/localization/Parser.java index cfa6022..239c779 100644 --- a/src/main/java/ru/windcorp/progressia/client/localization/Parser.java +++ b/src/main/java/ru/windcorp/progressia/client/localization/Parser.java @@ -3,6 +3,7 @@ package ru.windcorp.progressia.client.localization; import ru.windcorp.jputil.chars.EscapeException; import ru.windcorp.jputil.chars.Escaper; import ru.windcorp.progressia.common.resource.ResourceManager; +import ru.windcorp.progressia.common.util.crash.CrashReports; import java.io.IOException; import java.io.Reader; @@ -54,7 +55,7 @@ public class Parser { if (c == '=') { String key = ESCAPER.escape(stringBuilder.toString()); stringBuilder.setLength(0); - rawData.read(); //skip a char + rawData.read(); // skip a char while (true) { code = rawData.read(); if (code == -1) { @@ -80,7 +81,8 @@ public class Parser { } } catch (IOException | EscapeException e) { - throw new RuntimeException(e); + CrashReports.report(e, "Could not parse language file %s", filePath); + return null; } return parsedData; } diff --git a/src/main/java/ru/windcorp/progressia/client/world/ChunkUpdateListener.java b/src/main/java/ru/windcorp/progressia/client/world/ChunkUpdateListener.java new file mode 100644 index 0000000..d5fd908 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/client/world/ChunkUpdateListener.java @@ -0,0 +1,19 @@ +package ru.windcorp.progressia.client.world; + +import ru.windcorp.progressia.common.world.ChunkData; +import ru.windcorp.progressia.common.world.ChunkDataListener; + +class ChunkUpdateListener implements ChunkDataListener { + + private final WorldRender world; + + public ChunkUpdateListener(WorldRender world) { + this.world = world; + } + + @Override + public void onChunkChanged(ChunkData chunk) { + world.getChunk(chunk).markForUpdate(); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/client/world/WorldRender.java b/src/main/java/ru/windcorp/progressia/client/world/WorldRender.java index fc219f4..49b8272 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/WorldRender.java +++ b/src/main/java/ru/windcorp/progressia/client/world/WorldRender.java @@ -29,7 +29,9 @@ import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper; import ru.windcorp.progressia.client.world.entity.EntityRenderRegistry; import ru.windcorp.progressia.client.world.entity.EntityRenderable; import ru.windcorp.progressia.common.world.ChunkData; +import ru.windcorp.progressia.common.world.ChunkDataListeners; import ru.windcorp.progressia.common.world.WorldData; +import ru.windcorp.progressia.common.world.WorldDataListener; import ru.windcorp.progressia.common.world.entity.EntityData; public class WorldRender { @@ -43,9 +45,18 @@ public class WorldRender { public WorldRender(WorldData data) { this.data = data; - for (ChunkData chunkData : data.getChunks()) { - chunks.put(chunkData, new ChunkRender(this, chunkData)); - } + data.addListener(ChunkDataListeners.createAdder(new ChunkUpdateListener(this))); + data.addListener(new WorldDataListener() { + @Override + public void onChunkLoaded(WorldData world, ChunkData chunk) { + chunks.put(chunk, new ChunkRender(WorldRender.this, chunk)); + } + + @Override + public void beforeChunkUnloaded(WorldData world, ChunkData chunk) { + chunks.remove(chunk); + } + }); } public WorldData getData() { diff --git a/src/main/java/ru/windcorp/progressia/client/world/block/BlockRender.java b/src/main/java/ru/windcorp/progressia/client/world/block/BlockRender.java index 4f39b05..b7e8de5 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/block/BlockRender.java +++ b/src/main/java/ru/windcorp/progressia/client/world/block/BlockRender.java @@ -18,13 +18,13 @@ 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.client.graphics.model.Renderable; -import ru.windcorp.progressia.common.util.Namespaced; public abstract class BlockRender extends Namespaced { - public BlockRender(String namespace, String name) { - super(namespace, name); + public BlockRender(String id) { + super(id); } public void render(ShapeRenderHelper renderer) { diff --git a/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderNone.java b/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderNone.java index 590ec31..24db070 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderNone.java +++ b/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderNone.java @@ -22,8 +22,8 @@ import ru.windcorp.progressia.client.graphics.model.Renderable; public class BlockRenderNone extends BlockRender { - public BlockRenderNone(String namespace, String name) { - super(namespace, name); + public BlockRenderNone(String id) { + super(id); } @Override diff --git a/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderOpaqueCube.java b/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderOpaqueCube.java index 984f5f2..4f31089 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderOpaqueCube.java +++ b/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderOpaqueCube.java @@ -23,25 +23,22 @@ import ru.windcorp.progressia.common.world.block.BlockFace; public class BlockRenderOpaqueCube extends BlockRenderTexturedCube { public BlockRenderOpaqueCube( - String namespace, String name, + String id, Texture topTexture, Texture bottomTexture, Texture northTexture, Texture southTexture, Texture eastTexture, Texture westTexture ) { super( - namespace, name, + id, topTexture, bottomTexture, northTexture, southTexture, eastTexture, westTexture ); } - public BlockRenderOpaqueCube( - String namespace, String name, - Texture texture - ) { + public BlockRenderOpaqueCube(String id, Texture texture) { this( - namespace, name, + id, texture, texture, texture, texture, texture, texture diff --git a/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderRegistry.java b/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderRegistry.java index 869ff09..7392986 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderRegistry.java +++ b/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderRegistry.java @@ -22,9 +22,9 @@ import ru.windcorp.progressia.client.graphics.texture.Atlases.AtlasGroup; import ru.windcorp.progressia.client.graphics.texture.SimpleTexture; import ru.windcorp.progressia.client.graphics.texture.Texture; import ru.windcorp.progressia.common.resource.ResourceManager; -import ru.windcorp.progressia.common.util.NamespacedRegistry; +import ru.windcorp.progressia.common.util.namespaces.NamespacedInstanceRegistry; -public class BlockRenderRegistry extends NamespacedRegistry { +public class BlockRenderRegistry extends NamespacedInstanceRegistry { private static final BlockRenderRegistry INSTANCE = new BlockRenderRegistry(); diff --git a/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderTexturedCube.java b/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderTexturedCube.java index 7826652..649ad2f 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderTexturedCube.java +++ b/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderTexturedCube.java @@ -36,12 +36,12 @@ implements OpaqueCube { private final Map textures = new HashMap<>(); public BlockRenderTexturedCube( - String namespace, String name, + String id, Texture topTexture, Texture bottomTexture, Texture northTexture, Texture southTexture, Texture eastTexture, Texture westTexture ) { - super(namespace, name); + super(id); textures.put(TOP, topTexture); textures.put(BOTTOM, bottomTexture); diff --git a/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderTransparentCube.java b/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderTransparentCube.java index e369fdb..a478d2c 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderTransparentCube.java +++ b/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderTransparentCube.java @@ -23,25 +23,22 @@ import ru.windcorp.progressia.common.world.block.BlockFace; public class BlockRenderTransparentCube extends BlockRenderTexturedCube { public BlockRenderTransparentCube( - String namespace, String name, + String id, Texture topTexture, Texture bottomTexture, Texture northTexture, Texture southTexture, Texture eastTexture, Texture westTexture ) { super( - namespace, name, + id, topTexture, bottomTexture, northTexture, southTexture, eastTexture, westTexture ); } - public BlockRenderTransparentCube( - String namespace, String name, - Texture texture - ) { + public BlockRenderTransparentCube(String id, Texture texture) { this( - namespace, name, + id, texture, texture, texture, texture, texture, texture diff --git a/src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizerSupplier.java b/src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizerSupplier.java index a074c9f..702282c 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizerSupplier.java +++ b/src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizerSupplier.java @@ -19,21 +19,21 @@ package ru.windcorp.progressia.client.world.cro; import com.google.common.base.Supplier; -import ru.windcorp.progressia.common.util.Namespaced; +import ru.windcorp.progressia.common.util.namespaces.Namespaced; public abstract class ChunkRenderOptimizerSupplier extends Namespaced { - public ChunkRenderOptimizerSupplier(String namespace, String name) { - super(namespace, name); + public ChunkRenderOptimizerSupplier(String id) { + super(id); } public abstract ChunkRenderOptimizer createOptimizer(); public static ChunkRenderOptimizerSupplier of( - String namespace, String name, + String id, Supplier supplier ) { - return new ChunkRenderOptimizerSupplier(namespace, name) { + return new ChunkRenderOptimizerSupplier(id) { @Override public ChunkRenderOptimizer createOptimizer() { return supplier.get(); diff --git a/src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizers.java b/src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizers.java index 0447675..22b33b2 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizers.java +++ b/src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizers.java @@ -30,7 +30,7 @@ public class ChunkRenderOptimizers { static { register(ChunkRenderOptimizerSupplier.of( - "Default", "OpaqueCube", + "Default:OpaqueCube", ChunkRenderOptimizerCube::new )); } diff --git a/src/main/java/ru/windcorp/progressia/client/world/entity/EntityRender.java b/src/main/java/ru/windcorp/progressia/client/world/entity/EntityRender.java index 32c9be1..bb53974 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/entity/EntityRender.java +++ b/src/main/java/ru/windcorp/progressia/client/world/entity/EntityRender.java @@ -1,12 +1,12 @@ package ru.windcorp.progressia.client.world.entity; -import ru.windcorp.progressia.common.util.Namespaced; +import ru.windcorp.progressia.common.util.namespaces.Namespaced; import ru.windcorp.progressia.common.world.entity.EntityData; public abstract class EntityRender extends Namespaced { - public EntityRender(String namespace, String name) { - super(namespace, name); + public EntityRender(String id) { + super(id); } public abstract EntityRenderable createRenderable(EntityData entity); diff --git a/src/main/java/ru/windcorp/progressia/client/world/entity/EntityRenderRegistry.java b/src/main/java/ru/windcorp/progressia/client/world/entity/EntityRenderRegistry.java index e79cebe..4cb1966 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/entity/EntityRenderRegistry.java +++ b/src/main/java/ru/windcorp/progressia/client/world/entity/EntityRenderRegistry.java @@ -6,9 +6,10 @@ import ru.windcorp.progressia.client.graphics.texture.TextureLoader; import ru.windcorp.progressia.client.graphics.texture.TexturePrimitive; import ru.windcorp.progressia.client.graphics.texture.TextureSettings; import ru.windcorp.progressia.common.resource.ResourceManager; -import ru.windcorp.progressia.common.util.NamespacedRegistry; +import ru.windcorp.progressia.common.util.namespaces.NamespacedInstanceRegistry; +import ru.windcorp.progressia.common.util.crash.CrashReports; -public class EntityRenderRegistry extends NamespacedRegistry { +public class EntityRenderRegistry extends NamespacedInstanceRegistry { private static final EntityRenderRegistry INSTANCE = new EntityRenderRegistry(); @@ -28,7 +29,8 @@ public class EntityRenderRegistry extends NamespacedRegistry { ).getData() ); } catch (IOException e) { - throw new RuntimeException(e); + CrashReports.report(e, "Could not load entity texture %s", name); + return null; } } diff --git a/src/main/java/ru/windcorp/progressia/client/world/entity/HumanoidModel.java b/src/main/java/ru/windcorp/progressia/client/world/entity/HumanoidModel.java new file mode 100644 index 0000000..45817db --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/client/world/entity/HumanoidModel.java @@ -0,0 +1,116 @@ +package ru.windcorp.progressia.client.world.entity; + +import static java.lang.Math.*; +import static ru.windcorp.progressia.common.util.FloatMathUtils.*; + +import glm.mat._4.Mat4; +import glm.vec._3.Vec3; +import ru.windcorp.progressia.client.graphics.model.Renderable; +import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper; +import ru.windcorp.progressia.common.world.entity.EntityData; + +public class HumanoidModel extends NPedModel { + + protected static abstract class Limb extends BodyPart { + private final float animationOffset; + + public Limb( + Renderable renderable, Vec3 joint, + float animationOffset + ) { + super(renderable, joint); + this.animationOffset = animationOffset; + } + + @Override + protected void applyTransform(Mat4 mat, NPedModel model) { + float phase = model.getWalkingFrequency() * model.getWalkingParameter() + animationOffset; + float value = sin(phase); + float amplitude = getSwingAmplitude((HumanoidModel) model) * model.getVelocityParameter(); + mat.rotateY(value * amplitude); + } + + protected abstract float getSwingAmplitude(HumanoidModel model); + + } + + public static class Leg extends Limb { + public Leg( + Renderable renderable, Vec3 joint, + float animationOffset + ) { + super(renderable, joint, animationOffset); + } + + @Override + protected float getSwingAmplitude(HumanoidModel model) { + return model.walkingLegSwing; + } + } + + public static class Arm extends Limb { + public Arm( + Renderable renderable, Vec3 joint, + float animationOffset + ) { + super(renderable, joint, animationOffset); + } + + @Override + protected float getSwingAmplitude(HumanoidModel model) { + return model.walkingArmSwing; + } + } + + private final Arm leftArm; + private final Arm rightArm; + private final Leg leftLeg; + private final Leg rightLeg; + + private float walkingLegSwing; + private float walkingArmSwing; + + public HumanoidModel( + EntityData entity, + + Body body, Head head, + Arm leftArm, Arm rightArm, + Leg leftLeg, Leg rightLeg, + + float scale + ) { + super(entity, body, head, scale); + this.leftArm = leftArm; + this.rightArm = rightArm; + this.leftLeg = leftLeg; + this.rightLeg = rightLeg; + } + + @Override + protected void renderBodyParts(ShapeRenderHelper renderer) { + super.renderBodyParts(renderer); + leftArm.render(renderer, this); + rightArm.render(renderer, this); + leftLeg.render(renderer, this); + rightLeg.render(renderer, this); + } + + public float getWalkingArmSwing() { + return walkingArmSwing; + } + + public float getWalkingLegSwing() { + return walkingLegSwing; + } + + public HumanoidModel setWalkingLegSwing(float walkingLegSwing) { + this.walkingLegSwing = walkingLegSwing; + return this; + } + + public HumanoidModel setWalkingArmSwing(float walkingArmSwing) { + this.walkingArmSwing = walkingArmSwing; + return this; + } + +} diff --git a/src/main/java/ru/windcorp/progressia/client/world/entity/NPedModel.java b/src/main/java/ru/windcorp/progressia/client/world/entity/NPedModel.java new file mode 100644 index 0000000..441ff6e --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/client/world/entity/NPedModel.java @@ -0,0 +1,284 @@ +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.FloatMathUtils.normalizeAngle; + +import glm.Glm; +import glm.mat._4.Mat4; +import glm.vec._3.Vec3; +import glm.vec._4.Vec4; +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.Matrices; +import ru.windcorp.progressia.common.util.Vectors; +import ru.windcorp.progressia.common.world.entity.EntityData; + +public abstract class NPedModel extends EntityRenderable { + + protected static abstract class BodyPart { + private final Renderable renderable; + private final Vec3 translation = new Vec3(); + + public BodyPart(Renderable renderable, Vec3 joint) { + this.renderable = renderable; + if (joint != null) { + this.translation.set(joint); + } + } + + + protected void render( + ShapeRenderHelper renderer, NPedModel model + ) { + renderer.pushTransform().translate(translation); + applyTransform(renderer.pushTransform(), model); + renderable.render(renderer); + renderer.popTransform(); + renderer.popTransform(); + } + + protected abstract void applyTransform(Mat4 mat, NPedModel model); + + public Vec3 getTranslation() { + return translation; + } + } + + 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 { + private final float maxYaw; + private final float maxPitch; + + private final Vec3 viewPoint; + + public Head( + Renderable renderable, Vec3 joint, + double maxYawDegrees, double maxPitchDegrees, + Vec3 viewPoint + ) { + super(renderable, joint); + this.maxYaw = (float) toRadians(maxYawDegrees); + this.maxPitch = (float) toRadians(maxPitchDegrees); + this.viewPoint = viewPoint; + } + + @Override + protected void applyTransform(Mat4 mat, NPedModel model) { + mat.rotateZ(model.getHeadYaw()).rotateY(model.getHeadPitch()); + } + + public Vec3 getViewPoint() { + return viewPoint; + } + } + + protected final Body body; + protected final Head head; + + private float walkingParameter = 0; + private float velocityParameter = 0; + private float velocity = 0; + + /** + * If {@link #velocity} is greater than this value, {@link #velocityParameter} is 1.0. + */ + private float maxEffectiveVelocity = 5 * Units.METERS_PER_SECOND; + + /** + * If {@link #velocity} is less than {@link #maxEffectiveVelocity}, then + * {@code velocityCoeff = exp(velocity / maxEffectiveVelocity, velocityCoeffPower)}. + */ + private float velocityCoeffPower = 1; + + private final float scale; + + private float walkingFrequency; + + private float bodyYaw = Float.NaN; + private float headYaw; + private float headPitch; + + public NPedModel(EntityData data, Body body, Head head, float scale) { + super(data); + this.body = body; + this.head = head; + this.scale = scale; + } + + @Override + public void render(ShapeRenderHelper renderer) { + renderer.pushTransform().scale(scale).rotateZ(bodyYaw); + renderBodyParts(renderer); + renderer.popTransform(); + + accountForVelocity(); + evaluateAngles(); + } + + protected void renderBodyParts(ShapeRenderHelper renderer) { + body.render(renderer, this); + head.render(renderer, this); + } + + private void evaluateAngles() { + float globalYaw = normalizeAngle(getData().getYaw()); + + if (Float.isNaN(bodyYaw)) { + bodyYaw = globalYaw; + headYaw = 0; + } else { + headYaw = normalizeAngle(globalYaw - bodyYaw); + + if (headYaw > +head.maxYaw) { + bodyYaw += headYaw - +head.maxYaw; + headYaw = +head.maxYaw; + } else if (headYaw < -head.maxYaw) { + bodyYaw += headYaw - -head.maxYaw; + headYaw = -head.maxYaw; + } + } + + bodyYaw = normalizeAngle(bodyYaw); + + headPitch = Glm.clamp( + getData().getPitch(), + -head.maxPitch, head.maxPitch + ); + } + + private void accountForVelocity() { + Vec3 horizontal = Vectors.grab3(); + horizontal.set(getData().getVelocity()); + horizontal.z = 0; + + velocity = horizontal.length(); + + evaluateVelocityCoeff(); + + // TODO switch to world time + walkingParameter += velocity * GraphicsInterface.getFrameLength() * 1000; + + bodyYaw += velocityParameter * normalizeAngle( + (float) (atan2(horizontal.y, horizontal.x) - bodyYaw) + ) * min(1, GraphicsInterface.getFrameLength() * 10); + Vectors.release(horizontal); + } + + private void evaluateVelocityCoeff() { + if (velocity > maxEffectiveVelocity) { + velocityParameter = 1; + } else { + velocityParameter = (float) pow(velocity / maxEffectiveVelocity, velocityCoeffPower); + } + } + + @Override + public void getViewPoint(Vec3 output) { + Mat4 m = Matrices.grab4(); + Vec4 v = Vectors.grab4(); + + m.identity() + .scale(scale) + .rotateZ(bodyYaw) + .translate(head.getTranslation()) + .rotateZ(headYaw) + .rotateY(headPitch); + + v.set(head.getViewPoint(), 1); + m.mul(v); + + output.set(v.x, v.y, v.z); + + Vectors.release(v); + Matrices.release(m); + } + + public Body getBody() { + return body; + } + + public Head getHead() { + return head; + } + + public float getBodyYaw() { + return bodyYaw; + } + + public float getHeadYaw() { + return headYaw; + } + + public float getHeadPitch() { + return headPitch; + } + + /** + * Returns a number in the range [0; 1] that can be used to scale animation effects that depend on speed. + * This parameter is 0 when the entity is not moving and 1 when it's moving "fast". + * @return velocity parameter + */ + protected float getVelocityParameter() { + return velocityParameter; + } + + /** + * Returns a number that can be used to parameterize animation effects that depend on walking. + * This parameter increases when the entity moves (e.g. this can be total traveled distance). + * @return walking parameter + */ + protected float getWalkingParameter() { + return walkingParameter; + } + + protected float getVelocity() { + return velocity; + } + + public float getScale() { + return scale; + } + + protected float getWalkingFrequency() { + return walkingFrequency; + } + + public NPedModel setWalkingFrequency(float walkingFrequency) { + this.walkingFrequency = walkingFrequency; + return this; + } + + public float getMaxEffectiveVelocity() { + return maxEffectiveVelocity; + } + + public float getVelocityCoeffPower() { + return velocityCoeffPower; + } + + public NPedModel setMaxEffectiveVelocity(float maxEffectiveVelocity) { + this.maxEffectiveVelocity = maxEffectiveVelocity; + return this; + } + + public NPedModel setVelocityCoeffPower(float velocityCoeffPower) { + this.velocityCoeffPower = velocityCoeffPower; + return this; + } + +} \ No newline at end of file diff --git a/src/main/java/ru/windcorp/progressia/client/world/entity/QuadripedModel.java b/src/main/java/ru/windcorp/progressia/client/world/entity/QuadripedModel.java index c19a3b0..00311d4 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/entity/QuadripedModel.java +++ b/src/main/java/ru/windcorp/progressia/client/world/entity/QuadripedModel.java @@ -2,86 +2,15 @@ package ru.windcorp.progressia.client.world.entity; import static java.lang.Math.*; import static ru.windcorp.progressia.common.util.FloatMathUtils.*; +import static ru.windcorp.progressia.common.util.FloatMathUtils.sin; -import glm.Glm; import glm.mat._4.Mat4; import glm.vec._3.Vec3; -import glm.vec._4.Vec4; -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.util.Matrices; -import ru.windcorp.progressia.common.util.Vectors; import ru.windcorp.progressia.common.world.entity.EntityData; -public class QuadripedModel extends EntityRenderable { - - private static abstract class BodyPart { - private final Renderable renderable; - private final Vec3 translation = new Vec3(); - - public BodyPart(Renderable renderable, Vec3 joint) { - this.renderable = renderable; - if (joint != null) { - this.translation.set(joint); - } - } - - - protected void render( - ShapeRenderHelper renderer, QuadripedModel model - ) { - renderer.pushTransform().translate(translation); - applyTransform(renderer.pushTransform(), model); - renderable.render(renderer); - renderer.popTransform(); - renderer.popTransform(); - } - - protected abstract void applyTransform(Mat4 mat, QuadripedModel model); - - public Vec3 getTranslation() { - return translation; - } - } - - public static class Body extends BodyPart { - public Body(Renderable renderable) { - super(renderable, null); - } - - @Override - protected void applyTransform(Mat4 mat, QuadripedModel model) { - // Do nothing - } - } - - public static class Head extends BodyPart { - private final float maxYaw; - private final float maxPitch; - - private final Vec3 viewPoint; - - public Head( - Renderable renderable, Vec3 joint, - double maxYawDegrees, double maxPitchDegrees, - Vec3 viewPoint - ) { - super(renderable, joint); - this.maxYaw = (float) toRadians(maxYawDegrees); - this.maxPitch = (float) toRadians(maxPitchDegrees); - this.viewPoint = viewPoint; - } - - @Override - protected void applyTransform(Mat4 mat, QuadripedModel model) { - mat.rotateZ(model.headYaw).rotateY(model.headPitch); - } - - public Vec3 getViewPoint() { - return viewPoint; - } - } +public class QuadripedModel extends NPedModel { public static class Leg extends BodyPart { private final float animationOffset; @@ -95,33 +24,19 @@ public class QuadripedModel extends EntityRenderable { } @Override - protected void applyTransform(Mat4 mat, QuadripedModel model) { - mat.rotateY(sin(model.walkingFrequency * model.walkingAnimationParameter + animationOffset) * model.walkingSwing * model.velocityCoeff); + protected void applyTransform(Mat4 mat, NPedModel model) { + float phase = model.getWalkingFrequency() * model.getWalkingParameter() + animationOffset; + float value = sin(phase); + float amplitude = ((QuadripedModel) model).getWalkingSwing() * model.getVelocityParameter(); + mat.rotateY(value * amplitude); } + } - private final Body body; - private final Head head; private final Leg leftForeLeg, rightForeLeg; private final Leg leftHindLeg, rightHindLeg; - private final float scale; - - private float walkingAnimationParameter = 0; - private float velocityCoeff = 0; - private float velocity = 0; - - /** - * Controls how quickly velocityCoeff approaches 1 - */ - private float velocityCutoff = 10; - - private float walkingFrequency = 0.15f / 60.0f; private float walkingSwing = (float) toRadians(30); - - private float bodyYaw = Float.NaN; - private float headYaw; - private float headPitch; public QuadripedModel( EntityData entity, @@ -132,105 +47,30 @@ public class QuadripedModel extends EntityRenderable { float scale ) { - super(entity); + super(entity, body, head, scale); - this.body = body; - this.head = head; this.leftForeLeg = leftForeLeg; this.rightForeLeg = rightForeLeg; this.leftHindLeg = leftHindLeg; this.rightHindLeg = rightHindLeg; - - this.scale = scale; } @Override - public void render(ShapeRenderHelper renderer) { - renderer.pushTransform().scale(scale).rotateZ(bodyYaw); - body.render(renderer, this); - head.render(renderer, this); - leftForeLeg.render(renderer, this); - rightForeLeg.render(renderer, this); - leftHindLeg.render(renderer, this); - rightHindLeg.render(renderer, this); - renderer.popTransform(); - - accountForVelocity(); - evaluateAngles(); - } - - private void evaluateAngles() { - float globalYaw = normalizeAngle(getData().getYaw()); - - if (Float.isNaN(bodyYaw)) { - bodyYaw = globalYaw; - headYaw = 0; - } else { - headYaw = normalizeAngle(globalYaw - bodyYaw); - - if (headYaw > +head.maxYaw) { - bodyYaw += headYaw - +head.maxYaw; - headYaw = +head.maxYaw; - } else if (headYaw < -head.maxYaw) { - bodyYaw += headYaw - -head.maxYaw; - headYaw = -head.maxYaw; - } - } - - bodyYaw = normalizeAngle(bodyYaw); - - headPitch = Glm.clamp( - getData().getPitch(), - -head.maxPitch, head.maxPitch - ); - } - - private void accountForVelocity() { - Vec3 horizontal = Vectors.grab3(); - horizontal.set(getData().getVelocity()); - horizontal.z = 0; - - velocity = horizontal.length(); - - evaluateVelocityCoeff(); - - // TODO switch to world time - walkingAnimationParameter += velocity * GraphicsInterface.getFrameLength() * 1000; - - bodyYaw += velocityCoeff * normalizeAngle( - (float) (atan2(horizontal.y, horizontal.x) - bodyYaw) - ) * min(1, GraphicsInterface.getFrameLength() * 10); - Vectors.release(horizontal); + protected void renderBodyParts(ShapeRenderHelper renderer) { + super.renderBodyParts(renderer); + this.leftForeLeg.render(renderer, this); + this.rightForeLeg.render(renderer, this); + this.leftHindLeg.render(renderer, this); + this.rightHindLeg.render(renderer, this); } - private void evaluateVelocityCoeff() { - if (velocity * velocityCutoff > 1) { - velocityCoeff = 1; - } else { - velocityCoeff = velocity * velocityCutoff; - velocityCoeff *= velocityCoeff; - } + public float getWalkingSwing() { + return walkingSwing; } - @Override - public void getViewPoint(Vec3 output) { - Mat4 m = Matrices.grab4(); - Vec4 v = Vectors.grab4(); - - m.identity() - .scale(scale) - .rotateZ(bodyYaw) - .translate(head.getTranslation()) - .rotateZ(headYaw) - .rotateY(headPitch); - - v.set(head.getViewPoint(), 1); - m.mul(v); - - output.set(v.x, v.y, v.z); - - Vectors.release(v); - Matrices.release(m); + public QuadripedModel setWalkingSwing(float walkingSwing) { + this.walkingSwing = walkingSwing; + return this; } } diff --git a/src/main/java/ru/windcorp/progressia/client/world/tile/TileRender.java b/src/main/java/ru/windcorp/progressia/client/world/tile/TileRender.java index 13f72a7..65cf3a2 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/tile/TileRender.java +++ b/src/main/java/ru/windcorp/progressia/client/world/tile/TileRender.java @@ -3,13 +3,13 @@ package ru.windcorp.progressia.client.world.tile; import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper; import ru.windcorp.progressia.client.graphics.model.Renderable; import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizer; -import ru.windcorp.progressia.common.util.Namespaced; +import ru.windcorp.progressia.common.util.namespaces.Namespaced; import ru.windcorp.progressia.common.world.block.BlockFace; public class TileRender extends Namespaced { - public TileRender(String namespace, String name) { - super(namespace, name); + public TileRender(String id) { + super(id); } public void render(ShapeRenderHelper renderer, BlockFace face) { diff --git a/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderGrass.java b/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderGrass.java index a7be290..2e932e1 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderGrass.java +++ b/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderGrass.java @@ -18,10 +18,10 @@ public class TileRenderGrass extends TileRender implements OpaqueTile { private final Texture sideTexture; public TileRenderGrass( - String namespace, String name, + String id, Texture top, Texture side ) { - super(namespace, name); + super(id); this.topTexture = top; this.sideTexture = side; } diff --git a/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderRegistry.java b/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderRegistry.java index 889ec5a..4c11c01 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderRegistry.java +++ b/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderRegistry.java @@ -22,9 +22,9 @@ import ru.windcorp.progressia.client.graphics.texture.Atlases.AtlasGroup; import ru.windcorp.progressia.client.graphics.texture.SimpleTexture; import ru.windcorp.progressia.client.graphics.texture.Texture; import ru.windcorp.progressia.common.resource.ResourceManager; -import ru.windcorp.progressia.common.util.NamespacedRegistry; +import ru.windcorp.progressia.common.util.namespaces.NamespacedInstanceRegistry; -public class TileRenderRegistry extends NamespacedRegistry { +public class TileRenderRegistry extends NamespacedInstanceRegistry { private static final TileRenderRegistry INSTANCE = new TileRenderRegistry(); diff --git a/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderSimple.java b/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderSimple.java index 32e4435..0e4f3b6 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderSimple.java +++ b/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderSimple.java @@ -16,8 +16,8 @@ public class TileRenderSimple extends TileRender implements OpaqueTile { private final Texture texture; - public TileRenderSimple(String namespace, String name, Texture texture) { - super(namespace, name); + public TileRenderSimple(String id, Texture texture) { + super(id); this.texture = texture; } diff --git a/src/main/java/ru/windcorp/progressia/common/Units.java b/src/main/java/ru/windcorp/progressia/common/Units.java new file mode 100644 index 0000000..0c8348a --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/Units.java @@ -0,0 +1,56 @@ +package ru.windcorp.progressia.common; + +public class Units { + + // Base units + // We're SI. + public static final float METERS = 1; + public static final float KILOGRAMS = 1; + public static final float SECONDS = 1; + + // Length + public static final float CENTIMETERS = METERS / 100; + public static final float MILLIMETERS = METERS / 1000; + public static final float KILOMETERS = METERS * 1000; + + // Surface + public static final float SQUARE_CENTIMETERS = CENTIMETERS * CENTIMETERS; + public static final float SQUARE_METERS = METERS * METERS; + public static final float SQUARE_MILLIMETERS = MILLIMETERS * MILLIMETERS; + public static final float SQUARE_KILOMETERS = KILOMETERS * KILOMETERS; + + // Volume + public static final float CUBIC_CENTIMETERS = CENTIMETERS * CENTIMETERS * CENTIMETERS; + public static final float CUBIC_METERS = METERS * METERS * METERS; + public static final float CUBIC_MILLIMETERS = MILLIMETERS * MILLIMETERS * MILLIMETERS; + public static final float CUBIC_KILOMETERS = KILOMETERS * KILOMETERS * KILOMETERS; + + // Mass + public static final float GRAMS = KILOGRAMS / 1000; + public static final float TONNES = KILOGRAMS * 1000; + + // Density + public static final float KILOGRAMS_PER_CUBIC_METER = KILOGRAMS / CUBIC_METERS; + public static final float GRAMS_PER_CUBIC_CENTIMETER = GRAMS / CUBIC_CENTIMETERS; + + // Time + public static final float MILLISECONDS = SECONDS / 1000; + public static final float MINUTES = SECONDS * 60; + public static final float HOURS = MINUTES * 60; + public static final float DAYS = HOURS * 24; + + // Frequency + public static final float HERTZ = 1 / SECONDS; + public static final float KILOHERTZ = HERTZ * 1000; + + // Velocity + public static final float METERS_PER_SECOND = METERS / SECONDS; + public static final float KILOMETERS_PER_HOUR = KILOMETERS / HOURS; + + // Acceleration + public static final float METERS_PER_SECOND_SQUARED = METERS_PER_SECOND / SECONDS; + + // Force + public static final float NEWTONS = METERS_PER_SECOND_SQUARED * KILOGRAMS; + +} diff --git a/src/main/java/ru/windcorp/progressia/common/collision/AABB.java b/src/main/java/ru/windcorp/progressia/common/collision/AABB.java index 7188e33..e141673 100644 --- a/src/main/java/ru/windcorp/progressia/common/collision/AABB.java +++ b/src/main/java/ru/windcorp/progressia/common/collision/AABB.java @@ -1,93 +1,113 @@ package ru.windcorp.progressia.common.collision; -import java.util.Collection; -import java.util.Map; - import glm.vec._3.Vec3; -import ru.windcorp.progressia.common.world.block.BlockFace; -public class AABB implements CollisionModel { +/** + * An implementation of an + * Axis-Aligned Bounding Box. + * @author javapony + */ +public class AABB implements AABBoid { + + private class AABBWallImpl implements Wall { + + private final Vec3 originOffset = new Vec3(); + private final Vec3 widthSelector = new Vec3(); + private final Vec3 heightSelector = new Vec3(); - private final Map faces = BlockFace.mapToFaces( - new CollisionWall(-0.5f, -0.5f, -0.5f, +1, 0, 0, 0, 0, +1), - new CollisionWall(+0.5f, -0.5f, -0.5f, 0, +1, 0, 0, 0, +1), - new CollisionWall(+0.5f, +0.5f, -0.5f, -1, 0, 0, 0, 0, +1), - new CollisionWall(-0.5f, +0.5f, -0.5f, 0, -1, 0, 0, 0, +1), - - new CollisionWall(-0.5f, -0.5f, +0.5f, +1, 0, 0, 0, +1, 0), - new CollisionWall(-0.5f, -0.5f, -0.5f, 0, +1, 0, +1, 0, 0) - ); + public AABBWallImpl( + float ox, float oy, float oz, + float wx, float wy, float wz, + float hx, float hy, float hz + ) { + this.originOffset.set(ox, oy, oz); + this.widthSelector.set(wx, wy, wz); + this.heightSelector.set(hx, hy, hz); + } + + @Override + public void getOrigin(Vec3 output) { + output.set(originOffset).mul(AABB.this.getSize()).add(AABB.this.getOrigin()); + } + + @Override + public void getWidth(Vec3 output) { + output.set(AABB.this.getSize()).mul(widthSelector); + } + + @Override + public void getHeight(Vec3 output) { + output.set(AABB.this.getSize()).mul(heightSelector); + } + + } + + public static final AABB UNIT_CUBE = new AABB(0, 0, 0, 1, 1, 1); + + private final Wall[] walls = new Wall[] { + new AABBWallImpl(-0.5f, -0.5f, +0.5f, +1, 0, 0, 0, +1, 0), // Top + new AABBWallImpl(-0.5f, -0.5f, -0.5f, 0, +1, 0, +1, 0, 0), // Bottom + new AABBWallImpl(+0.5f, -0.5f, -0.5f, 0, +1, 0, 0, 0, +1), // North + new AABBWallImpl(-0.5f, +0.5f, -0.5f, 0, -1, 0, 0, 0, +1), // South + new AABBWallImpl(+0.5f, +0.5f, -0.5f, -1, 0, 0, 0, 0, +1), // West + new AABBWallImpl(-0.5f, -0.5f, -0.5f, +1, 0, 0, 0, 0, +1) // East + }; private final Vec3 origin = new Vec3(); private final Vec3 size = new Vec3(); public AABB(Vec3 origin, Vec3 size) { - this.origin.set(origin); - this.size.set(size); - - for (CollisionWall wall : getFaces()) { - wall.moveOrigin(origin); - wall.getWidth().mul(size); - wall.getHeight().mul(size); - } + this(origin.x, origin.y, origin.z, size.x, size.y, size.z); } public AABB( - float ox, float oy, float oz, + float ox, float oy, float oz, float xSize, float ySize, float zSize ) { this.origin.set(ox, oy, oz); this.size.set(xSize, ySize, zSize); - - for (CollisionWall wall : getFaces()) { - wall.moveOrigin(ox, oy, oz); - wall.getWidth().mul(xSize, ySize, zSize); - wall.getHeight().mul(xSize, ySize, zSize); - } - } - - public Collection getFaces() { - return faces.values(); } public Vec3 getOrigin() { return origin; } + + @Override + public void getOrigin(Vec3 output) { + output.set(origin); + } @Override public void setOrigin(Vec3 origin) { - for (CollisionWall wall : getFaces()) { - wall.getOrigin().sub(this.origin).add(origin); - } - this.origin.set(origin); } @Override public void moveOrigin(Vec3 displacement) { - for (CollisionWall wall : getFaces()) { - wall.getOrigin().add(displacement); - } - this.origin.add(displacement); } public Vec3 getSize() { return size; } + + @Override + public void getSize(Vec3 output) { + output.set(size); + } public void setSize(Vec3 size) { setSize(size.x, size.y, size.z); } public void setSize(float xSize, float ySize, float zSize) { - for (CollisionWall wall : getFaces()) { - wall.getWidth().div(this.size).mul(xSize, ySize, zSize); - wall.getHeight().div(this.size).mul(xSize, ySize, zSize); - wall.getOrigin().sub(getOrigin()).div(this.size).mul(xSize, ySize, zSize).add(getOrigin()); - } - this.size.set(xSize, ySize, zSize); } + + @Override + public Wall getWall(int faceId) { + // No, we don't support Apple. + return walls[faceId]; + } } diff --git a/src/main/java/ru/windcorp/progressia/common/collision/AABBoid.java b/src/main/java/ru/windcorp/progressia/common/collision/AABBoid.java new file mode 100644 index 0000000..6e1d384 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/collision/AABBoid.java @@ -0,0 +1,17 @@ +package ru.windcorp.progressia.common.collision; + +import glm.vec._3.Vec3; +import ru.windcorp.progressia.common.world.block.BlockFace; + +public interface AABBoid extends CollisionModel { + + void getOrigin(Vec3 output); + void getSize(Vec3 output); + + default Wall getWall(BlockFace face) { + return getWall(face.getId()); + } + + Wall getWall(int faceId); + +} diff --git a/src/main/java/ru/windcorp/progressia/common/collision/CollisionClock.java b/src/main/java/ru/windcorp/progressia/common/collision/CollisionClock.java deleted file mode 100644 index cf42614..0000000 --- a/src/main/java/ru/windcorp/progressia/common/collision/CollisionClock.java +++ /dev/null @@ -1,8 +0,0 @@ -package ru.windcorp.progressia.common.collision; - -public interface CollisionClock { - - float getTime(); - void advanceTime(float change); - -} diff --git a/src/main/java/ru/windcorp/progressia/common/collision/CollisionPathComputer.java b/src/main/java/ru/windcorp/progressia/common/collision/CollisionPathComputer.java new file mode 100644 index 0000000..dd752dd --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/collision/CollisionPathComputer.java @@ -0,0 +1,81 @@ +package ru.windcorp.progressia.common.collision; + +import java.util.function.Consumer; + +import glm.vec._3.Vec3; +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.util.Vectors; + +import static java.lang.Math.*; + +public class CollisionPathComputer { + + private static final float PADDING = 0.5f; + + public static void forEveryBlockInCollisionPath( + Collideable coll, + float maxTime, + Consumer action + ) { + Vec3 displacement = Vectors.grab3(); + coll.getCollideableVelocity(displacement); + displacement.mul(maxTime); + + handleModel(coll.getCollisionModel(), displacement, action); + + Vectors.release(displacement); + } + + private static void handleModel( + CollisionModel model, + Vec3 displacement, + Consumer action + ) { + if (model instanceof CompoundCollisionModel) { + for (CollisionModel subModel : ((CompoundCollisionModel) model).getModels()) { + handleModel(subModel, displacement, action); + } + } else if (model instanceof AABBoid) { + handleAABBoid((AABBoid) model, displacement, action); + } else { + throw new RuntimeException("not supported"); + } + } + + private static void handleAABBoid(AABBoid model, Vec3 displacement, Consumer action) { + Vec3 size = Vectors.grab3(); + Vec3 origin = Vectors.grab3(); + + model.getOrigin(origin); + model.getSize(size); + + origin.mul(2).sub(size).div(2); // Subtract 0.5*size + + Vec3i pos = Vectors.grab3i(); + + for ( + pos.x = (int) floor(origin.x + min(0, size.x) + min(0, displacement.x) - PADDING); + pos.x <= (int) ceil(origin.x + max(0, size.x) + max(0, displacement.x) + PADDING); + pos.x += 1 + ) { + for ( + pos.y = (int) floor(origin.y + min(0, size.y) + min(0, displacement.y) - PADDING); + pos.y <= (int) ceil(origin.y + max(0, size.y) + max(0, displacement.y) + PADDING); + pos.y += 1 + ) { + for ( + pos.z = (int) floor(origin.z + min(0, size.z) + min(0, displacement.z) - PADDING); + pos.z <= (int) ceil(origin.z + max(0, size.z) + max(0, displacement.z) + PADDING); + pos.z += 1 + ) { + action.accept(pos); + } + } + } + + Vectors.release(origin); + Vectors.release(size); + Vectors.release(pos); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/collision/CollisionWall.java b/src/main/java/ru/windcorp/progressia/common/collision/CollisionWall.java deleted file mode 100644 index 8963ffd..0000000 --- a/src/main/java/ru/windcorp/progressia/common/collision/CollisionWall.java +++ /dev/null @@ -1,55 +0,0 @@ -package ru.windcorp.progressia.common.collision; - -import glm.vec._3.Vec3; - -public class CollisionWall { - - private final Vec3 origin = new Vec3(); - private final Vec3 width = new Vec3(); - private final Vec3 height = new Vec3(); - - public CollisionWall(Vec3 origin, Vec3 width, Vec3 height) { - this.origin.set(origin); - this.width.set(width); - this.height.set(height); - } - - public CollisionWall( - float ox, float oy, float oz, - float wx, float wy, float wz, - float hx, float hy, float hz - ) { - this.origin.set(ox, oy, oz); - this.width.set(wx, wy, wz); - this.height.set(hx, hy, hz); - } - - public Vec3 getOrigin() { - return origin; - } - - public Vec3 getWidth() { - return width; - } - - public Vec3 getHeight() { - return height; - } - - public void setOrigin(Vec3 origin) { - setOrigin(origin.x, origin.y, origin.z); - } - - public void setOrigin(float x, float y, float z) { - this.origin.set(x, y, z); - } - - public void moveOrigin(Vec3 displacement) { - moveOrigin(displacement.x, displacement.y, displacement.z); - } - - public void moveOrigin(float dx, float dy, float dz) { - this.origin.add(dx, dy, dz); - } - -} diff --git a/src/main/java/ru/windcorp/progressia/common/collision/CompoundCollisionModel.java b/src/main/java/ru/windcorp/progressia/common/collision/CompoundCollisionModel.java index 2cf4572..ab5e766 100644 --- a/src/main/java/ru/windcorp/progressia/common/collision/CompoundCollisionModel.java +++ b/src/main/java/ru/windcorp/progressia/common/collision/CompoundCollisionModel.java @@ -8,9 +8,9 @@ import glm.vec._3.Vec3; public class CompoundCollisionModel implements CollisionModel { - private final Collection models; + private final Collection models; - public CompoundCollisionModel(Collection models) { + public CompoundCollisionModel(Collection models) { this.models = models; } @@ -18,7 +18,7 @@ public class CompoundCollisionModel implements CollisionModel { this(ImmutableList.copyOf(models)); } - public Collection getModels() { + public Collection getModels() { return models; } diff --git a/src/main/java/ru/windcorp/progressia/common/collision/TranslatedAABB.java b/src/main/java/ru/windcorp/progressia/common/collision/TranslatedAABB.java new file mode 100644 index 0000000..a22c330 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/collision/TranslatedAABB.java @@ -0,0 +1,105 @@ +package ru.windcorp.progressia.common.collision; + +import glm.vec._3.Vec3; +import ru.windcorp.progressia.common.util.Vectors; +import ru.windcorp.progressia.common.world.block.BlockFace; + +public class TranslatedAABB implements AABBoid { + + private class TranslatedAABBWall implements Wall { + private final int id; + + public TranslatedAABBWall(int id) { + this.id = id; + } + + @Override + public void getOrigin(Vec3 output) { + parent.getWall(id).getOrigin(output); + output.add(translation); + } + + @Override + public void getWidth(Vec3 output) { + parent.getWall(id).getWidth(output); + } + + @Override + public void getHeight(Vec3 output) { + parent.getWall(id).getHeight(output); + } + } + + private AABBoid parent; + private final Vec3 translation = new Vec3(); + + private final TranslatedAABBWall[] walls = new TranslatedAABBWall[BlockFace.BLOCK_FACE_COUNT]; + + { + for (int id = 0; id < walls.length; ++id) { + walls[id] = new TranslatedAABBWall(id); + } + } + + public TranslatedAABB(AABBoid parent, float tx, float ty, float tz) { + setParent(parent); + setTranslation(tx, ty, tz); + } + + public TranslatedAABB(AABBoid parent, Vec3 translation) { + this(parent, translation.x, translation.y, translation.z); + } + + public TranslatedAABB() { + this(null, 0, 0, 0); + } + + @Override + public void setOrigin(Vec3 origin) { + Vec3 v = Vectors.grab3().set(origin).sub(translation); + parent.setOrigin(v); + Vectors.release(v); + } + + @Override + public void moveOrigin(Vec3 displacement) { + parent.moveOrigin(displacement); + } + + @Override + public void getOrigin(Vec3 output) { + parent.getOrigin(output); + output.add(translation); + } + + @Override + public void getSize(Vec3 output) { + parent.getSize(output); + } + + @Override + public Wall getWall(int faceId) { + return walls[faceId]; + } + + public AABBoid getParent() { + return parent; + } + + public void setParent(AABBoid parent) { + this.parent = parent; + } + + public Vec3 getTranslation() { + return translation; + } + + public void setTranslation(Vec3 translation) { + setTranslation(translation.x, translation.y, translation.z); + } + + public void setTranslation(float tx, float ty, float tz) { + this.translation.set(tx, ty, tz); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/collision/Wall.java b/src/main/java/ru/windcorp/progressia/common/collision/Wall.java new file mode 100644 index 0000000..9549f04 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/collision/Wall.java @@ -0,0 +1,12 @@ +package ru.windcorp.progressia.common.collision; + +import glm.vec._3.Vec3; + +public interface Wall { + + void getOrigin(Vec3 output); + + void getWidth(Vec3 output); + void getHeight(Vec3 output); + +} diff --git a/src/main/java/ru/windcorp/progressia/common/collision/WorldCollisionHelper.java b/src/main/java/ru/windcorp/progressia/common/collision/WorldCollisionHelper.java new file mode 100644 index 0000000..ebf2c0b --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/collision/WorldCollisionHelper.java @@ -0,0 +1,94 @@ +package ru.windcorp.progressia.common.collision; + +import java.util.ArrayList; +import java.util.Collection; + +import glm.vec._3.Vec3; +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.util.LowOverheadCache; +import ru.windcorp.progressia.common.world.WorldData; + +public class WorldCollisionHelper { + + private final Collideable collideable = new Collideable() { + @Override + public boolean onCollision(Collideable other) { + return false; + } + + @Override + public void moveAsCollideable(Vec3 displacement) { + // Ignore + assert displacement.length() < 1e-3f; + } + + @Override + public CollisionModel getCollisionModel() { + return WorldCollisionHelper.this.model; + } + + @Override + public float getCollisionMass() { + return Float.POSITIVE_INFINITY; + } + + @Override + public void getCollideableVelocity(Vec3 output) { + output.set(0); + } + + @Override + public void changeVelocityOnCollision(Vec3 velocityChange) { + // Ignore + assert velocityChange.length() < 1e-3f; + } + }; + + private final Collection activeBlockModels = new ArrayList<>(); + private final CollisionModel model = new CompoundCollisionModel(activeBlockModels); + private final LowOverheadCache blockModelCache = new LowOverheadCache<>(TranslatedAABB::new); + + /** + * Changes the state of this helper's {@link #getCollideable()} so it is ready to adequately handle + * collisions with the {@code collideable} that might happen in the next {@code maxTime} seconds. + * This helper is only valid for checking collisions with the given Collideable and only within + * the given time limit. + * @param collideable the {@link Collideable} that collisions will be checked against + * @param maxTime maximum collision time + */ + public void tuneToCollideable(WorldData world, Collideable collideable, float maxTime) { + activeBlockModels.forEach(blockModelCache::release); + activeBlockModels.clear(); + CollisionPathComputer.forEveryBlockInCollisionPath( + collideable, + maxTime, + v -> addModel(world.getCollisionModelOfBlock(v), v) + ); + } + + private void addModel(CollisionModel model, Vec3i pos) { + if (model == null) { + // Ignore + } else if (model instanceof AABBoid) { + addAABBoidModel((AABBoid) model, pos); + } else if (model instanceof CompoundCollisionModel) { + for (CollisionModel subModel : ((CompoundCollisionModel) model).getModels()) { + addModel(subModel, pos); + } + } else { + throw new RuntimeException("not supported"); + } + } + + private void addAABBoidModel(AABBoid model, Vec3i pos) { + TranslatedAABB translator = blockModelCache.grab(); + translator.setParent(model); + translator.setTranslation(pos.x, pos.y, pos.z); + activeBlockModels.add(translator); + } + + public Collideable getCollideable() { + return collideable; + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/collision/colliders/AABBWithAABBCollider.java b/src/main/java/ru/windcorp/progressia/common/collision/colliders/AABBoidCollider.java similarity index 78% rename from src/main/java/ru/windcorp/progressia/common/collision/colliders/AABBWithAABBCollider.java rename to src/main/java/ru/windcorp/progressia/common/collision/colliders/AABBoidCollider.java index 7b961e1..f440761 100644 --- a/src/main/java/ru/windcorp/progressia/common/collision/colliders/AABBWithAABBCollider.java +++ b/src/main/java/ru/windcorp/progressia/common/collision/colliders/AABBoidCollider.java @@ -2,26 +2,25 @@ package ru.windcorp.progressia.common.collision.colliders; import glm.mat._3.Mat3; import glm.vec._3.Vec3; -import ru.windcorp.progressia.common.collision.AABB; -import ru.windcorp.progressia.common.collision.Collideable; -import ru.windcorp.progressia.common.collision.CollisionWall; +import ru.windcorp.progressia.common.collision.*; import ru.windcorp.progressia.common.collision.colliders.Collider.ColliderWorkspace; import ru.windcorp.progressia.common.collision.colliders.Collider.Collision; import ru.windcorp.progressia.common.util.Matrices; import ru.windcorp.progressia.common.util.Vectors; +import ru.windcorp.progressia.common.world.block.BlockFace; -class AABBWithAABBCollider { +class AABBoidCollider { static Collider.Collision computeModelCollision( Collideable aBody, Collideable bBody, - AABB aModel, AABB bModel, + AABBoid aModel, AABBoid bModel, float tickLength, ColliderWorkspace workspace ) { Collideable obstacleBody = bBody; Collideable colliderBody = aBody; - AABB obstacleModel = bModel; - AABB colliderModel = aModel; + AABBoid obstacleModel = bModel; + AABBoid colliderModel = aModel; Collision result = null; @@ -32,7 +31,8 @@ class AABBWithAABBCollider { computeCollisionVelocity(collisionVelocity, obstacleBody, colliderBody); // For every wall of collision space - for (CollisionWall wall : originCollisionSpace.getFaces()) { + for (int i = 0; i < BlockFace.BLOCK_FACE_COUNT; ++i) { + Wall wall = originCollisionSpace.getWall(i); Collision collision = computeWallCollision( wall, colliderModel, @@ -80,12 +80,21 @@ class AABBWithAABBCollider { Vectors.release(colliderVelocity); } - private static AABB createOriginCollisionSpace(AABB obstacle, AABB collider, AABB output) { - output.setOrigin(obstacle.getOrigin()); + private static AABB createOriginCollisionSpace(AABBoid obstacle, AABBoid collider, AABB output) { + Vec3 obstacleOrigin = Vectors.grab3(); + Vec3 obstacleSize = Vectors.grab3(); + Vec3 colliderSize = Vectors.grab3(); - Vec3 size = Vectors.grab3().set(obstacle.getSize()).add(collider.getSize()); - output.setSize(size); - Vectors.release(size); + obstacle.getOrigin(obstacleOrigin); + output.setOrigin(obstacleOrigin); + + obstacle.getSize(obstacleSize); + collider.getSize(colliderSize); + output.setSize(obstacleSize.add(colliderSize)); + + Vectors.release(obstacleOrigin); + Vectors.release(obstacleSize); + Vectors.release(colliderSize); return output; } @@ -134,27 +143,34 @@ class AABBWithAABBCollider { * If all conditions are satisfied, then the moment of impact is t0 + t. */ private static Collision computeWallCollision( - CollisionWall obstacleWall, - AABB colliderModel, + Wall obstacleWall, + AABBoid colliderModel, Vec3 collisionVelocity, float tickLength, ColliderWorkspace workspace, Collideable aBody, Collideable bBody ) { - Vec3 w = obstacleWall.getWidth(); - Vec3 h = obstacleWall.getHeight(); + Vec3 w = Vectors.grab3(); + Vec3 h = Vectors.grab3(); Vec3 v = Vectors.grab3(); Mat3 m = Matrices.grab3(); // The matrix [w h -v] Vec3 r = Vectors.grab3(); + Vec3 r_line = Vectors.grab3(); + Vec3 r_wall = Vectors.grab3(); Vec3 xyt = Vectors.grab3(); try { + obstacleWall.getWidth(w); + obstacleWall.getHeight(h); + v.set(collisionVelocity); if (isExiting(v, w, h)) { return null; } - r.set(colliderModel.getOrigin()).sub(obstacleWall.getOrigin()); + obstacleWall.getOrigin(r_wall); + colliderModel.getOrigin(r_line); + r.set(r_line).sub(r_wall); m.c0(w).c1(h).c2(v.negate()); if (Math.abs(m.det()) < 1e-6) { @@ -179,9 +195,13 @@ class AABBWithAABBCollider { return workspace.grab().set(aBody, bBody, obstacleWall, t); } finally { + Vectors.release(w); + Vectors.release(h); Vectors.release(v); - Vectors.release(r); Matrices.release(m); + Vectors.release(r); + Vectors.release(r_line); + Vectors.release(r_wall); Vectors.release(xyt); } } @@ -193,6 +213,6 @@ class AABBWithAABBCollider { return result; } - private AABBWithAABBCollider() {} + private AABBoidCollider() {} } diff --git a/src/main/java/ru/windcorp/progressia/common/collision/colliders/Collider.java b/src/main/java/ru/windcorp/progressia/common/collision/colliders/Collider.java index 74bcc2c..07c62c4 100644 --- a/src/main/java/ru/windcorp/progressia/common/collision/colliders/Collider.java +++ b/src/main/java/ru/windcorp/progressia/common/collision/colliders/Collider.java @@ -6,22 +6,35 @@ import java.util.List; import org.apache.logging.log4j.LogManager; import glm.vec._3.Vec3; -import ru.windcorp.progressia.common.collision.AABB; -import ru.windcorp.progressia.common.collision.Collideable; -import ru.windcorp.progressia.common.collision.CollisionClock; -import ru.windcorp.progressia.common.collision.CollisionModel; -import ru.windcorp.progressia.common.collision.CollisionWall; -import ru.windcorp.progressia.common.collision.CompoundCollisionModel; +import ru.windcorp.progressia.common.collision.*; import ru.windcorp.progressia.common.util.LowOverheadCache; import ru.windcorp.progressia.common.util.Vectors; +import ru.windcorp.progressia.common.world.WorldData; public class Collider { private static final int MAX_COLLISIONS_PER_ENTITY = 64; + /** + * Dear Princess Celestia, + *

+ * When {@linkplain #advanceTime(Collection, Collision, WorldData, float) advancing time}, + * time step for all entities except currently colliding bodies is the current + * collisions's timestamp relative to now. However, currently colliding bodies + * (Collision.a and Collision.b) have a smaller time step. This is done to make sure + * they don't intersect due to rounding errors. + *

+ * Today I learned that bad code has nothing to do with friendship, although lemme tell ya: + * it's got some dank magic. + *

+ * Your faithful student,
+ * Kostyl. + */ + private static final float TIME_STEP_COEFFICIENT_FOR_CURRENTLY_COLLIDING_BODIES = 1e-1f; + public static void performCollisions( List colls, - CollisionClock clock, + WorldData world, float tickLength, ColliderWorkspace workspace ) { @@ -37,12 +50,12 @@ public class Collider { return; } - Collision firstCollision = getFirstCollision(colls, tickLength, workspace); + Collision firstCollision = getFirstCollision(colls, tickLength, world, workspace); if (firstCollision == null) { break; } else { - collide(firstCollision, colls, clock, tickLength, workspace); + collide(firstCollision, colls, world, tickLength, workspace); workspace.release(firstCollision); collisionCount++; @@ -50,45 +63,49 @@ public class Collider { } } - advanceTime(colls, clock, tickLength); + advanceTime(colls, null, world, tickLength); } private static Collision getFirstCollision( List colls, float tickLength, + WorldData world, ColliderWorkspace workspace ) { Collision result = null; + Collideable worldColl = workspace.worldCollisionHelper.getCollideable(); // For every pair of colls for (int i = 0; i < colls.size(); ++i) { Collideable a = colls.get(i); + tuneWorldCollisionHelper(a, tickLength, world, workspace); + + result = workspace.updateLatestCollision( + result, + getCollision(a, worldColl, tickLength, workspace) + ); + for (int j = i + 1; j < colls.size(); ++j) { Collideable b = colls.get(j); - Collision collision = getCollision(a, b, tickLength, workspace); - - // Update result - if (collision != null) { - Collision second; - - if (result == null || collision.time < result.time) { - second = result; - result = collision; - } else { - second = collision; - } - - // Release Collision that is no longer used - if (second != null) workspace.release(second); - } + result = workspace.updateLatestCollision(result, collision); } } return result; } + private static void tuneWorldCollisionHelper( + Collideable coll, + float tickLength, + WorldData world, + ColliderWorkspace workspace + ) { + WorldCollisionHelper wch = workspace.worldCollisionHelper; + wch.tuneToCollideable(world, coll, tickLength); + } + static Collision getCollision( Collideable a, Collideable b, @@ -108,10 +125,10 @@ public class Collider { float tickLength, ColliderWorkspace workspace ) { - if (aModel instanceof AABB && bModel instanceof AABB) { - return AABBWithAABBCollider.computeModelCollision( + if (aModel instanceof AABBoid && bModel instanceof AABBoid) { + return AABBoidCollider.computeModelCollision( aBody, bBody, - (AABB) aModel, (AABB) bModel, + (AABBoid) aModel, (AABBoid) bModel, tickLength, workspace ); @@ -144,11 +161,11 @@ public class Collider { Collision collision, Collection colls, - CollisionClock clock, + WorldData world, float tickLength, ColliderWorkspace workspace ) { - advanceTime(colls, clock, collision.time); + advanceTime(colls, collision, world, collision.time); boolean doNotHandle = false; @@ -237,7 +254,7 @@ public class Collider { Vec3 du_a = Vectors.grab3(); Vec3 du_b = Vectors.grab3(); - n.set(collision.wall.getWidth()).cross(collision.wall.getHeight()).normalize(); + n.set(collision.wallWidth).cross(collision.wallHeight).normalize(); collision.a.getCollideableVelocity(v_a); collision.b.getCollideableVelocity(v_b); @@ -274,8 +291,6 @@ public class Collider { collision.a.changeVelocityOnCollision(du_a); collision.b.changeVelocityOnCollision(du_b); - separate(collision, n, m_a, m_b); - // JGML is still to fuck Vectors.release(n); Vectors.release(v_a); @@ -287,35 +302,26 @@ public class Collider { Vectors.release(du_b); } - private static void separate( - Collision collision, - Vec3 normal, float aRelativeMass, float bRelativeMass - ) { - final float margin = 1e-4f; - - Vec3 displacement = Vectors.grab3(); - - displacement.set(normal).mul(margin).mul(bRelativeMass); - collision.a.moveAsCollideable(displacement); - - displacement.set(normal).mul(margin).mul(aRelativeMass).negate(); - collision.b.moveAsCollideable(displacement); - - Vectors.release(displacement); - } - private static void advanceTime( Collection colls, - CollisionClock clock, + Collision exceptions, + WorldData world, float step ) { - clock.advanceTime(step); + world.advanceTime(step); Vec3 tmp = Vectors.grab3(); for (Collideable coll : colls) { coll.getCollideableVelocity(tmp); - tmp.mul(step); + + float currentStep = step; + + if (exceptions != null && (exceptions.a == coll || exceptions.b == coll)) { + currentStep *= TIME_STEP_COEFFICIENT_FOR_CURRENTLY_COLLIDING_BODIES; + } + + tmp.mul(currentStep); coll.moveAsCollideable(tmp); } @@ -328,6 +334,8 @@ public class Collider { new LowOverheadCache<>(Collision::new); AABB dummyAABB = new AABB(0, 0, 0, 1, 1, 1); + + WorldCollisionHelper worldCollisionHelper = new WorldCollisionHelper(); Collision grab() { return collisionCache.grab(); @@ -337,12 +345,35 @@ public class Collider { collisionCache.release(object); } + Collision updateLatestCollision(Collision a, Collision b) { + if (a == null) { + return b; // may be null + } else if (b == null) { + return a; + } + + Collision first, second; + + if (a.time > b.time) { + first = b; + second = a; + } else { + first = a; + second = b; + } + + release(second); + return first; + } + } static class Collision { public Collideable a; public Collideable b; - public final CollisionWall wall = new CollisionWall(0, 0, 0, 0, 0, 0, 0, 0, 0); + + public final Vec3 wallWidth = new Vec3(); + public final Vec3 wallHeight = new Vec3(); /** * Time offset from the start of the tick. @@ -350,12 +381,15 @@ public class Collider { */ public float time; - public Collision set(Collideable a, Collideable b, CollisionWall wall, float time) { + public Collision set( + Collideable a, Collideable b, + Wall wall, + float time + ) { this.a = a; this.b = b; - this.wall.getOrigin().set(wall.getOrigin()); - this.wall.getWidth().set(wall.getWidth()); - this.wall.getHeight().set(wall.getHeight()); + wall.getWidth(wallWidth); + wall.getHeight(wallHeight); this.time = time; return this; diff --git a/src/main/java/ru/windcorp/progressia/common/comms/controls/ControlData.java b/src/main/java/ru/windcorp/progressia/common/comms/controls/ControlData.java index 065bbdf..0f27553 100644 --- a/src/main/java/ru/windcorp/progressia/common/comms/controls/ControlData.java +++ b/src/main/java/ru/windcorp/progressia/common/comms/controls/ControlData.java @@ -1,11 +1,11 @@ package ru.windcorp.progressia.common.comms.controls; -import ru.windcorp.progressia.common.util.Namespaced; +import ru.windcorp.progressia.common.util.namespaces.Namespaced; public class ControlData extends Namespaced { - public ControlData(String namespace, String name) { - super(namespace, name); + public ControlData(String id) { + super(id); } } diff --git a/src/main/java/ru/windcorp/progressia/common/comms/controls/ControlDataRegistry.java b/src/main/java/ru/windcorp/progressia/common/comms/controls/ControlDataRegistry.java index 8c9146a..5e38b90 100644 --- a/src/main/java/ru/windcorp/progressia/common/comms/controls/ControlDataRegistry.java +++ b/src/main/java/ru/windcorp/progressia/common/comms/controls/ControlDataRegistry.java @@ -1,8 +1,8 @@ package ru.windcorp.progressia.common.comms.controls; -import ru.windcorp.progressia.common.util.NamespacedRegistry; +import ru.windcorp.progressia.common.util.namespaces.NamespacedFactoryRegistry; -public class ControlDataRegistry extends NamespacedRegistry { +public class ControlDataRegistry extends NamespacedFactoryRegistry { private static final ControlDataRegistry INSTANCE = new ControlDataRegistry(); diff --git a/src/main/java/ru/windcorp/progressia/common/comms/controls/PacketControl.java b/src/main/java/ru/windcorp/progressia/common/comms/controls/PacketControl.java index 20fbd24..e9b37d3 100644 --- a/src/main/java/ru/windcorp/progressia/common/comms/controls/PacketControl.java +++ b/src/main/java/ru/windcorp/progressia/common/comms/controls/PacketControl.java @@ -6,8 +6,8 @@ public class PacketControl extends Packet { private final ControlData control; - public PacketControl(String namespace, String name, ControlData control) { - super(namespace, name); + public PacketControl(String id, ControlData control) { + super(id); this.control = control; } diff --git a/src/main/java/ru/windcorp/progressia/common/comms/packets/Packet.java b/src/main/java/ru/windcorp/progressia/common/comms/packets/Packet.java index 114aa18..425a64b 100644 --- a/src/main/java/ru/windcorp/progressia/common/comms/packets/Packet.java +++ b/src/main/java/ru/windcorp/progressia/common/comms/packets/Packet.java @@ -1,11 +1,11 @@ package ru.windcorp.progressia.common.comms.packets; -import ru.windcorp.progressia.common.util.Namespaced; +import ru.windcorp.progressia.common.util.namespaces.Namespaced; public class Packet extends Namespaced { - public Packet(String namespace, String name) { - super(namespace, name); + public Packet(String id) { + super(id); } } diff --git a/src/main/java/ru/windcorp/progressia/common/comms/packets/PacketSetLocalPlayer.java b/src/main/java/ru/windcorp/progressia/common/comms/packets/PacketSetLocalPlayer.java index 4e12d23..c45ef9b 100644 --- a/src/main/java/ru/windcorp/progressia/common/comms/packets/PacketSetLocalPlayer.java +++ b/src/main/java/ru/windcorp/progressia/common/comms/packets/PacketSetLocalPlayer.java @@ -5,7 +5,11 @@ public class PacketSetLocalPlayer extends Packet { private long localPlayerEntityId; public PacketSetLocalPlayer(long entityId) { - super("Core", "SetLocalPlayer"); + this("Core:SetLocalPlayer", entityId); + } + + protected PacketSetLocalPlayer(String id, long entityId) { + super(id); this.localPlayerEntityId = entityId; } diff --git a/src/main/java/ru/windcorp/progressia/common/comms/packets/PacketWorldChange.java b/src/main/java/ru/windcorp/progressia/common/comms/packets/PacketWorldChange.java index 32bd02c..929902a 100644 --- a/src/main/java/ru/windcorp/progressia/common/comms/packets/PacketWorldChange.java +++ b/src/main/java/ru/windcorp/progressia/common/comms/packets/PacketWorldChange.java @@ -4,8 +4,8 @@ import ru.windcorp.progressia.common.world.WorldData; public abstract class PacketWorldChange extends Packet { - public PacketWorldChange(String namespace, String name) { - super(namespace, name); + public PacketWorldChange(String id) { + super(id); } public abstract void apply(WorldData world); diff --git a/src/main/java/ru/windcorp/progressia/common/resource/Resource.java b/src/main/java/ru/windcorp/progressia/common/resource/Resource.java index 7a14329..43b1839 100644 --- a/src/main/java/ru/windcorp/progressia/common/resource/Resource.java +++ b/src/main/java/ru/windcorp/progressia/common/resource/Resource.java @@ -30,50 +30,53 @@ import com.google.common.io.CharStreams; import ru.windcorp.progressia.Progressia; import ru.windcorp.progressia.common.util.Named; +import ru.windcorp.progressia.common.util.crash.CrashReports; public class Resource extends Named { - + public Resource(String name) { super(name); } - + public InputStream getInputStream() { // TODO Do proper resource lookup return Progressia.class.getClassLoader().getResourceAsStream(getName()); } - + public Reader getReader() { return new InputStreamReader(getInputStream()); } - + public String readAsString() { try (Reader reader = getReader()) { return CharStreams.toString(reader); } catch (IOException e) { - throw new RuntimeException(e); // TODO handle gracefully + CrashReports.report(e, "Could not read resource %s as text", this); + return null; } } - + public ByteBuffer readAsBytes(ByteBuffer output) { byte[] byteArray; try (InputStream stream = getInputStream()) { byteArray = ByteStreams.toByteArray(stream); } catch (IOException e) { - throw new RuntimeException(e); // TODO handle gracefully + CrashReports.report(e, "Could not read resource %s as bytes", this); + return null; } - + if (output == null || output.remaining() < byteArray.length) { output = BufferUtils.createByteBuffer(byteArray.length); } - + int position = output.position(); output.put(byteArray); output.limit(output.position()); output.position(position); - + return output; } - + public ByteBuffer readAsBytes() { return readAsBytes(null); } diff --git a/src/main/java/ru/windcorp/progressia/common/state/InspectingStatefulObjectLayout.java b/src/main/java/ru/windcorp/progressia/common/state/InspectingStatefulObjectLayout.java index 14fa24c..767c1f1 100644 --- a/src/main/java/ru/windcorp/progressia/common/state/InspectingStatefulObjectLayout.java +++ b/src/main/java/ru/windcorp/progressia/common/state/InspectingStatefulObjectLayout.java @@ -43,10 +43,8 @@ extends AbstractStatefulObjectLayout { } @Override - public StateFieldBuilder getBuilder(String namespace, String name) { - return new InspectingStateFieldBuilder( - namespace, name - ); + public StateFieldBuilder getBuilder(String id) { + return new InspectingStateFieldBuilder(id); } private class InspectingStateFieldBuilder implements StateFieldBuilder { @@ -56,7 +54,7 @@ extends AbstractStatefulObjectLayout { @Override public IntStateField build() { return registerField(new IntStateField( - namespace, name, + id, isLocal, fieldIndexCounters.getIntsThenIncrement() )); @@ -64,16 +62,12 @@ extends AbstractStatefulObjectLayout { } - private final String namespace; - private final String name; + private final String id; private boolean isLocal = true; - public InspectingStateFieldBuilder( - String namespace, String name - ) { - this.namespace = namespace; - this.name = name; + public InspectingStateFieldBuilder(String id) { + this.id = id; } @Override diff --git a/src/main/java/ru/windcorp/progressia/common/state/IntStateField.java b/src/main/java/ru/windcorp/progressia/common/state/IntStateField.java index 433ccd5..3ce0165 100644 --- a/src/main/java/ru/windcorp/progressia/common/state/IntStateField.java +++ b/src/main/java/ru/windcorp/progressia/common/state/IntStateField.java @@ -7,11 +7,11 @@ import java.io.IOException; public class IntStateField extends StateField { public IntStateField( - String namespace, String name, + String id, boolean isLocal, int index ) { - super(namespace, name, isLocal, index); + super(id, isLocal, index); } public int get(StatefulObject object) { diff --git a/src/main/java/ru/windcorp/progressia/common/state/OptimizedStatefulObjectLayout.java b/src/main/java/ru/windcorp/progressia/common/state/OptimizedStatefulObjectLayout.java index 95e7593..05abaec 100644 --- a/src/main/java/ru/windcorp/progressia/common/state/OptimizedStatefulObjectLayout.java +++ b/src/main/java/ru/windcorp/progressia/common/state/OptimizedStatefulObjectLayout.java @@ -35,7 +35,7 @@ extends AbstractStatefulObjectLayout { } @Override - public StateFieldBuilder getBuilder(String namespace, String name) { + public StateFieldBuilder getBuilder(String id) { return new RetrieverStateFieldBuilder(); } diff --git a/src/main/java/ru/windcorp/progressia/common/state/StateField.java b/src/main/java/ru/windcorp/progressia/common/state/StateField.java index 4238a8b..837f661 100644 --- a/src/main/java/ru/windcorp/progressia/common/state/StateField.java +++ b/src/main/java/ru/windcorp/progressia/common/state/StateField.java @@ -4,7 +4,7 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; -import ru.windcorp.progressia.common.util.Namespaced; +import ru.windcorp.progressia.common.util.namespaces.Namespaced; public abstract class StateField extends Namespaced { @@ -12,11 +12,11 @@ public abstract class StateField extends Namespaced { private final int index; public StateField( - String namespace, String name, + String id, boolean isLocal, int index ) { - super(namespace, name); + super(id); this.isLocal = isLocal; this.index = index; } diff --git a/src/main/java/ru/windcorp/progressia/common/state/StatefulObject.java b/src/main/java/ru/windcorp/progressia/common/state/StatefulObject.java index f35bf19..65bac14 100644 --- a/src/main/java/ru/windcorp/progressia/common/state/StatefulObject.java +++ b/src/main/java/ru/windcorp/progressia/common/state/StatefulObject.java @@ -5,7 +5,7 @@ import java.io.DataOutput; import java.io.IOException; import java.util.Objects; -import ru.windcorp.progressia.common.util.Namespaced; +import ru.windcorp.progressia.common.util.namespaces.Namespaced; /** * An abstract class describing objects that have trackable state, @@ -44,10 +44,9 @@ public abstract class StatefulObject extends Namespaced { public StatefulObject( StatefulObjectRegistry type, - String namespace, - String name + String id ) { - super(namespace, name); + super(id); this.layout = type.getLayout(getId()); this.storage = getLayout().createStorage(); } @@ -96,8 +95,8 @@ public abstract class StatefulObject extends Namespaced { * * @return a configured builder */ - protected StateFieldBuilder field(String namespace, String name) { - StateFieldBuilder builder = getLayout().getBuilder(namespace, name); + protected StateFieldBuilder field(String id) { + StateFieldBuilder builder = getLayout().getBuilder(id); builder.setOrdinal(fieldOrdinal); fieldOrdinal++; diff --git a/src/main/java/ru/windcorp/progressia/common/state/StatefulObjectLayout.java b/src/main/java/ru/windcorp/progressia/common/state/StatefulObjectLayout.java index 5c36487..5165662 100644 --- a/src/main/java/ru/windcorp/progressia/common/state/StatefulObjectLayout.java +++ b/src/main/java/ru/windcorp/progressia/common/state/StatefulObjectLayout.java @@ -43,6 +43,6 @@ public abstract class StatefulObjectLayout { public abstract int computeHashCode(StatefulObject object); public abstract boolean areEqual(StatefulObject a, StatefulObject b); - public abstract StateFieldBuilder getBuilder(String namespace, String name); + public abstract StateFieldBuilder getBuilder(String id); } diff --git a/src/main/java/ru/windcorp/progressia/common/state/StatefulObjectRegistry.java b/src/main/java/ru/windcorp/progressia/common/state/StatefulObjectRegistry.java index 1c118ab..42028cf 100644 --- a/src/main/java/ru/windcorp/progressia/common/state/StatefulObjectRegistry.java +++ b/src/main/java/ru/windcorp/progressia/common/state/StatefulObjectRegistry.java @@ -5,8 +5,8 @@ import java.util.Map; import java.util.WeakHashMap; import java.util.concurrent.atomic.AtomicBoolean; -import ru.windcorp.progressia.common.util.Namespaced; -import ru.windcorp.progressia.common.util.NamespacedRegistry; +import ru.windcorp.progressia.common.util.namespaces.Namespaced; +import ru.windcorp.progressia.common.util.namespaces.NamespacedInstanceRegistry; /** * Registry-like object for identification of various {@link StatefulObject} @@ -30,8 +30,8 @@ public class StatefulObjectRegistry { private final AtomicBoolean isRegistered = new AtomicBoolean(false); - public Type(String namespace, String name, Factory factory) { - super(namespace, name); + public Type(String id, Factory factory) { + super(id); this.factory = factory; } @@ -45,8 +45,8 @@ public class StatefulObjectRegistry { } - private final NamespacedRegistry> registry = - new NamespacedRegistry>() { + private final NamespacedInstanceRegistry> registry = + new NamespacedInstanceRegistry>() { @Override public void register(Type element) { super.register(element); @@ -91,8 +91,8 @@ public class StatefulObjectRegistry { return registry.get(id).build(); } - public void register(String namespace, String name, Factory factory) { - registry.register(new Type<>(namespace, name, factory)); + public void register(String id, Factory factory) { + registry.register(new Type<>(id, factory)); } } diff --git a/src/main/java/ru/windcorp/progressia/common/util/MultiLOC.java b/src/main/java/ru/windcorp/progressia/common/util/MultiLOC.java new file mode 100644 index 0000000..b9e67d9 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/util/MultiLOC.java @@ -0,0 +1,25 @@ +package ru.windcorp.progressia.common.util; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +public class MultiLOC { + + private final Map, LowOverheadCache> caches = new HashMap<>(); + + public MultiLOC addClass(Class clazz, Supplier generator) { + caches.put(clazz, new LowOverheadCache<>(generator)); + return this; + } + + public T grab(Class clazz) { + return clazz.cast(caches.get(clazz).grab()); + } + + @SuppressWarnings("unchecked") + public void release(Object obj) { + ((LowOverheadCache) caches.get(obj.getClass())).release(obj); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/util/NamespacedUtil.java b/src/main/java/ru/windcorp/progressia/common/util/NamespacedUtil.java deleted file mode 100644 index 2e5dafd..0000000 --- a/src/main/java/ru/windcorp/progressia/common/util/NamespacedUtil.java +++ /dev/null @@ -1,46 +0,0 @@ -package ru.windcorp.progressia.common.util; - -import java.util.Objects; -import java.util.function.Predicate; -import java.util.regex.Pattern; - -public class NamespacedUtil { - - public static final char SEPARATOR = ':'; - public static final int MAX_PART_LENGTH = 127; - public static final int MAX_TOTAL_LENGTH = MAX_PART_LENGTH * 2 + 1; - - private static final String PART_REGEX = "^[A-Z][a-zA-Z0-9]{2,}$"; - - private static final Predicate PART_CHECKER = - Pattern.compile(PART_REGEX).asPredicate(); - - public static String getId(String namespace, String name) { - checkPart(namespace, "Namespace"); - checkPart(name, "Name"); - - return namespace + SEPARATOR + name; - } - - private static void checkPart(String data, String name) { - Objects.requireNonNull(data, name); - - if (data.length() > MAX_PART_LENGTH) { - throw new IllegalArgumentException( - name + " \"" + data + "\" is too long. " - + "Expected at most " + MAX_PART_LENGTH - + " characters" - ); - } - - if (!PART_CHECKER.test(name)) { - throw new IllegalArgumentException( - name + " \"" + data + "\" is invalid. " - + "Allowed is: " + PART_REGEX - ); - } - } - - private NamespacedUtil() {} - -} diff --git a/src/main/java/ru/windcorp/progressia/common/util/VectorUtil.java b/src/main/java/ru/windcorp/progressia/common/util/VectorUtil.java index 712a9cf..2b42481 100644 --- a/src/main/java/ru/windcorp/progressia/common/util/VectorUtil.java +++ b/src/main/java/ru/windcorp/progressia/common/util/VectorUtil.java @@ -3,12 +3,22 @@ package ru.windcorp.progressia.common.util; import java.util.function.Consumer; import glm.mat._4.Mat4; +import glm.vec._2.Vec2; +import glm.vec._2.d.Vec2d; +import glm.vec._2.i.Vec2i; import glm.vec._3.Vec3; +import glm.vec._3.d.Vec3d; import glm.vec._3.i.Vec3i; import glm.vec._4.Vec4; +import glm.vec._4.d.Vec4d; +import glm.vec._4.i.Vec4i; public class VectorUtil { + public static enum Axis { + X, Y, Z, W; + } + public static void forEachVectorInCuboid( int x0, int y0, int z0, int x1, int y1, int z1, @@ -47,6 +57,204 @@ public class VectorUtil { inOut.set(vec4.x, vec4.y, vec4.z); } + public static Vec3 linearCombination( + Vec3 va, float ka, + Vec3 vb, float kb, + Vec3 output + ) { + output.set( + va.x * ka + vb.x * kb, + va.y * ka + vb.y * kb, + va.z * ka + vb.z * kb + ); + return output; + } + + public static Vec3 linearCombination( + Vec3 va, float ka, + Vec3 vb, float kb, + Vec3 vc, float kc, + Vec3 output + ) { + output.set( + va.x * ka + vb.x * kb + vc.x * kc, + va.y * ka + vb.y * kb + vc.y * kc, + va.z * ka + vb.z * kb + vc.z * kc + ); + return output; + } + + public static float get(Vec2 v, Axis a) { + switch (a) { + case X: return v.x; + case Y: return v.y; + default: throw new IllegalArgumentException("Vec2 does not have axis " + a); + } + } + + public static Vec2 set(Vec2 v, Axis a, float value) { + switch (a) { + case X: v.x = value; break; + case Y: v.y = value; break; + default: throw new IllegalArgumentException("Vec2 does not have axis " + a); + } + return v; + } + + public static int get(Vec2i v, Axis a) { + switch (a) { + case X: return v.x; + case Y: return v.y; + default: throw new IllegalArgumentException("Vec2i does not have axis " + a); + } + } + + public static Vec2i set(Vec2i v, Axis a, int value) { + switch (a) { + case X: v.x = value; break; + case Y: v.y = value; break; + default: throw new IllegalArgumentException("Vec2i does not have axis " + a); + } + return v; + } + + public static double get(Vec2d v, Axis a) { + switch (a) { + case X: return v.x; + case Y: return v.y; + default: throw new IllegalArgumentException("Vec2d does not have axis " + a); + } + } + + public static Vec2d set(Vec2d v, Axis a, double value) { + switch (a) { + case X: v.x = value; break; + case Y: v.y = value; break; + default: throw new IllegalArgumentException("Vec2d does not have axis " + a); + } + return v; + } + + public static float get(Vec3 v, Axis a) { + switch (a) { + case X: return v.x; + case Y: return v.y; + case Z: return v.z; + default: throw new IllegalArgumentException("Vec3 does not have axis " + a); + } + } + + public static Vec3 set(Vec3 v, Axis a, float value) { + switch (a) { + case X: v.x = value; break; + case Y: v.y = value; break; + case Z: v.z = value; break; + default: throw new IllegalArgumentException("Vec3 does not have axis " + a); + } + return v; + } + + public static int get(Vec3i v, Axis a) { + switch (a) { + case X: return v.x; + case Y: return v.y; + case Z: return v.z; + default: throw new IllegalArgumentException("Vec3i does not have axis " + a); + } + } + + public static Vec3i set(Vec3i v, Axis a, int value) { + switch (a) { + case X: v.x = value; break; + case Y: v.y = value; break; + case Z: v.z = value; break; + default: throw new IllegalArgumentException("Vec3i does not have axis " + a); + } + return v; + } + + public static double get(Vec3d v, Axis a) { + switch (a) { + case X: return v.x; + case Y: return v.y; + case Z: return v.z; + default: throw new IllegalArgumentException("Vec3d does not have axis " + a); + } + } + + public static Vec3d set(Vec3d v, Axis a, double value) { + switch (a) { + case X: v.x = value; break; + case Y: v.y = value; break; + case Z: v.z = value; break; + default: throw new IllegalArgumentException("Vec3d does not have axis " + a); + } + return v; + } + + public static float get(Vec4 v, Axis a) { + switch (a) { + case X: return v.x; + case Y: return v.y; + case Z: return v.z; + case W: return v.w; + default: throw new IllegalArgumentException("Vec4 does not have axis " + a); + } + } + + public static Vec4 set(Vec4 v, Axis a, float value) { + switch (a) { + case X: v.x = value; break; + case Y: v.y = value; break; + case Z: v.z = value; break; + case W: v.w = value; break; + default: throw new IllegalArgumentException("Vec4 does not have axis " + a); + } + return v; + } + + public static int get(Vec4i v, Axis a) { + switch (a) { + case X: return v.x; + case Y: return v.y; + case Z: return v.z; + case W: return v.w; + default: throw new IllegalArgumentException("Vec4i does not have axis " + a); + } + } + + public static Vec4i set(Vec4i v, Axis a, int value) { + switch (a) { + case X: v.x = value; break; + case Y: v.y = value; break; + case Z: v.z = value; break; + case W: v.w = value; break; + default: throw new IllegalArgumentException("Vec4i does not have axis " + a); + } + return v; + } + + public static double get(Vec4d v, Axis a) { + switch (a) { + case X: return v.x; + case Y: return v.y; + case Z: return v.z; + case W: return v.w; + default: throw new IllegalArgumentException("Vec4d does not have axis " + a); + } + } + + public static Vec4d set(Vec4d v, Axis a, double value) { + switch (a) { + case X: v.x = value; break; + case Y: v.y = value; break; + case Z: v.z = value; break; + case W: v.w = value; break; + default: throw new IllegalArgumentException("Vec4d does not have axis " + a); + } + return v; + } + private VectorUtil() {} } diff --git a/src/main/java/ru/windcorp/progressia/common/util/Vectors.java b/src/main/java/ru/windcorp/progressia/common/util/Vectors.java index 489bf13..7cc3623 100644 --- a/src/main/java/ru/windcorp/progressia/common/util/Vectors.java +++ b/src/main/java/ru/windcorp/progressia/common/util/Vectors.java @@ -30,6 +30,19 @@ import glm.vec._4.i.Vec4i; */ public class Vectors { + public static final Vec2 ZERO_2 = new Vec2 (0, 0); + public static final Vec2i ZERO_2i = new Vec2i(0, 0); + public static final Vec3 ZERO_3 = new Vec3 (0, 0, 0); + public static final Vec3i ZERO_3i = new Vec3i(0, 0, 0); + public static final Vec4 ZERO_4 = new Vec4 (0, 0, 0, 0); + public static final Vec4i ZERO_4i = new Vec4i(0, 0, 0, 0); + public static final Vec2 UNIT_2 = new Vec2 (1, 1); + public static final Vec2i UNIT_2i = new Vec2i(1, 1); + public static final Vec3 UNIT_3 = new Vec3 (1, 1, 1); + public static final Vec3i UNIT_3i = new Vec3i(1, 1, 1); + public static final Vec4 UNIT_4 = new Vec4 (1, 1, 1, 1); + public static final Vec4i UNIT_4i = new Vec4i(1, 1, 1, 1); + private static final LowOverheadCache VEC3IS = new LowOverheadCache<>(Vec3i::new); diff --git a/src/main/java/ru/windcorp/progressia/common/util/crash/Analyzer.java b/src/main/java/ru/windcorp/progressia/common/util/crash/Analyzer.java new file mode 100644 index 0000000..df9bdcd --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/util/crash/Analyzer.java @@ -0,0 +1,32 @@ +package ru.windcorp.progressia.common.util.crash; + +/** + * A crash report utility that performs analysis of a problem during crash + * report generation and presents its conclusion to the user via the crash report. + * Unlike {@link ContextProvider}s, Analyzers are provided with the reported problem + * details. + * @see ContextProvider + * @author serega404 + */ +public interface Analyzer { + + /** + * Provides a human-readable string describing this analyzer's conclusion + * on the presented problem, or returns {@code null} if no conclusion + * could be made. + * @param throwable The reported throwable (may be {@code null}) + * @param messageFormat A {@linkplain java.util.Formatter#syntax format string} of a + * human-readable description of the problem + * @param args The arguments for the format string + * @return a conclusion or {@code null} + */ + String analyze(Throwable throwable, String messageFormat, Object... args); + + /** + * Returns this analyzer's human-readable name. + * It should be A String In Title Case With Spaces. + * @return this analyzer's name + */ + String getName(); + +} diff --git a/src/main/java/ru/windcorp/progressia/common/util/crash/ContextProvider.java b/src/main/java/ru/windcorp/progressia/common/util/crash/ContextProvider.java new file mode 100644 index 0000000..7938c2a --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/util/crash/ContextProvider.java @@ -0,0 +1,31 @@ +package ru.windcorp.progressia.common.util.crash; + +import java.util.Map; + +/** + * A crash report utility that gathers information about game and system state + * when a crash occurs and presents it to the user via the crash report. + * ContextProviders are not aware of the nature of the problem, unlike {@link Analyzer}s. + * @see Analyzer + * @author serega404 + */ +public interface ContextProvider { + + /** + * Provides human-readable description of the state of the game and the system. + * This information is {@link Map#put(Object, Object) put} into the provided map + * as key-value pairs. Keys are the characteristic being described, such as "OS Name", + * and should be Strings In Title Case With Spaces. + * If this provider cannot provide any information at this moment, the map is not + * modified. + * @param output the map to append output to + */ + void provideContext(Map output); + + /** + * Returns this provider's human-readable name. + * It should be A String In Title Case With Spaces. + * @return this provider's name + */ + String getName(); +} diff --git a/src/main/java/ru/windcorp/progressia/common/util/crash/CrashReports.java b/src/main/java/ru/windcorp/progressia/common/util/crash/CrashReports.java new file mode 100644 index 0000000..f6dd3d2 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/util/crash/CrashReports.java @@ -0,0 +1,232 @@ +package ru.windcorp.progressia.common.util.crash; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.util.StringBuilderWriter; + +import ru.windcorp.jputil.chars.StringUtil; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.*; + +public class CrashReports { + + private CrashReports() { + } + + private static final Path CRASH_REPORTS_PATH = Paths.get("crash-reports"); + + private static final Collection PROVIDERS = Collections.synchronizedCollection(new ArrayList<>()); + + private static final Collection ANALYZERS = Collections.synchronizedCollection(new ArrayList<>()); + + private static final Logger LOGGER = LogManager.getLogger("crash"); + + /** + * This method never returns. + *

+ * TODO document + * + * @param throwable + * @param messageFormat + * @param args + */ + public static void report(Throwable throwable, String messageFormat, Object... args) { + StringBuilder output = new StringBuilder(); + + try { + String.format(messageFormat, args); + } catch (IllegalFormatException e) { + messageFormat = StringUtil.replaceAll(messageFormat, "%", "%%"); + + if (args.length != 0) { + messageFormat += "\nArgs:"; + for (Object arg : args) { + try { + messageFormat += " \"" + arg.toString() + "\""; + } catch (Throwable t) { + messageFormat += " exc: \"" + t.getClass().toString() + "\""; + } + } + args = new Object[0]; // clear args + } + + messageFormat += "\nCould not format provided description"; + } + + appendContextProviders(output); + addSeparator(output); + if (appendAnalyzers(output, throwable, messageFormat, args)) { + addSeparator(output); + } + + appendMessageFormat(output, messageFormat, args); + + appendStackTrace(output, throwable); + + export(output.toString()); + + System.exit(0); + } + + private static void appendContextProviders(StringBuilder output) { + + // Do a local copy to avoid deadlocks -OLEGSHA + ContextProvider[] localProvidersCopy = PROVIDERS.toArray(new ContextProvider[PROVIDERS.size()]); + + for (ContextProvider provider : localProvidersCopy) { + if (provider == null) + continue; + + addSeparator(output); + + try { + Map buf = new HashMap<>(); + provider.provideContext(buf); + + if (!buf.isEmpty()) { + output.append("Provider name: ").append(provider.getName()).append("\n"); + for (Map.Entry entry : buf.entrySet()) { + output.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); + } + } + } catch (Throwable t) { + output.append("\n"); + + String providerName; + + try { + providerName = provider.getName(); + } catch (Throwable t1) { + providerName = provider.getClass().getName(); + } + + output.append(providerName).append(" is broken").append("\n"); + // ContextProvider is broken + } + } + } + + private static boolean appendAnalyzers(StringBuilder output, Throwable throwable, String messageFormat, + Object[] args) { + boolean analyzerResponsesExist = false; + + // Do a local copy to avoid deadlocks -OLEGSHA + Analyzer[] localAnalyzersCopy = ANALYZERS.toArray(new Analyzer[ANALYZERS.size()]); + + for (Analyzer analyzer : localAnalyzersCopy) { + if (analyzer == null) + continue; + + String answer; + try { + answer = analyzer.analyze(throwable, messageFormat, args); + + if (answer != null && !answer.isEmpty()) { + analyzerResponsesExist = true; + output.append(analyzer.getName()).append(": ").append(answer).append("\n"); + } + } catch (Throwable t) { + analyzerResponsesExist = true; + + output.append("\n"); + + String analyzerName; + + try { + analyzerName = analyzer.getName(); + } catch (Throwable t1) { + analyzerName = analyzer.getClass().getName(); + } + + output.append(analyzerName).append(" is broken").append("\n"); + // Analyzer is broken + } + } + + return analyzerResponsesExist; + } + + private static void appendMessageFormat(StringBuilder output, String messageFormat, Object... arg) { + output.append("Provided description: \n").append(String.format(messageFormat, arg)).append("\n"); + + addSeparator(output); + } + + private static void appendStackTrace(StringBuilder output, Throwable throwable) { + output.append("Stacktrace: \n"); + + if (throwable == null) { + output.append("no Throwable provided").append("\n"); + return; + } + + // Formatting to a human-readable string + Writer sink = new StringBuilderWriter(output); + try { + throwable.printStackTrace(new PrintWriter(sink)); + } catch (Exception e) { + // PLAK + } + output.append("\n"); + } + + private static void export(String report) { + try { + LOGGER.fatal("/n" + report); + } catch (Exception e) { + // PLAK + } + + System.err.println(report); + + generateCrashReportFiles(report); + } + + private static void generateCrashReportFiles(String output) { + Date date = new Date(); + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH.mm.ss"); + + try { + if (!Files.exists(CRASH_REPORTS_PATH)) + Files.createDirectory(CRASH_REPORTS_PATH); + + createFileForCrashReport(output, CRASH_REPORTS_PATH.toString() + "/latest.log"); + createFileForCrashReport(output, + CRASH_REPORTS_PATH.toString() + "/crash-" + dateFormat.format(date) + ".log"); + } catch (Throwable t) { + // Crash Report not created + } + } + + private static void createFileForCrashReport(String buffer, String filename) { + try (BufferedWriter writer = Files.newBufferedWriter(Paths.get(filename), StandardCharsets.UTF_8)) { + writer.write(buffer); + } catch (IOException ex) { + // Crash Report not created + } + } + + public static void registerProvider(ContextProvider provider) { + PROVIDERS.add(provider); + } + + public static void registerAnalyzer(Analyzer analyzer) { + ANALYZERS.add(analyzer); + } + + private static void addSeparator(StringBuilder sb) { + sb.append( + // 80 chars + "--------------------------------------------------------------------------------").append("\n"); + } +} diff --git a/src/main/java/ru/windcorp/progressia/common/util/crash/analyzers/OutOfMemoryAnalyzer.java b/src/main/java/ru/windcorp/progressia/common/util/crash/analyzers/OutOfMemoryAnalyzer.java new file mode 100644 index 0000000..95d5f76 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/util/crash/analyzers/OutOfMemoryAnalyzer.java @@ -0,0 +1,17 @@ +package ru.windcorp.progressia.common.util.crash.analyzers; + +import ru.windcorp.progressia.common.util.crash.Analyzer; + +public class OutOfMemoryAnalyzer implements Analyzer { + @Override + public String analyze(Throwable throwable, String messageFormat, Object... args) { + if (throwable instanceof OutOfMemoryError) + return "Try to add memory to the JVM"; + return null; + } + + @Override + public String getName() { + return "Out Of Memory Analyzer"; + } +} diff --git a/src/main/java/ru/windcorp/progressia/common/util/crash/providers/OSContextProvider.java b/src/main/java/ru/windcorp/progressia/common/util/crash/providers/OSContextProvider.java new file mode 100644 index 0000000..f8d4ab4 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/util/crash/providers/OSContextProvider.java @@ -0,0 +1,20 @@ +package ru.windcorp.progressia.common.util.crash.providers; + +import ru.windcorp.progressia.common.util.crash.ContextProvider; + +import java.util.Map; + +public class OSContextProvider implements ContextProvider { + + @Override + public void provideContext(Map output) { + output.put("OS Name", System.getProperty("os.name")); + output.put("OS Version", System.getProperty("os.version")); + output.put("OS Architecture", System.getProperty("os.arch")); + } + + @Override + public String getName() { + return "OS Context Provider"; + } +} diff --git a/src/main/java/ru/windcorp/progressia/common/util/namespaces/IllegalIdException.java b/src/main/java/ru/windcorp/progressia/common/util/namespaces/IllegalIdException.java new file mode 100644 index 0000000..92fbec0 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/util/namespaces/IllegalIdException.java @@ -0,0 +1,27 @@ +package ru.windcorp.progressia.common.util.namespaces; + +public class IllegalIdException extends RuntimeException { + + private static final long serialVersionUID = -1572240191058305981L; + + public IllegalIdException() { + super(); + } + + protected IllegalIdException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + public IllegalIdException(String message, Throwable cause) { + super(message, cause); + } + + public IllegalIdException(String message) { + super(message); + } + + public IllegalIdException(Throwable cause) { + super(cause); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/util/Namespaced.java b/src/main/java/ru/windcorp/progressia/common/util/namespaces/Namespaced.java similarity index 51% rename from src/main/java/ru/windcorp/progressia/common/util/Namespaced.java rename to src/main/java/ru/windcorp/progressia/common/util/namespaces/Namespaced.java index 635ea85..33b9f74 100644 --- a/src/main/java/ru/windcorp/progressia/common/util/Namespaced.java +++ b/src/main/java/ru/windcorp/progressia/common/util/namespaces/Namespaced.java @@ -15,50 +15,27 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . *******************************************************************************/ -package ru.windcorp.progressia.common.util; +package ru.windcorp.progressia.common.util.namespaces; -import java.util.Objects; -import java.util.function.Predicate; -import java.util.regex.Pattern; - -public abstract class Namespaced extends Named { +public abstract class Namespaced { - private static final char SEPARATOR = ':'; - - private static final String PART_REGEX = "^[A-Z][a-zA-Z0-9]{2,}$"; - - private static final Predicate PART_CHECKER = - Pattern.compile(PART_REGEX).asPredicate(); - - private final String namespace; private final String id; - public Namespaced(String namespace, String name) { - super(name); - this.namespace = Objects.requireNonNull(namespace, "namespace"); - this.id = namespace + SEPARATOR + name; - - if (!PART_CHECKER.test(name)) { - throw new IllegalArgumentException( - "Name \"" + name + "\" is invalid. " - + "Allowed is: " + PART_REGEX - ); - } - - if (!PART_CHECKER.test(namespace)) { - throw new IllegalArgumentException( - "Namespace \"" + namespace + "\" is invalid. " - + "Allowed is: " + PART_REGEX - ); - } + public Namespaced(String id) { + NamespacedUtil.checkId(id); + this.id = id; } - public String getId() { + public final String getId() { return id; } public String getNamespace() { - return namespace; + return NamespacedUtil.getNamespace(getId()); + } + + public String getName() { + return NamespacedUtil.getName(getId()); } @Override @@ -75,15 +52,10 @@ public abstract class Namespaced extends Named { public boolean equals(Object obj) { if (this == obj) return true; - if (!super.equals(obj)) - return false; if (getClass() != obj.getClass()) return false; Namespaced other = (Namespaced) obj; - if (id == null) { - if (other.id != null) - return false; - } else if (!id.equals(other.id)) + if (!id.equals(other.id)) return false; return true; } diff --git a/src/main/java/ru/windcorp/progressia/common/util/namespaces/NamespacedFactoryRegistry.java b/src/main/java/ru/windcorp/progressia/common/util/namespaces/NamespacedFactoryRegistry.java new file mode 100644 index 0000000..583ccd2 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/util/namespaces/NamespacedFactoryRegistry.java @@ -0,0 +1,114 @@ +package ru.windcorp.progressia.common.util.namespaces; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class NamespacedFactoryRegistry +implements Map> { + + @FunctionalInterface + public static interface Factory { + E build(String id); + } + + private final Map> backingMap = + Collections.synchronizedMap(new HashMap<>()); + + private final Logger logger = LogManager.getLogger(getClass()); + + public void register(String id, Factory element) { + if (get(id) != null) { + throw new IllegalArgumentException("ID " + id + " is already registered in " + getClass().getSimpleName()); + } + + logger.debug("Registering {} in {}", id, getClass().getSimpleName()); + backingMap.put(id, element); + } + + @Override + public int size() { + return backingMap.size(); + } + + @Override + public boolean isEmpty() { + return backingMap.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return backingMap.containsKey(key); + } + + public boolean has(String id) { + return backingMap.containsKey(id); + } + + @Override + public boolean containsValue(Object value) { + return backingMap.containsValue(value); + } + + public boolean isRegistered(E element) { + return has(element.getId()); + } + + @Override + public Factory get(Object key) { + return backingMap.get(key); + } + + public E create(String id) { + Factory factory = get(id); + E result = factory.build(id); + if (!result.getId().equals(id)) { + throw new IllegalStateException("Requested ID " + id + " but factory " + factory + " returned an object with ID " + result.getId()); + } + return result; + } + + @Override + public Factory put(String id, Factory factory) { + register(id, factory); + return null; + } + + @Override + public void putAll(Map> m) { + synchronized (backingMap) { + m.entrySet().forEach(e -> register(e.getKey(), e.getValue())); + } + } + + @Override + public Factory remove(Object key) { + return backingMap.remove(key); + } + + @Override + public void clear() { + backingMap.clear(); + } + + @Override + public Set keySet() { + return backingMap.keySet(); + } + + @Override + public Collection> values() { + return backingMap.values(); + } + + @Override + public Set>> entrySet() { + return backingMap.entrySet(); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/util/NamespacedRegistry.java b/src/main/java/ru/windcorp/progressia/common/util/namespaces/NamespacedInstanceRegistry.java similarity index 77% rename from src/main/java/ru/windcorp/progressia/common/util/NamespacedRegistry.java rename to src/main/java/ru/windcorp/progressia/common/util/namespaces/NamespacedInstanceRegistry.java index a2ce835..ca4a56f 100644 --- a/src/main/java/ru/windcorp/progressia/common/util/NamespacedRegistry.java +++ b/src/main/java/ru/windcorp/progressia/common/util/namespaces/NamespacedInstanceRegistry.java @@ -1,4 +1,4 @@ -package ru.windcorp.progressia.common.util; +package ru.windcorp.progressia.common.util.namespaces; import java.util.Collection; import java.util.Collections; @@ -7,17 +7,20 @@ import java.util.Map; import java.util.Set; import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import com.google.errorprone.annotations.DoNotCall; -public class NamespacedRegistry +public class NamespacedInstanceRegistry implements Map { private final Map backingMap = Collections.synchronizedMap(new HashMap<>()); + private final Logger logger = LogManager.getLogger(getClass()); + public void register(E element) { - LogManager.getLogger(getClass()).debug("Registering " + element.getId()); + logger.debug("Registering {} in {}", element.getId(), getClass().getSimpleName()); backingMap.put(element.getId(), element); } @@ -67,7 +70,7 @@ implements Map { @DoNotCall @Deprecated public E put(String key, E value) { throw new UnsupportedOperationException( - "Use NamespacedRegistry.register(E)" + "Use NamespacedInstanceRegistry.register(E)" ); } @@ -83,7 +86,7 @@ implements Map { @DoNotCall @Deprecated public void putAll(Map m) { throw new UnsupportedOperationException( - "Use NamespacedRegistry.registerAll(Collection)" + "Use NamespacedInstanceRegistry.registerAll(Collection)" ); } diff --git a/src/main/java/ru/windcorp/progressia/common/util/namespaces/NamespacedUtil.java b/src/main/java/ru/windcorp/progressia/common/util/namespaces/NamespacedUtil.java new file mode 100644 index 0000000..5ced799 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/util/namespaces/NamespacedUtil.java @@ -0,0 +1,106 @@ +package ru.windcorp.progressia.common.util.namespaces; + +import java.util.Objects; + +import ru.windcorp.jputil.chars.StringUtil; + +public class NamespacedUtil { + + public static final char SEPARATOR = ':'; + + public static final int MAX_ID_LENGTH = 255; + private static final int MAX_PART_LENGTH = (MAX_ID_LENGTH - 1) / 2; + + public static final int MAX_NAMESPACE_LENGTH = MAX_PART_LENGTH; + public static final int MAX_NAME_LENGTH = MAX_PART_LENGTH; + + private static final int MIN_PART_LENGTH = 3; + + public static final int MIN_NAMESPACE_LENGTH = MIN_PART_LENGTH; + public static final int MIN_NAME_LENGTH = MIN_PART_LENGTH; + + /* + * This is the definition of the accepted pattern, but the value of + * these constants is not actually consulted in the check* methods. + */ + private static final String PART_CORE_REGEX = "[A-Z][a-zA-Z0-9]{2," + (MAX_PART_LENGTH - 1) + "}"; + private static final String PART_REGEX = "^" + PART_CORE_REGEX + "$"; + + public static final String NAMESPACE_REGEX = PART_REGEX; + public static final String NAME_REGEX = PART_REGEX; + public static final String ID_REGEX = "^" + PART_CORE_REGEX + ":" + PART_CORE_REGEX + "$"; + + public static String getName(String id) { + checkId(id); + return id.substring(id.indexOf(':') + 1); + } + + public static String getNamespace(String id) { + checkId(id); + return id.substring(0, id.indexOf(':')); + } + + public static String getId(String namespace, String name) { + checkPart(namespace, 0, namespace.length(), "Namespace"); + checkPart(name, 0, name.length(), "Name"); + + return namespace + SEPARATOR + name; + } + + public static void checkId(String id) { + Objects.requireNonNull(id, "id"); + + int firstSeparator = id.indexOf(SEPARATOR); + + boolean areSeparatorsInvalid = (firstSeparator < 0) || (id.indexOf(SEPARATOR, firstSeparator + 1) >= 0); + + if (areSeparatorsInvalid) { + int separators = StringUtil.count(id, SEPARATOR); + throw new IllegalIdException( + "ID \"" + id + "\" is invalid. " + + (separators == 0 ? "No " : "Too many (" + separators + ") ") + + "separators '" + SEPARATOR + "' found, exactly one required" + ); + } + + checkPart(id, 0, firstSeparator, "namespace"); + checkPart(id, firstSeparator + 1, id.length() - firstSeparator - 1, "name"); + } + + private static void checkPart(String data, int offset, int length, String nameForErrors) { + Objects.requireNonNull(data, nameForErrors); + + if (length > MAX_PART_LENGTH) { + throw new IllegalIdException( + nameForErrors + " \"" + data.substring(offset, offset + length) + "\" is too long. " + + "Expected at most " + MAX_PART_LENGTH + + " characters" + ); + } else if (length < MIN_PART_LENGTH) { + throw new IllegalIdException( + nameForErrors + " \"" + data.substring(offset, offset + length) + "\" is too short. " + + "Expected at lest " + MIN_PART_LENGTH + + " characters" + ); + } + + // Don't actually use *_REGEX for speed + + for (int i = 0; i < length; ++i) { + char c = data.charAt(i + offset); + if (!( + ( c >= 'A' && c <= 'Z') || + (i != 0 && c >= 'a' && c <= 'z') || + (i != 0 && c >= '0' && c <= '9') + )) { + throw new IllegalIdException( + nameForErrors + " \"" + data.substring(offset, offset + length) + "\" is invalid. " + + "Allowed is: " + PART_REGEX + ); + } + } + } + + private NamespacedUtil() {} + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/BlockRay.java b/src/main/java/ru/windcorp/progressia/common/world/BlockRay.java new file mode 100644 index 0000000..ab4dbf7 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/BlockRay.java @@ -0,0 +1,148 @@ +package ru.windcorp.progressia.common.world; + +import glm.vec._3.Vec3; +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.util.VectorUtil; +import ru.windcorp.progressia.common.util.VectorUtil.Axis; +import ru.windcorp.progressia.common.world.block.BlockFace; + +import static java.lang.Math.*; + +public class BlockRay { + + private final Vec3 position = new Vec3(); + private final Vec3 direction = new Vec3(); + + private float distance; + + private final Vec3i block = new Vec3i(); + private BlockFace currentFace = null; + + private boolean isValid = false; + + public void start(Vec3 position, Vec3 direction) { + if (!direction.any()) { + throw new IllegalArgumentException("Direction is a zero vector"); + } + + isValid = true; + this.position.set(position).sub(0.5f); // Make sure lattice points are block vertices, not centers + this.direction.set(direction).normalize(); + this.block.set(toBlock(position.x), toBlock(position.y), toBlock(position.z)); + this.distance = 0; + } + + public void end() { + isValid = false; + } + + public Vec3i next() { + checkState(); + + float tx = distanceToEdge(position.x, direction.x); + float ty = distanceToEdge(position.y, direction.y); + float tz = distanceToEdge(position.z, direction.z); + + float tMin; + Axis axis; + + if (tx < ty && tx < tz) { + tMin = tx; + axis = Axis.X; + } else if (ty < tx && ty < tz) { + tMin = ty; + axis = Axis.Y; + } else { + tMin = tz; + axis = Axis.Z; + } + + // block.(axis) += signum(direction.(axis)) + VectorUtil.set(block, axis, VectorUtil.get(block, axis) + (int) signum(VectorUtil.get(direction, axis))); + + // position += direction * tMin + VectorUtil.linearCombination(position, 1, direction, tMin, position); // position += direction * tMin + distance += tMin; + + // position.(axis) = round(position.(axis)) + VectorUtil.set(position, axis, round(VectorUtil.get(position, axis))); + + this.currentFace = computeCurrentFace(axis, (int) signum(VectorUtil.get(direction, axis))); + + return block; + } + + private static float distanceToEdge(float c, float dir) { + if (dir == 0) return Float.POSITIVE_INFINITY; + + float edge; + + if (dir > 0) { + edge = strictCeil(c); + } else { + edge = strictFloor(c); + } + + return (edge - c) / dir; + } + + private BlockFace computeCurrentFace(Axis axis, int sign) { + if (sign == 0) throw new IllegalStateException("sign is zero"); + + switch (axis) { + case X: return sign > 0 ? BlockFace.SOUTH : BlockFace.NORTH; + case Y: return sign > 0 ? BlockFace.EAST : BlockFace.WEST; + default: + case Z: return sign > 0 ? BlockFace.BOTTOM : BlockFace.TOP; + } + } + + public Vec3i current() { + checkState(); + return block; + } + + public Vec3 getPoint(Vec3 output) { + output.set(position); + output.add(0.5f); // Make sure we're in the block-center coordinate system + return output; + } + + public BlockFace getCurrentFace() { + return currentFace; + } + + public float getDistance() { + checkState(); + return distance; + } + + private void checkState() { + if (!isValid) { + throw new IllegalStateException("BlockRay not started"); + } + } + + private static int toBlock(float c) { + return (int) round(c); + } + + /** + * Returns a smallest integer a such that a > c. + * @param c the number to compute strict ceiling of + * @return the strict ceiling of c + */ + private static float strictCeil(float c) { + return (float) (floor(c) + 1); + } + + /** + * Returns a largest integer a such that a < c. + * @param c the number to compute strict ceiling of + * @return the strict ceiling of c + */ + private static float strictFloor(float c) { + return (float) (ceil(c) - 1); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java b/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java index 18d44b0..d725193 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java +++ b/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java @@ -20,6 +20,7 @@ package ru.windcorp.progressia.common.world; import static ru.windcorp.progressia.common.world.block.BlockFace.*; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.function.BiConsumer; @@ -63,8 +64,11 @@ public class ChunkData { private final List entities = Collections.synchronizedList(new ArrayList<>()); - public ChunkData(int x, int y, int z, WorldData world) { - this.position.set(x, y, z); + private final Collection listeners = + Collections.synchronizedCollection(new ArrayList<>()); + + public ChunkData(Vec3i position, WorldData world) { + this.position.set(position.x, position.y, position.z); this.world = world; tmp_generate(); @@ -80,7 +84,7 @@ public class ChunkData { TileData flowers = TileDataRegistry.getInstance().get("Test:YellowFlowers"); TileData sand = TileDataRegistry.getInstance().get("Test:Sand"); - Vec3i aPoint = new Vec3i(5, 0, BLOCKS_PER_CHUNK + BLOCKS_PER_CHUNK/2); + Vec3i aPoint = new Vec3i(5, 0, BLOCKS_PER_CHUNK + BLOCKS_PER_CHUNK/2).sub(getPosition()); Vec3i pos = new Vec3i(); for (int x = 0; x < BLOCKS_PER_CHUNK; ++x) { @@ -92,11 +96,11 @@ public class ChunkData { pos.set(x, y, z); if (f > 17) { - setBlock(pos, stone); + setBlock(pos, stone, false); } else if (f > 14) { - setBlock(pos, dirt); + setBlock(pos, dirt, false); } else { - setBlock(pos, air); + setBlock(pos, air, false); } } @@ -132,26 +136,36 @@ public class ChunkData { } } - EntityData javapony = EntityDataRegistry.getInstance().create("Test:Javapony"); - javapony.setEntityId(0x42); - javapony.setPosition(new Vec3(-6, -6, 20)); - javapony.setDirection(new Vec2( - (float) Math.toRadians(40), (float) Math.toRadians(45) - )); - getEntities().add(javapony); - - EntityData statie = EntityDataRegistry.getInstance().create("Test:Statie"); - statie.setEntityId(0xDEADBEEF); - statie.setPosition(new Vec3(0, 15, 16)); - getEntities().add(statie); + if (!getPosition().any()) { + EntityData player = EntityDataRegistry.getInstance().create("Test:Player"); + player.setEntityId(0x42); + player.setPosition(new Vec3(-6, -6, 20)); + player.setDirection(new Vec2( + (float) Math.toRadians(40), (float) Math.toRadians(45) + )); + getEntities().add(player); + + EntityData statie = EntityDataRegistry.getInstance().create("Test:Statie"); + statie.setEntityId(0xDEADBEEF); + statie.setPosition(new Vec3(0, 15, 16)); + getEntities().add(statie); + } } public BlockData getBlock(Vec3i posInChunk) { return blocks[getBlockIndex(posInChunk)]; } - public void setBlock(Vec3i posInChunk, BlockData block) { + public void setBlock(Vec3i posInChunk, BlockData block, boolean notify) { + BlockData previous = blocks[getBlockIndex(posInChunk)]; blocks[getBlockIndex(posInChunk)] = block; + + if (notify) { + getListeners().forEach(l -> { + l.onChunkBlockChanged(this, posInChunk, previous, block); + l.onChunkChanged(this); + }); + } } public List getTilesOrNull(Vec3i blockInChunk, BlockFace face) { @@ -243,7 +257,7 @@ public class ChunkData { private static void checkLocalCoordinates(Vec3i posInChunk) { if (!isInBounds(posInChunk)) { - throw new IllegalArgumentException( + throw new IllegalCoordinatesException( "Coordinates " + str(posInChunk) + " " + "are not legal chunk coordinates" ); @@ -329,8 +343,28 @@ public class ChunkData { return world; } + public Collection getListeners() { + return listeners; + } + + public void addListener(ChunkDataListener listener) { + this.listeners.add(listener); + } + + public void removeListener(ChunkDataListener listener) { + this.listeners.remove(listener); + } + private static String str(Vec3i v) { return "(" + v.x + "; " + v.y + "; " + v.z + ")"; } + protected void onLoaded() { + getListeners().forEach(l -> l.onChunkLoaded(this)); + } + + protected void beforeUnloaded() { + getListeners().forEach(l -> l.beforeChunkUnloaded(this)); + } + } diff --git a/src/main/java/ru/windcorp/progressia/common/world/ChunkDataListener.java b/src/main/java/ru/windcorp/progressia/common/world/ChunkDataListener.java new file mode 100644 index 0000000..216617e --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/ChunkDataListener.java @@ -0,0 +1,51 @@ +package ru.windcorp.progressia.common.world; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.block.BlockData; +import ru.windcorp.progressia.common.world.block.BlockFace; +import ru.windcorp.progressia.common.world.tile.TileData; + +public interface ChunkDataListener { + + /** + * Invoked after a block has changed in a chunk. + * This is not triggered when a change is caused by chunk loading or unloading. + * @param chunk the chunk that has changed + * @param blockInChunk the {@linkplain Coordinates#blockInChunk chunk coordinates} of the change + * @param previous the previous occupant of {@code blockInChunk} + * @param current the current (new) occupant of {@code blockInChunk} + */ + default void onChunkBlockChanged(ChunkData chunk, Vec3i blockInChunk, BlockData previous, BlockData current) {} + + /** + * Invoked after a tile has been added or removed from a chunk. + * This is not triggered when a change is caused by chunk loading or unloading. + * @param chunk the chunk that has changed + * @param blockInChunk the {@linkplain Coordinates#blockInChunk chunk coordinates} of the change + * @param face the face that the changed tile belongs or belonged to + * @param tile the tile that has been added or removed + * @param wasAdded {@code true} iff the tile has been added, {@code false} iff the tile has been removed + */ + default void onChunkTilesChanged(ChunkData chunk, Vec3i blockInChunk, BlockFace face, TileData tile, boolean wasAdded) {} + + /** + * Invoked whenever a chunk changes, loads or unloads. If some other method in this + * {@code ChunkDataListener} are to be invoked, e.g. is the change was caused by a + * block being removed, this method is called last. + * @param chunk the chunk that has changed + */ + default void onChunkChanged(ChunkData chunk) {} + + /** + * Invoked whenever a chunk has been loaded. + * @param chunk the chunk that has loaded + */ + default void onChunkLoaded(ChunkData chunk) {} + + /** + * Invoked whenever a chunk is about to be unloaded. + * @param chunk the chunk that is going to be loaded + */ + default void beforeChunkUnloaded(ChunkData chunk) {} + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/ChunkDataListeners.java b/src/main/java/ru/windcorp/progressia/common/world/ChunkDataListeners.java new file mode 100644 index 0000000..e0ca46a --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/ChunkDataListeners.java @@ -0,0 +1,25 @@ +package ru.windcorp.progressia.common.world; + +import java.util.function.Consumer; +import java.util.function.Supplier; + +import glm.vec._3.i.Vec3i; + +public class ChunkDataListeners { + + public static WorldDataListener createAdder(Supplier listenerSupplier) { + return new WorldDataListener() { + @Override + public void getChunkListeners(WorldData world, Vec3i chunk, Consumer chunkListenerSink) { + chunkListenerSink.accept(listenerSupplier.get()); + } + }; + } + + public static WorldDataListener createAdder(ChunkDataListener listener) { + return createAdder(() -> listener); + } + + private ChunkDataListeners() {} + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/Coordinates.java b/src/main/java/ru/windcorp/progressia/common/world/Coordinates.java index 76a3f95..bfc7773 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/Coordinates.java +++ b/src/main/java/ru/windcorp/progressia/common/world/Coordinates.java @@ -11,18 +11,17 @@ import glm.vec._3.i.Vec3i; * Three types of coordinates are used in Progressia: *

    * - *
  • World coordinates, in code referred to as {@code blockInWorld} - + *
  • World coordinates, in code referred to as {@code blockInWorld} - * coordinates relative to world origin. Every block in the world has unique * world coordinates.
  • * - *
  • Chunk coordinates, in code referred to as {@code blockInChunk} - + *
  • Chunk coordinates, in code referred to as {@code blockInChunk} - * coordinates relative some chunk's origin. Every block in the chunk has unique * chunk coordinates, but blocks in different chunks may have identical chunk * coordinates. These coordinates are only useful in combination with a chunk - * reference. Chunk coordinates are always [0; {@link #BLOCKS_PER_CHUNK}) - * .
  • + * reference. Chunk coordinates are always [0; {@link #BLOCKS_PER_CHUNK}). * - *
  • Coordinates of chunk, in code referred to as {@code chunk} - + *
  • Coordinates of chunk, in code referred to as {@code chunk} - * chunk coordinates relative to world origin. Every chunk in the world has * unique coordinates of chunk.
  • * diff --git a/src/main/java/ru/windcorp/progressia/common/world/IllegalCoordinatesException.java b/src/main/java/ru/windcorp/progressia/common/world/IllegalCoordinatesException.java new file mode 100644 index 0000000..aba1390 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/IllegalCoordinatesException.java @@ -0,0 +1,28 @@ +package ru.windcorp.progressia.common.world; + +public class IllegalCoordinatesException extends RuntimeException { + + private static final long serialVersionUID = 1362481281554206710L; + + public IllegalCoordinatesException() { + super(); + } + + public IllegalCoordinatesException(String message) { + super(message); + } + + public IllegalCoordinatesException(Throwable cause) { + super(cause); + } + + public IllegalCoordinatesException(String message, Throwable cause) { + super(message, cause); + } + + public IllegalCoordinatesException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/Player.java b/src/main/java/ru/windcorp/progressia/common/world/Player.java new file mode 100644 index 0000000..14d8c76 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/Player.java @@ -0,0 +1,17 @@ +package ru.windcorp.progressia.common.world; + +import ru.windcorp.progressia.common.world.entity.EntityData; + +public class Player { + + private EntityData entity; + + public Player(EntityData entity) { + this.entity = entity; + } + + public EntityData getEntity() { + return entity; + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/WorldData.java b/src/main/java/ru/windcorp/progressia/common/world/WorldData.java index 6440709..29e82ce 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/WorldData.java +++ b/src/main/java/ru/windcorp/progressia/common/world/WorldData.java @@ -17,6 +17,7 @@ *******************************************************************************/ package ru.windcorp.progressia.common.world; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -24,8 +25,10 @@ import glm.vec._3.i.Vec3i; import gnu.trove.impl.sync.TSynchronizedLongObjectMap; import gnu.trove.map.TLongObjectMap; import gnu.trove.map.hash.TLongObjectHashMap; +import ru.windcorp.progressia.common.collision.CollisionModel; import ru.windcorp.progressia.common.util.CoordinatePacker; import ru.windcorp.progressia.common.util.Vectors; +import ru.windcorp.progressia.common.world.block.BlockData; import ru.windcorp.progressia.common.world.entity.EntityData; public class WorldData { @@ -42,30 +45,52 @@ public class WorldData { private final Collection entities = Collections.unmodifiableCollection(entitiesById.valueCollection()); + private float time = 0; + + private final Collection listeners = + Collections.synchronizedCollection(new ArrayList<>()); + public WorldData() { - final int size = 1; - for (int x = -(size / 2); x <= (size / 2); ++x) { - for (int y = -(size / 2); y <= (size / 2); ++y) { - addChunk(new ChunkData(x, y, 0, this)); + } + + public void tmp_generate() { + final int size = 1; + Vec3i cursor = new Vec3i(0, 0, 0); + + for (cursor.x = -(size / 2); cursor.x <= (size / 2); ++cursor.x) { + for (cursor.y = -(size / 2); cursor.y <= (size / 2); ++cursor.y) { + ChunkData chunk = new ChunkData(cursor, this); + addChunkListeners(chunk); + addChunk(chunk); } } } + private void addChunkListeners(ChunkData chunk) { + getListeners().forEach(l -> l.getChunkListeners(this, chunk.getPosition(), chunk::addListener)); + } + private synchronized void addChunk(ChunkData chunk) { chunksByPos.put(getChunkKey(chunk), chunk); chunk.forEachEntity(entity -> entitiesById.put(entity.getEntityId(), entity) ); + + chunk.onLoaded(); + getListeners().forEach(l -> l.onChunkLoaded(this, chunk)); } // private synchronized void removeChunk(ChunkData chunk) { -// chunksByPos.remove(getChunkKey(chunk)); +// getListeners().forEach(l -> l.beforeChunkUnloaded(this, chunk)); +// chunk.beforeUnloaded(); // // chunk.forEachEntity(entity -> // entitiesById.remove(entity.getEntityId()) // ); +// +// chunksByPos.remove(getChunkKey(chunk)); // } private static long getChunkKey(ChunkData chunk) { @@ -84,6 +109,31 @@ public class WorldData { return result; } + public BlockData getBlock(Vec3i blockInWorld) { + ChunkData chunk = getChunkByBlock(blockInWorld); + if (chunk == null) return null; + + Vec3i blockInChunk = Vectors.grab3i(); + Coordinates.convertInWorldToInChunk(blockInWorld, blockInChunk); + BlockData result = chunk.getBlock(blockInChunk); + + return result; + } + + public void setBlock(Vec3i blockInWorld, BlockData block, boolean notify) { + ChunkData chunk = getChunkByBlock(blockInWorld); + if (chunk == null) + throw new IllegalCoordinatesException( + "Coordinates " + + "(" + blockInWorld.x + "; " + blockInWorld.y + "; " + blockInWorld.z + ") " + + "do not belong to a loaded chunk" + ); + + Vec3i blockInChunk = Vectors.grab3i(); + Coordinates.convertInWorldToInChunk(blockInWorld, blockInChunk); + chunk.setBlock(blockInChunk, block, notify); + } + public Collection getChunks() { return chunks; } @@ -96,4 +146,37 @@ public class WorldData { return entities; } + public float getTime() { + return time; + } + + public void advanceTime(float change) { + this.time += change; + } + + public CollisionModel getCollisionModelOfBlock(Vec3i blockInWorld) { + ChunkData chunk = getChunkByBlock(blockInWorld); + if (chunk == null) return null; + + Vec3i blockInChunk = Vectors.grab3i(); + Coordinates.convertInWorldToInChunk(blockInWorld, blockInChunk); + BlockData block = chunk.getBlock(blockInChunk); + Vectors.release(blockInChunk); + + if (block == null) return null; + return block.getCollisionModel(); + } + + public Collection getListeners() { + return listeners; + } + + public void addListener(WorldDataListener e) { + listeners.add(e); + } + + public void removeListener(WorldDataListener o) { + listeners.remove(o); + } + } diff --git a/src/main/java/ru/windcorp/progressia/common/world/WorldDataListener.java b/src/main/java/ru/windcorp/progressia/common/world/WorldDataListener.java new file mode 100644 index 0000000..12b3b9f --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/WorldDataListener.java @@ -0,0 +1,34 @@ +package ru.windcorp.progressia.common.world; + +import java.util.function.Consumer; + +import glm.vec._3.i.Vec3i; + +public interface WorldDataListener { + + /** + * Invoked when a new {@link ChunkData} instance is created. This method should be used to add + * {@link ChunkDataListener}s to a new chunk. When listeners are added with this method, + * their {@link ChunkDataListener#onChunkLoaded(ChunkData) onChunkLoaded} methods will be invoked. + * @param world the world instance + * @param chunk the {@linkplain Coordinates#chunk coordinates of chunk} of the chunk about to load + * @param chunkListenerSink a sink for listeners. All listeners passed to its + * {@link Consumer#accept(Object) accept} method will be added to the chunk. + */ + default void getChunkListeners(WorldData world, Vec3i chunk, Consumer chunkListenerSink) {} + + /** + * Invoked whenever a {@link Chunk} has been loaded. + * @param world the world instance + * @param chunk the chunk that has loaded + */ + default void onChunkLoaded(WorldData world, ChunkData chunk) {} + + /** + * Invoked whenever a {@link Chunk} is about to be unloaded. + * @param world the world instance + * @param chunk the chunk that is going to be unloaded + */ + default void beforeChunkUnloaded(WorldData world, ChunkData chunk) {} + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/block/BlockData.java b/src/main/java/ru/windcorp/progressia/common/world/block/BlockData.java index 5bcbd50..87feb5e 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/block/BlockData.java +++ b/src/main/java/ru/windcorp/progressia/common/world/block/BlockData.java @@ -17,12 +17,18 @@ *******************************************************************************/ package ru.windcorp.progressia.common.world.block; -import ru.windcorp.progressia.common.util.Namespaced; +import ru.windcorp.progressia.common.collision.AABB; +import ru.windcorp.progressia.common.collision.CollisionModel; +import ru.windcorp.progressia.common.util.namespaces.Namespaced; public class BlockData extends Namespaced { - public BlockData(String namespace, String name) { - super(namespace, name); + public BlockData(String id) { + super(id); + } + + public CollisionModel getCollisionModel() { + return AABB.UNIT_CUBE; } } diff --git a/src/main/java/ru/windcorp/progressia/common/world/block/BlockDataRegistry.java b/src/main/java/ru/windcorp/progressia/common/world/block/BlockDataRegistry.java index 6711b3a..f3541a3 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/block/BlockDataRegistry.java +++ b/src/main/java/ru/windcorp/progressia/common/world/block/BlockDataRegistry.java @@ -17,9 +17,9 @@ *******************************************************************************/ package ru.windcorp.progressia.common.world.block; -import ru.windcorp.progressia.common.util.NamespacedRegistry; +import ru.windcorp.progressia.common.util.namespaces.NamespacedInstanceRegistry; -public class BlockDataRegistry extends NamespacedRegistry { +public class BlockDataRegistry extends NamespacedInstanceRegistry { private static final BlockDataRegistry INSTANCE = new BlockDataRegistry(); diff --git a/src/main/java/ru/windcorp/progressia/common/world/block/BlockFace.java b/src/main/java/ru/windcorp/progressia/common/world/block/BlockFace.java index f33f8d7..461429e 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/block/BlockFace.java +++ b/src/main/java/ru/windcorp/progressia/common/world/block/BlockFace.java @@ -25,12 +25,12 @@ import glm.vec._3.i.Vec3i; public final class BlockFace extends BlockRelation { public static final BlockFace - TOP = new BlockFace( 0, 0, +1, true), - BOTTOM = new BlockFace( 0, 0, -1, false), - NORTH = new BlockFace(+1, 0, 0, true), - SOUTH = new BlockFace(-1, 0, 0, false), - WEST = new BlockFace( 0, +1, 0, false), - EAST = new BlockFace( 0, -1, 0, true); + TOP = new BlockFace( 0, 0, +1, true, "TOP"), + BOTTOM = new BlockFace( 0, 0, -1, false, "BOTTOM"), + NORTH = new BlockFace(+1, 0, 0, true, "NORTH"), + SOUTH = new BlockFace(-1, 0, 0, false, "SOUTH"), + WEST = new BlockFace( 0, +1, 0, false, "WEST"), + EAST = new BlockFace( 0, -1, 0, true, "EAST"); private static final ImmutableList ALL_FACES = ImmutableList.of(TOP, BOTTOM, NORTH, SOUTH, WEST, EAST); @@ -77,15 +77,21 @@ public final class BlockFace extends BlockRelation { private static int nextId = 0; private final int id; + private final String name; private BlockFace counterFace; private final boolean isPrimary; - private BlockFace(int x, int y, int z, boolean isPrimary) { + private BlockFace(int x, int y, int z, boolean isPrimary, String name) { super(x, y, z); this.id = nextId++; this.isPrimary = isPrimary; + this.name = name; } + public String getName() { + return name; + } + public boolean isPrimary() { return isPrimary; } @@ -132,5 +138,10 @@ public final class BlockFace extends BlockRelation { public int getManhattanDistance() { return 1; } + + @Override + public String toString() { + return getName(); + } } diff --git a/src/main/java/ru/windcorp/progressia/common/world/entity/EntityData.java b/src/main/java/ru/windcorp/progressia/common/world/entity/EntityData.java index 0b3b73c..b8fa18f 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/entity/EntityData.java +++ b/src/main/java/ru/windcorp/progressia/common/world/entity/EntityData.java @@ -20,8 +20,8 @@ public class EntityData extends StatefulObject implements Collideable { private double age = 0; - public EntityData(String namespace, String name) { - super(EntityDataRegistry.getInstance(), namespace, name); + public EntityData(String id) { + super(EntityDataRegistry.getInstance(), id); } public Vec3 getPosition() { @@ -119,5 +119,15 @@ public class EntityData extends StatefulObject implements Collideable { public void changeVelocityOnCollision(Vec3 velocityChange) { getVelocity().add(velocityChange); } + + public Vec3 getLookingAtVector(Vec3 output) { + output.set( + Math.cos(getPitch()) * Math.cos(getYaw()), + Math.cos(getPitch()) * Math.sin(getYaw()), + -Math.sin(getPitch()) + ); + + return output; + } } diff --git a/src/main/java/ru/windcorp/progressia/common/world/entity/EntityDataRegistry.java b/src/main/java/ru/windcorp/progressia/common/world/entity/EntityDataRegistry.java index 5624b71..9f0c8f1 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/entity/EntityDataRegistry.java +++ b/src/main/java/ru/windcorp/progressia/common/world/entity/EntityDataRegistry.java @@ -10,8 +10,8 @@ public class EntityDataRegistry extends StatefulObjectRegistry { return INSTANCE; } - public void register(String namespace, String name) { - super.register(namespace, name, () -> new EntityData(namespace, name)); + public void register(String id) { + super.register(id, () -> new EntityData(id)); } } diff --git a/src/main/java/ru/windcorp/progressia/common/world/entity/PacketEntityChange.java b/src/main/java/ru/windcorp/progressia/common/world/entity/PacketEntityChange.java index f6c5c8b..569db84 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/entity/PacketEntityChange.java +++ b/src/main/java/ru/windcorp/progressia/common/world/entity/PacketEntityChange.java @@ -7,25 +7,30 @@ import java.io.IOException; import ru.windcorp.progressia.common.comms.packets.PacketWorldChange; import ru.windcorp.progressia.common.state.IOContext; import ru.windcorp.progressia.common.util.DataBuffer; +import ru.windcorp.progressia.common.util.crash.CrashReports; import ru.windcorp.progressia.common.world.WorldData; public class PacketEntityChange extends PacketWorldChange { - + private long entityId; private final DataBuffer buffer = new DataBuffer(); public PacketEntityChange() { - super("Core", "EntityChange"); + super("Core:EntityChange"); } + protected PacketEntityChange(String id) { + super(id); + } + public long getEntityId() { return entityId; } - + public void setEntityId(long entityId) { this.entityId = entityId; } - + public DataBuffer getBuffer() { return buffer; } @@ -41,19 +46,15 @@ public class PacketEntityChange extends PacketWorldChange { @Override public void apply(WorldData world) { EntityData entity = world.getEntity(getEntityId()); - + if (entity == null) { - throw new RuntimeException( - "Entity with ID " + getEntityId() + " not found" - ); + CrashReports.report(null, "Entity with ID %d not found", getEntityId()); } - + try { entity.read(getReader(), IOContext.COMMS); } catch (IOException e) { - throw new RuntimeException( - "Entity could not be read", e - ); + CrashReports.report(e, "Entity could not be read"); } } diff --git a/src/main/java/ru/windcorp/progressia/common/world/tile/TileData.java b/src/main/java/ru/windcorp/progressia/common/world/tile/TileData.java index 7cbbded..d3354f6 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/tile/TileData.java +++ b/src/main/java/ru/windcorp/progressia/common/world/tile/TileData.java @@ -17,12 +17,12 @@ *******************************************************************************/ package ru.windcorp.progressia.common.world.tile; -import ru.windcorp.progressia.common.util.Namespaced; +import ru.windcorp.progressia.common.util.namespaces.Namespaced; public class TileData extends Namespaced { - public TileData(String namespace, String name) { - super(namespace, name); + public TileData(String id) { + super(id); } } diff --git a/src/main/java/ru/windcorp/progressia/common/world/tile/TileDataRegistry.java b/src/main/java/ru/windcorp/progressia/common/world/tile/TileDataRegistry.java index c19fb3d..6a95eae 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/tile/TileDataRegistry.java +++ b/src/main/java/ru/windcorp/progressia/common/world/tile/TileDataRegistry.java @@ -17,9 +17,9 @@ *******************************************************************************/ package ru.windcorp.progressia.common.world.tile; -import ru.windcorp.progressia.common.util.NamespacedRegistry; +import ru.windcorp.progressia.common.util.namespaces.NamespacedInstanceRegistry; -public class TileDataRegistry extends NamespacedRegistry { +public class TileDataRegistry extends NamespacedInstanceRegistry { private static final TileDataRegistry INSTANCE = new TileDataRegistry(); diff --git a/src/main/java/ru/windcorp/progressia/server/ServerState.java b/src/main/java/ru/windcorp/progressia/server/ServerState.java index d8071f6..aa19807 100644 --- a/src/main/java/ru/windcorp/progressia/server/ServerState.java +++ b/src/main/java/ru/windcorp/progressia/server/ServerState.java @@ -16,6 +16,7 @@ public class ServerState { public static void startServer() { Server server = new Server(new WorldData()); + server.getWorld().getData().tmp_generate(); setInstance(server); server.start(); } diff --git a/src/main/java/ru/windcorp/progressia/server/comms/controls/ControlLogic.java b/src/main/java/ru/windcorp/progressia/server/comms/controls/ControlLogic.java index 17ff7f5..28aca40 100644 --- a/src/main/java/ru/windcorp/progressia/server/comms/controls/ControlLogic.java +++ b/src/main/java/ru/windcorp/progressia/server/comms/controls/ControlLogic.java @@ -1,14 +1,23 @@ package ru.windcorp.progressia.server.comms.controls; import ru.windcorp.progressia.common.comms.controls.PacketControl; -import ru.windcorp.progressia.common.util.Namespaced; +import ru.windcorp.progressia.common.util.namespaces.Namespaced; import ru.windcorp.progressia.server.Server; import ru.windcorp.progressia.server.comms.Client; public abstract class ControlLogic extends Namespaced { + + @FunctionalInterface + public static interface Lambda { + void apply( + Server server, + PacketControl packet, + Client client + ); + } - public ControlLogic(String namespace, String name) { - super(namespace, name); + public ControlLogic(String id) { + super(id); } public abstract void apply( @@ -16,5 +25,14 @@ public abstract class ControlLogic extends Namespaced { PacketControl packet, Client client ); + + public static ControlLogic of(String id, Lambda logic) { + return new ControlLogic(id) { + @Override + public void apply(Server server, PacketControl packet, Client client) { + logic.apply(server, packet, client); + } + }; + } } diff --git a/src/main/java/ru/windcorp/progressia/server/comms/controls/ControlLogicRegistry.java b/src/main/java/ru/windcorp/progressia/server/comms/controls/ControlLogicRegistry.java index d37f328..403644d 100644 --- a/src/main/java/ru/windcorp/progressia/server/comms/controls/ControlLogicRegistry.java +++ b/src/main/java/ru/windcorp/progressia/server/comms/controls/ControlLogicRegistry.java @@ -1,8 +1,8 @@ package ru.windcorp.progressia.server.comms.controls; -import ru.windcorp.progressia.common.util.NamespacedRegistry; +import ru.windcorp.progressia.common.util.namespaces.NamespacedInstanceRegistry; -public class ControlLogicRegistry extends NamespacedRegistry { +public class ControlLogicRegistry extends NamespacedInstanceRegistry { private static final ControlLogicRegistry INSTANCE = new ControlLogicRegistry(); diff --git a/src/main/java/ru/windcorp/progressia/server/world/ImplementedChangeTracker.java b/src/main/java/ru/windcorp/progressia/server/world/ImplementedChangeTracker.java index 70fada1..3cb6a8b 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/ImplementedChangeTracker.java +++ b/src/main/java/ru/windcorp/progressia/server/world/ImplementedChangeTracker.java @@ -11,6 +11,7 @@ import ru.windcorp.progressia.common.comms.packets.PacketWorldChange; import ru.windcorp.progressia.common.state.IOContext; import ru.windcorp.progressia.common.util.LowOverheadCache; import ru.windcorp.progressia.common.util.Vectors; +import ru.windcorp.progressia.common.util.crash.CrashReports; import ru.windcorp.progressia.common.world.Coordinates; import ru.windcorp.progressia.common.world.WorldData; import ru.windcorp.progressia.common.world.block.BlockData; @@ -21,50 +22,54 @@ import ru.windcorp.progressia.common.world.tile.TileData; import ru.windcorp.progressia.server.Server; public class ImplementedChangeTracker implements Changer { - + public static interface ChangeImplementation { void applyOnServer(WorldData world); Packet asPacket(); } - + private static class SetBlock extends PacketWorldChange implements ChangeImplementation { private final Vec3i position = new Vec3i(); private BlockData block; - + public SetBlock() { - super("Core", "SetBlock"); + this("Core:SetBlock"); } + protected SetBlock(String id) { + super(id); + } + public void initialize(Vec3i position, BlockData block) { this.position.set(position.x, position.y, position.z); this.block = block; } - + @Override public void applyOnServer(WorldData world) { Vec3i blockInChunk = Vectors.grab3i(); Coordinates.convertInWorldToInChunk(position, blockInChunk); - - world.getChunkByBlock(position).setBlock(blockInChunk, block); - + + world.getChunkByBlock(position).setBlock(blockInChunk, block, true); + Vectors.release(blockInChunk); } - + @Override public void apply(WorldData world) { applyOnServer(world); } - + @Override public Packet asPacket() { return this; } - + } - + private static class AddOrRemoveTile extends PacketWorldChange implements ChangeImplementation { @@ -72,13 +77,17 @@ public class ImplementedChangeTracker implements Changer { private final Vec3i position = new Vec3i(); private BlockFace face; private TileData tile; - + private boolean shouldAdd; - + public AddOrRemoveTile() { - super("Core", "AddOrRemoveTile"); + this("Core:AddOrRemoveTile"); } + protected AddOrRemoveTile(String id) { + super(id); + } + public void initialize( Vec3i position, BlockFace face, TileData tile, @@ -89,52 +98,51 @@ public class ImplementedChangeTracker implements Changer { this.tile = tile; this.shouldAdd = shouldAdd; } - + @Override public void applyOnServer(WorldData world) { Vec3i blockInChunk = Vectors.grab3i(); Coordinates.convertInWorldToInChunk(position, blockInChunk); - - List tiles = world.getChunkByBlock(position) - .getTiles(blockInChunk, face); - + + List tiles = world.getChunkByBlock(position).getTiles(blockInChunk, face); + if (shouldAdd) { tiles.add(tile); } else { tiles.remove(tile); } - + Vectors.release(blockInChunk); } - + @Override public void apply(WorldData world) { applyOnServer(world); } - + @Override public Packet asPacket() { return this; } - + } - + private static class ChangeEntity implements ChangeImplementation { - + private EntityData entity; private Change change; - + private final PacketEntityChange packet = new PacketEntityChange(); public void set(T entity, Change change) { this.entity = entity; this.change = change; - + packet.setEntityId(entity.getEntityId()); try { entity.write(packet.getWriter(), IOContext.COMMS); } catch (IOException e) { - throw new RuntimeException(e); + CrashReports.report(e, "Could not write entity %s", entity); } } @@ -142,11 +150,11 @@ public class ImplementedChangeTracker implements Changer { @Override public void applyOnServer(WorldData world) { ((Change) change).change(entity); - + try { entity.write(packet.getWriter(), IOContext.COMMS); } catch (IOException e) { - throw new RuntimeException("Entity could not be written", e); + CrashReports.report(e, "Could not write entity %s", entity); } } @@ -154,11 +162,11 @@ public class ImplementedChangeTracker implements Changer { public Packet asPacket() { return packet; } - + } private final List changes = new ArrayList<>(1024); - + private final LowOverheadCache setBlockCache = new LowOverheadCache<>(SetBlock::new); @@ -174,21 +182,21 @@ public class ImplementedChangeTracker implements Changer { change.initialize(pos, block); changes.add(change); } - + @Override public void addTile(Vec3i block, BlockFace face, TileData tile) { AddOrRemoveTile change = addOrRemoveTileCache.grab(); change.initialize(block, face, tile, true); changes.add(change); } - + @Override public void removeTile(Vec3i block, BlockFace face, TileData tile) { AddOrRemoveTile change = addOrRemoveTileCache.grab(); change.initialize(block, face, tile, false); changes.add(change); } - + @Override public void changeEntity( T entity, Change change @@ -197,7 +205,7 @@ public class ImplementedChangeTracker implements Changer { changeRecord.set(entity, change); changes.add(changeRecord); } - + public void applyChanges(Server server) { changes.forEach(c -> c.applyOnServer(server.getWorld().getData())); changes.stream().map(ChangeImplementation::asPacket).filter(Objects::nonNull).forEach( @@ -206,7 +214,7 @@ public class ImplementedChangeTracker implements Changer { changes.forEach(this::release); changes.clear(); } - + private void release(ChangeImplementation c) { if (c instanceof SetBlock) { setBlockCache.release((SetBlock) c); diff --git a/src/main/java/ru/windcorp/progressia/server/world/WorldLogic.java b/src/main/java/ru/windcorp/progressia/server/world/WorldLogic.java index 2aae605..10e0db8 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/WorldLogic.java +++ b/src/main/java/ru/windcorp/progressia/server/world/WorldLogic.java @@ -5,8 +5,12 @@ import java.util.HashMap; import java.util.Map; import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.util.Vectors; import ru.windcorp.progressia.common.world.ChunkData; +import ru.windcorp.progressia.common.world.Coordinates; import ru.windcorp.progressia.common.world.WorldData; +import ru.windcorp.progressia.common.world.WorldDataListener; +import ru.windcorp.progressia.server.world.block.BlockLogic; public class WorldLogic { @@ -17,9 +21,17 @@ public class WorldLogic { public WorldLogic(WorldData data) { this.data = data; - for (ChunkData chunkData : data.getChunks()) { - chunks.put(chunkData, new ChunkLogic(this, chunkData)); - } + data.addListener(new WorldDataListener() { + @Override + public void onChunkLoaded(WorldData world, ChunkData chunk) { + chunks.put(chunk, new ChunkLogic(WorldLogic.this, chunk)); + } + + @Override + public void beforeChunkUnloaded(WorldData world, ChunkData chunk) { + chunks.remove(chunk); + } + }); } public WorldData getData() { @@ -34,6 +46,25 @@ public class WorldLogic { return chunks.get(getData().getChunk(pos)); } + public ChunkLogic getChunkByBlock(Vec3i blockInWorld) { + Vec3i chunkPos = Vectors.grab3i(); + Coordinates.convertInWorldToChunk(blockInWorld, chunkPos); + ChunkLogic result = getChunk(chunkPos); + Vectors.release(chunkPos); + return result; + } + + public BlockLogic getBlock(Vec3i blockInWorld) { + ChunkLogic chunk = getChunkByBlock(blockInWorld); + if (chunk == null) return null; + + Vec3i blockInChunk = Vectors.grab3i(); + Coordinates.convertInWorldToInChunk(blockInWorld, blockInChunk); + BlockLogic result = chunk.getBlock(blockInChunk); + + return result; + } + public Collection getChunks() { return chunks.values(); } diff --git a/src/main/java/ru/windcorp/progressia/server/world/block/BlockLogic.java b/src/main/java/ru/windcorp/progressia/server/world/block/BlockLogic.java index a010749..5c9645a 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/block/BlockLogic.java +++ b/src/main/java/ru/windcorp/progressia/server/world/block/BlockLogic.java @@ -1,11 +1,11 @@ package ru.windcorp.progressia.server.world.block; -import ru.windcorp.progressia.common.util.Namespaced; +import ru.windcorp.progressia.common.util.namespaces.Namespaced; public class BlockLogic extends Namespaced { - public BlockLogic(String namespace, String name) { - super(namespace, name); + public BlockLogic(String id) { + super(id); } } diff --git a/src/main/java/ru/windcorp/progressia/server/world/block/BlockLogicRegistry.java b/src/main/java/ru/windcorp/progressia/server/world/block/BlockLogicRegistry.java index 3666c5d..5cdb1f0 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/block/BlockLogicRegistry.java +++ b/src/main/java/ru/windcorp/progressia/server/world/block/BlockLogicRegistry.java @@ -1,8 +1,8 @@ package ru.windcorp.progressia.server.world.block; -import ru.windcorp.progressia.common.util.NamespacedRegistry; +import ru.windcorp.progressia.common.util.namespaces.NamespacedInstanceRegistry; -public class BlockLogicRegistry extends NamespacedRegistry { +public class BlockLogicRegistry extends NamespacedInstanceRegistry { private static final BlockLogicRegistry INSTANCE = new BlockLogicRegistry(); diff --git a/src/main/java/ru/windcorp/progressia/server/world/block/UpdatableBlock.java b/src/main/java/ru/windcorp/progressia/server/world/block/UpdateableBlock.java similarity index 79% rename from src/main/java/ru/windcorp/progressia/server/world/block/UpdatableBlock.java rename to src/main/java/ru/windcorp/progressia/server/world/block/UpdateableBlock.java index b76f6da..406f4c9 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/block/UpdatableBlock.java +++ b/src/main/java/ru/windcorp/progressia/server/world/block/UpdateableBlock.java @@ -2,7 +2,7 @@ package ru.windcorp.progressia.server.world.block; import ru.windcorp.progressia.server.world.Changer; -public interface UpdatableBlock { +public interface UpdateableBlock { void update(BlockTickContext context, Changer changer); diff --git a/src/main/java/ru/windcorp/progressia/server/world/entity/EntityLogic.java b/src/main/java/ru/windcorp/progressia/server/world/entity/EntityLogic.java index 22f31c5..f678e25 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/entity/EntityLogic.java +++ b/src/main/java/ru/windcorp/progressia/server/world/entity/EntityLogic.java @@ -1,14 +1,14 @@ package ru.windcorp.progressia.server.world.entity; -import ru.windcorp.progressia.common.util.Namespaced; +import ru.windcorp.progressia.common.util.namespaces.Namespaced; import ru.windcorp.progressia.common.world.entity.EntityData; import ru.windcorp.progressia.server.world.Changer; import ru.windcorp.progressia.server.world.TickContext; public class EntityLogic extends Namespaced { - public EntityLogic(String namespace, String name) { - super(namespace, name); + public EntityLogic(String id) { + super(id); } public void tick(EntityData entity, TickContext context, Changer changer) { diff --git a/src/main/java/ru/windcorp/progressia/server/world/entity/EntityLogicRegistry.java b/src/main/java/ru/windcorp/progressia/server/world/entity/EntityLogicRegistry.java index 21e37f9..012ee09 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/entity/EntityLogicRegistry.java +++ b/src/main/java/ru/windcorp/progressia/server/world/entity/EntityLogicRegistry.java @@ -1,8 +1,8 @@ package ru.windcorp.progressia.server.world.entity; -import ru.windcorp.progressia.common.util.NamespacedRegistry; +import ru.windcorp.progressia.common.util.namespaces.NamespacedInstanceRegistry; -public class EntityLogicRegistry extends NamespacedRegistry { +public class EntityLogicRegistry extends NamespacedInstanceRegistry { private static final EntityLogicRegistry INSTANCE = new EntityLogicRegistry(); diff --git a/src/main/java/ru/windcorp/progressia/server/world/tile/TileLogic.java b/src/main/java/ru/windcorp/progressia/server/world/tile/TileLogic.java index f530c26..c44ec7e 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/tile/TileLogic.java +++ b/src/main/java/ru/windcorp/progressia/server/world/tile/TileLogic.java @@ -1,12 +1,12 @@ package ru.windcorp.progressia.server.world.tile; -import ru.windcorp.progressia.common.util.Namespaced; +import ru.windcorp.progressia.common.util.namespaces.Namespaced; import ru.windcorp.progressia.common.world.block.BlockFace; public class TileLogic extends Namespaced { - public TileLogic(String namespace, String name) { - super(namespace, name); + public TileLogic(String id) { + super(id); } public boolean canOccupyFace(TileTickContext context) { diff --git a/src/main/java/ru/windcorp/progressia/server/world/tile/TileLogicRegistry.java b/src/main/java/ru/windcorp/progressia/server/world/tile/TileLogicRegistry.java index a5b522b..2424e46 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/tile/TileLogicRegistry.java +++ b/src/main/java/ru/windcorp/progressia/server/world/tile/TileLogicRegistry.java @@ -1,8 +1,8 @@ package ru.windcorp.progressia.server.world.tile; -import ru.windcorp.progressia.common.util.NamespacedRegistry; +import ru.windcorp.progressia.common.util.namespaces.NamespacedInstanceRegistry; -public class TileLogicRegistry extends NamespacedRegistry { +public class TileLogicRegistry extends NamespacedInstanceRegistry { private static final TileLogicRegistry INSTANCE = new TileLogicRegistry(); diff --git a/src/main/java/ru/windcorp/progressia/test/AABBRenderer.java b/src/main/java/ru/windcorp/progressia/test/AABBRenderer.java deleted file mode 100644 index e9a6b7b..0000000 --- a/src/main/java/ru/windcorp/progressia/test/AABBRenderer.java +++ /dev/null @@ -1,35 +0,0 @@ -package ru.windcorp.progressia.test; - -import ru.windcorp.progressia.client.graphics.model.Shape; -import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper; -import ru.windcorp.progressia.client.graphics.model.Shapes; -import ru.windcorp.progressia.client.graphics.texture.Texture; -import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram; -import ru.windcorp.progressia.common.collision.AABB; -import ru.windcorp.progressia.common.collision.CollisionModel; -import ru.windcorp.progressia.common.collision.CompoundCollisionModel; - -public class AABBRenderer { - - private static final Shape CUBE = new Shapes.PppBuilder(WorldRenderProgram.getDefault(), (Texture) null).setColorMultiplier(1.0f, 0.7f, 0.2f).create(); - - public static void renderAABB(AABB aabb, ShapeRenderHelper helper) { - helper.pushTransform().translate(aabb.getOrigin()).scale(aabb.getSize()); - CUBE.render(helper); - helper.popTransform(); - } - - public static void renderAABBsInCompound( - CompoundCollisionModel model, - ShapeRenderHelper helper - ) { - for (CollisionModel part : model.getModels()) { - if (part instanceof CompoundCollisionModel) { - renderAABBsInCompound((CompoundCollisionModel) part, helper); - } else if (part instanceof AABB) { - renderAABB((AABB) part, helper); - } - } - } - -} diff --git a/src/main/java/ru/windcorp/progressia/test/CollisionModelRenderer.java b/src/main/java/ru/windcorp/progressia/test/CollisionModelRenderer.java new file mode 100644 index 0000000..e2d226f --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/CollisionModelRenderer.java @@ -0,0 +1,61 @@ +package ru.windcorp.progressia.test; + +import glm.mat._4.Mat4; +import glm.vec._3.Vec3; +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.client.graphics.model.Shape; +import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper; +import ru.windcorp.progressia.client.graphics.model.Shapes; +import ru.windcorp.progressia.client.graphics.texture.Texture; +import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram; +import ru.windcorp.progressia.common.collision.AABBoid; +import ru.windcorp.progressia.common.collision.CollisionModel; +import ru.windcorp.progressia.common.collision.CompoundCollisionModel; +import ru.windcorp.progressia.common.util.Vectors; + +public class CollisionModelRenderer { + + private static final Shape CUBE = new Shapes.PppBuilder(WorldRenderProgram.getDefault(), (Texture) null).setColorMultiplier(1.0f, 0.7f, 0.2f).create(); + private static final Shape CUBE_GRAY = new Shapes.PppBuilder(WorldRenderProgram.getDefault(), (Texture) null).setColorMultiplier(0.5f, 0.5f, 0.5f).create(); + + public static void renderCollisionModel(CollisionModel model, ShapeRenderHelper helper) { + if (model instanceof AABBoid) { + renderAABBoid((AABBoid) model, helper); + } else if (model instanceof CompoundCollisionModel) { + renderCompound((CompoundCollisionModel) model, helper); + } else { + // Ignore silently + } + } + + private static void renderAABBoid(AABBoid aabb, ShapeRenderHelper helper) { + Mat4 mat = helper.pushTransform(); + Vec3 tmp = Vectors.grab3(); + + aabb.getOrigin(tmp); + mat.translate(tmp); + aabb.getSize(tmp); + mat.scale(tmp); + + Vectors.release(tmp); + + CUBE.render(helper); + helper.popTransform(); + } + + private static void renderCompound( + CompoundCollisionModel model, + ShapeRenderHelper helper + ) { + for (CollisionModel part : model.getModels()) { + renderCollisionModel(part, helper); + } + } + + public static void renderBlock(Vec3i coords, ShapeRenderHelper helper) { + helper.pushTransform().translate(coords.x, coords.y, coords.z).scale(0.25f); + CUBE_GRAY.render(helper); + helper.popTransform(); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/ControlBreakBlockData.java b/src/main/java/ru/windcorp/progressia/test/ControlBreakBlockData.java new file mode 100644 index 0000000..23761e5 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/ControlBreakBlockData.java @@ -0,0 +1,22 @@ +package ru.windcorp.progressia.test; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.comms.controls.ControlData; + +public class ControlBreakBlockData extends ControlData { + + private final Vec3i blockInWorld = new Vec3i(); + + public ControlBreakBlockData(String id) { + super(id); + } + + public Vec3i getBlockInWorld() { + return blockInWorld; + } + + public void setBlockInWorld(Vec3i blockInWorld) { + this.blockInWorld.set(blockInWorld.x, blockInWorld.y, blockInWorld.z); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/ControlPlaceBlockData.java b/src/main/java/ru/windcorp/progressia/test/ControlPlaceBlockData.java new file mode 100644 index 0000000..82dc80b --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/ControlPlaceBlockData.java @@ -0,0 +1,22 @@ +package ru.windcorp.progressia.test; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.comms.controls.ControlData; + +public class ControlPlaceBlockData extends ControlData { + + private final Vec3i blockInWorld = new Vec3i(); + + public ControlPlaceBlockData(String id) { + super(id); + } + + public Vec3i getBlockInWorld() { + return blockInWorld; + } + + public void setBlockInWorld(Vec3i blockInWorld) { + this.blockInWorld.set(blockInWorld.x, blockInWorld.y, blockInWorld.z); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/LayerTestGUI.java b/src/main/java/ru/windcorp/progressia/test/LayerTestGUI.java new file mode 100755 index 0000000..2ae9828 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/LayerTestGUI.java @@ -0,0 +1,155 @@ +/******************************************************************************* + * Progressia + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.progressia.test; + +import java.util.ArrayList; +import java.util.Collection; + +import ru.windcorp.progressia.client.ClientState; +import ru.windcorp.progressia.client.graphics.font.Font; +import ru.windcorp.progressia.client.graphics.gui.GUILayer; +import ru.windcorp.progressia.client.graphics.gui.Label; +import ru.windcorp.progressia.client.graphics.gui.Panel; +import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign; +import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical; + +public class LayerTestGUI extends GUILayer { + + public LayerTestGUI() { + super("LayerTestGui", new LayoutAlign(0, 1, 5)); + + Panel panel = new Panel("ControlDisplays", new LayoutVertical(5)); + + Collection