Merge branch 'master' into audio
This commit is contained in:
commit
104d64ff9a
25
build.gradle
25
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)
|
||||
|
@ -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) {
|
||||
@ -230,6 +278,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) {
|
||||
if (array[i] == element) {
|
||||
@ -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 <T extends Comparable<T>> 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;
|
||||
|
@ -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.
|
||||
* <p>
|
||||
* Indices {@code 0} and {@code src.length() - 1} produce {@code str} excluding
|
||||
* the specified character and {@code ""}.
|
||||
* <p>
|
||||
* @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.
|
||||
* <p>
|
||||
* Indices {@code 0} and {@code src.length() - 1} produce extra zero-length outputs.
|
||||
* Duplicate indices produce extra zero-length outputs.
|
||||
* <p>
|
||||
* Examples:
|
||||
* <pre>
|
||||
* 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"}
|
||||
* </pre>
|
||||
* @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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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());
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -1,15 +1,16 @@
|
||||
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 {
|
||||
@ -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,7 +38,11 @@ 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);
|
||||
@ -50,10 +51,6 @@ public class DefaultClientCommsListener implements CommsListener {
|
||||
));
|
||||
}
|
||||
|
||||
private void tmp_reassembleWorld() {
|
||||
getClient().getWorld().getChunks().forEach(ChunkRender::markForUpdate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIOError(IOException reason) {
|
||||
// TODO implement
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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<InputEvent> predicate;
|
||||
private final BiConsumer<InputEvent, ControlData> dataWriter;
|
||||
|
||||
public ControlTriggerLambda(
|
||||
String id,
|
||||
Predicate<InputEvent> predicate,
|
||||
BiConsumer<InputEvent, ControlData> 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;
|
||||
}
|
||||
|
||||
}
|
@ -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<KeyEvent> predicate;
|
||||
private final PacketControl packet;
|
||||
|
||||
public ControlTriggerOnKeyPress(
|
||||
String namespace, String name,
|
||||
Predicate<KeyEvent> 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;
|
||||
}
|
||||
|
||||
}
|
@ -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<ControlTrigger> {
|
||||
public class ControlTriggerRegistry extends NamespacedInstanceRegistry<ControlTrigger> {
|
||||
|
||||
private static final ControlTriggerRegistry INSTANCE =
|
||||
new ControlTriggerRegistry();
|
||||
|
@ -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<InputEvent, ControlData> dataWriter,
|
||||
Predicate<InputEvent> predicate
|
||||
) {
|
||||
return new ControlTriggerLambda(id, predicate, dataWriter);
|
||||
}
|
||||
|
||||
public static ControlTriggerInputBased of(
|
||||
String id,
|
||||
Consumer<ControlData> dataWriter,
|
||||
Predicate<InputEvent> predicate
|
||||
) {
|
||||
return of(
|
||||
id,
|
||||
(input, control) -> dataWriter.accept(control),
|
||||
predicate
|
||||
);
|
||||
}
|
||||
|
||||
public static ControlTriggerInputBased of(
|
||||
String id,
|
||||
Predicate<InputEvent> predicate
|
||||
) {
|
||||
return of(
|
||||
id,
|
||||
(input, control) -> {},
|
||||
predicate
|
||||
);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <I extends InputEvent> ControlTriggerInputBased of(
|
||||
String id,
|
||||
Class<I> inputType,
|
||||
BiConsumer<I, ControlData> dataWriter,
|
||||
Predicate<I>... predicates
|
||||
) {
|
||||
return of(
|
||||
id,
|
||||
createCheckedDataWriter(inputType, dataWriter),
|
||||
createCheckedCompoundPredicate(inputType, predicates)
|
||||
);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <I extends InputEvent> ControlTriggerInputBased of(
|
||||
String id,
|
||||
Class<I> inputType,
|
||||
Consumer<ControlData> dataWriter,
|
||||
Predicate<I>... predicates
|
||||
) {
|
||||
return of(
|
||||
id,
|
||||
inputType,
|
||||
(input, control) -> dataWriter.accept(control),
|
||||
predicates
|
||||
);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <I extends InputEvent> ControlTriggerInputBased of(
|
||||
String id,
|
||||
Class<I> inputType,
|
||||
Predicate<I>... predicates
|
||||
) {
|
||||
return of(
|
||||
id,
|
||||
(input, control) -> {},
|
||||
createCheckedCompoundPredicate(inputType, predicates)
|
||||
);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static ControlTriggerInputBased of(
|
||||
String id,
|
||||
BiConsumer<InputEvent, ControlData> dataWriter,
|
||||
Predicate<InputEvent>... predicates
|
||||
) {
|
||||
return of(
|
||||
id,
|
||||
InputEvent.class,
|
||||
dataWriter,
|
||||
predicates
|
||||
);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <I extends InputEvent> ControlTriggerInputBased of(
|
||||
String id,
|
||||
Consumer<ControlData> dataWriter,
|
||||
Predicate<InputEvent>... predicates
|
||||
) {
|
||||
return of(
|
||||
id,
|
||||
(input, control) -> dataWriter.accept(control),
|
||||
predicates
|
||||
);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static ControlTriggerInputBased of(
|
||||
String id,
|
||||
Predicate<InputEvent>... predicates
|
||||
) {
|
||||
return of(
|
||||
id,
|
||||
InputEvent.class,
|
||||
(input, control) -> {},
|
||||
predicates
|
||||
);
|
||||
}
|
||||
|
||||
private static
|
||||
<I extends InputEvent>
|
||||
BiConsumer<InputEvent, ControlData>
|
||||
createCheckedDataWriter(
|
||||
Class<I> inputType,
|
||||
BiConsumer<I, ControlData> dataWriter
|
||||
) {
|
||||
return (inputEvent, control) -> dataWriter.accept(inputType.cast(inputEvent), control);
|
||||
}
|
||||
|
||||
private static
|
||||
<I extends InputEvent>
|
||||
Predicate<InputEvent>
|
||||
createCheckedCompoundPredicate(
|
||||
Class<I> inputType,
|
||||
Predicate<I>[] predicates
|
||||
) {
|
||||
return new CompoundCastPredicate<>(inputType, predicates);
|
||||
}
|
||||
|
||||
private static class CompoundCastPredicate<I extends InputEvent> implements Predicate<InputEvent> {
|
||||
|
||||
private final Class<I> inputType;
|
||||
private final Predicate<I>[] predicates;
|
||||
|
||||
public CompoundCastPredicate(Class<I> inputType, Predicate<I>[] 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<I> predicate : predicates) {
|
||||
if (!predicate.test(castEvent)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private ControlTriggers() {}
|
||||
|
||||
}
|
@ -24,6 +24,7 @@ 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 {
|
||||
|
||||
@ -39,7 +40,7 @@ public class Program implements OpenGLDeletable {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,12 +26,12 @@ 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;
|
||||
|
||||
@ -79,7 +79,7 @@ public class Shader implements OpenGLDeletable {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@
|
||||
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 {
|
||||
|
||||
@ -26,7 +27,7 @@ public class Attribute {
|
||||
|
||||
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;
|
||||
|
@ -18,6 +18,7 @@
|
||||
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 {
|
||||
|
||||
@ -26,7 +27,7 @@ public class Uniform {
|
||||
|
||||
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;
|
||||
|
@ -21,14 +21,13 @@ 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 AtlasGroup ATLAS_GROUP_GNU_UNIFONT = new AtlasGroup("GNUUnifont", 1 << 12);
|
||||
|
||||
private static final TextureSettings TEXTURE_SETTINGS =
|
||||
new TextureSettings(false);
|
||||
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();
|
||||
@ -55,29 +54,17 @@ public class GNUUnifontLoader {
|
||||
|
||||
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<String> createStream(BufferedReader reader) {
|
||||
@ -85,38 +72,37 @@ public class GNUUnifontLoader {
|
||||
}
|
||||
|
||||
private static ParsedGlyph parse(String declar) {
|
||||
try {
|
||||
|
||||
int width = getWidth(declar);
|
||||
checkDeclaration(declar, width);
|
||||
|
||||
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
|
||||
);
|
||||
editor.setPixel(x, GNUUnifont.HEIGHT - y - 1, getBit(declar, bit) ? 0xFFFFFFFF : 0x00000000);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@ -138,35 +124,39 @@ public class GNUUnifontLoader {
|
||||
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)");
|
||||
}
|
||||
|
||||
@ -175,11 +165,8 @@ public class GNUUnifontLoader {
|
||||
return new AtlasGlyph(glyph.c, new SimpleTexture(sprite));
|
||||
}
|
||||
|
||||
private static
|
||||
Collector<AtlasGlyph, ?, TCharObjectMap<Texture>>
|
||||
createMapper() {
|
||||
return Collector.of(
|
||||
TCharObjectHashMap<Texture>::new,
|
||||
private static Collector<AtlasGlyph, ?, TCharObjectMap<Texture>> createMapper() {
|
||||
return Collector.of(TCharObjectHashMap<Texture>::new,
|
||||
|
||||
(map, glyph) -> map.put(glyph.c, glyph.texture),
|
||||
|
||||
@ -188,10 +175,10 @@ public class GNUUnifontLoader {
|
||||
return a;
|
||||
},
|
||||
|
||||
Characteristics.UNORDERED
|
||||
);
|
||||
Characteristics.UNORDERED);
|
||||
}
|
||||
|
||||
private GNUUnifontLoader() {}
|
||||
private GNUUnifontLoader() {
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -39,11 +39,11 @@ 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<Component> children =
|
||||
Collections.synchronizedList(new CopyOnWriteArrayList<>());
|
||||
private final List<Component> children = Collections.synchronizedList(new CopyOnWriteArrayList<>());
|
||||
|
||||
private Component parent = null;
|
||||
|
||||
@ -88,7 +88,8 @@ public class Component extends Named {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -107,7 +108,8 @@ public class Component extends Named {
|
||||
}
|
||||
|
||||
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);
|
||||
@ -123,7 +125,8 @@ public class Component extends Named {
|
||||
}
|
||||
|
||||
public Component addChild(Component child, int index) {
|
||||
if (index == -1) index = getChildren().size();
|
||||
if (index == -1)
|
||||
index = getChildren().size();
|
||||
|
||||
invalidate();
|
||||
getChildren().add(index, child);
|
||||
@ -232,7 +235,7 @@ public class Component extends Named {
|
||||
|
||||
valid = true;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
CrashReports.report(e, "Could not layout Component %s", this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -245,7 +248,7 @@ public class Component extends Named {
|
||||
try {
|
||||
return getLayout().calculatePreferredSize(this);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
CrashReports.report(e, "Could not calculate preferred size for Component %s", this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -342,7 +345,8 @@ public class Component extends Named {
|
||||
}
|
||||
|
||||
private Component getNextFocusCandidate(boolean canUseChildren) {
|
||||
if (canUseChildren) synchronized (getChildren()) {
|
||||
if (canUseChildren)
|
||||
synchronized (getChildren()) {
|
||||
if (!getChildren().isEmpty()) {
|
||||
return getChild(0);
|
||||
}
|
||||
@ -458,20 +462,19 @@ public class Component extends Named {
|
||||
}
|
||||
|
||||
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 <T extends InputEvent> void addListener(
|
||||
Class<? extends T> type,
|
||||
boolean handlesConsumed,
|
||||
InputListener<T> listener
|
||||
) {
|
||||
public <T extends InputEvent> void addListener(Class<? extends T> type, boolean handlesConsumed,
|
||||
InputListener<T> listener) {
|
||||
if (inputBus == null) {
|
||||
inputBus = new InputBus();
|
||||
}
|
||||
@ -479,10 +482,7 @@ public class Component extends Named {
|
||||
inputBus.register(type, handlesConsumed, listener);
|
||||
}
|
||||
|
||||
public <T extends InputEvent> void addListener(
|
||||
Class<? extends T> type,
|
||||
InputListener<T> listener
|
||||
) {
|
||||
public <T extends InputEvent> void addListener(Class<? extends T> type, InputListener<T> listener) {
|
||||
if (inputBus == null) {
|
||||
inputBus = new InputBus();
|
||||
}
|
||||
@ -517,15 +517,17 @@ public class Component extends Named {
|
||||
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);
|
||||
@ -555,8 +557,10 @@ 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();
|
||||
|
||||
@ -574,16 +578,11 @@ public class Component extends Named {
|
||||
}
|
||||
|
||||
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() {
|
||||
@ -610,7 +609,7 @@ public class Component extends Named {
|
||||
try {
|
||||
assembleSelf(target);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
CrashReports.report(e, "Could not assemble Component %s", this);
|
||||
}
|
||||
|
||||
assembleChildren(target);
|
||||
@ -618,7 +617,7 @@ public class Component extends Named {
|
||||
try {
|
||||
postAssembleSelf(target);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
CrashReports.report(e, "Post-assembly failed for Component %s", this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -634,28 +633,28 @@ public class Component extends Named {
|
||||
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);
|
||||
// }
|
||||
|
||||
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*******************************************************************************/
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
@ -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<KeyEvent> matcher() {
|
||||
return build()::matches;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -31,26 +31,23 @@ 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<String> CODES_TO_NAMES =
|
||||
new TIntObjectHashMap<>();
|
||||
private static final TIntObjectMap<String> CODES_TO_NAMES = new TIntObjectHashMap<>();
|
||||
|
||||
private static final TObjectIntMap<String> NAMES_TO_CODES =
|
||||
new TObjectIntHashMap<>();
|
||||
private static final TObjectIntMap<String> 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<String> 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<String> 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
|
||||
));
|
||||
@ -63,28 +60,28 @@ public class Keys {
|
||||
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 (!name.startsWith(KEY_PREFIX) && !name.startsWith(MOUSE_BUTTON_PREFIX))
|
||||
continue;
|
||||
|
||||
if (IGNORE_FIELDS.contains(name)) 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();
|
||||
@ -97,11 +94,8 @@ public class Keys {
|
||||
}
|
||||
|
||||
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);
|
||||
@ -125,8 +119,7 @@ public class Keys {
|
||||
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('_', ' ');
|
||||
|
||||
|
@ -122,4 +122,13 @@ 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 }
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ 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 {
|
||||
|
||||
@ -24,10 +25,7 @@ public class Atlases {
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,11 +69,8 @@ public class Atlases {
|
||||
|
||||
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;
|
||||
|
||||
@ -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) {
|
||||
@ -152,10 +144,8 @@ public class Atlases {
|
||||
|
||||
private static final TextureSettings SETTINGS = new TextureSettings(false);
|
||||
|
||||
private static final Map<Resource, Sprite> LOADED =
|
||||
new HashMap<>();
|
||||
private static final Multimap<AtlasGroup, Atlas> ATLASES =
|
||||
MultimapBuilder.hashKeys().arrayListValues().build();
|
||||
private static final Map<Resource, Sprite> LOADED = new HashMap<>();
|
||||
private static final Multimap<AtlasGroup, Atlas> ATLASES = MultimapBuilder.hashKeys().arrayListValues().build();
|
||||
|
||||
public static Sprite getSprite(Resource resource, AtlasGroup group) {
|
||||
return LOADED.computeIfAbsent(resource, k -> loadSprite(k, group));
|
||||
@ -163,12 +153,12 @@ public class Atlases {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -180,14 +170,11 @@ public class Atlases {
|
||||
private static Atlas getReadyAtlas(AtlasGroup group, TextureData data) {
|
||||
List<Atlas> atlases = (List<Atlas>) 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);
|
||||
@ -202,6 +189,7 @@ public class Atlases {
|
||||
});
|
||||
}
|
||||
|
||||
private Atlases() {}
|
||||
private Atlases() {
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -6,6 +6,7 @@ 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 {
|
||||
|
||||
@ -30,7 +31,8 @@ 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ 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 {
|
||||
|
||||
@ -89,7 +90,7 @@ public class TexturePrimitive implements OpenGLDeletable {
|
||||
OpenGLObjectTracker.register(this);
|
||||
|
||||
if (handle < 0) {
|
||||
throw new RuntimeException("oops");
|
||||
CrashReports.report(null, "Failed to allocate texture");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -272,6 +272,10 @@ public class Camera {
|
||||
}
|
||||
}
|
||||
|
||||
public int getCurrentModeIndex() {
|
||||
return currentModeIndex;
|
||||
}
|
||||
|
||||
public float getLastAnchorYaw() {
|
||||
return lastAnchorYaw;
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
if (client.getLocalPlayer() != null) {
|
||||
client.getLocalPlayer().update(client.getWorld());
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private void renderWorld() {
|
||||
@ -128,150 +95,117 @@ public class LayerWorld extends Layer {
|
||||
private final Collider.ColliderWorkspace tmp_colliderWorkspace = new Collider.ColliderWorkspace();
|
||||
private final List<Collideable> 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) {
|
||||
tmp_performCollisions(tickLength);
|
||||
tmp_drawSelectionBox();
|
||||
|
||||
tmp_testControls.applyPlayerControls();
|
||||
|
||||
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_applyFriction(data);
|
||||
tmp_applyGravity(data, tickLength);
|
||||
tmp_renderCollisionModel(data);
|
||||
}
|
||||
} 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,
|
||||
new CollisionClock() {
|
||||
private float t = 0;
|
||||
@Override
|
||||
public float getTime() {
|
||||
return t;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void advanceTime(float change) {
|
||||
t += change;
|
||||
}
|
||||
},
|
||||
(float) GraphicsInterface.getFrameLength(),
|
||||
this.client.getWorld().getData(),
|
||||
tickLength,
|
||||
tmp_colliderWorkspace
|
||||
);
|
||||
|
||||
final float frictionCoeff = 1 - 1e-2f;
|
||||
|
||||
for (EntityData data : this.client.getWorld().getData().getEntities()) {
|
||||
data.getVelocity().mul(frictionCoeff);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.exit(31337);
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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<WeakReference<LocaleListener>> 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<WeakReference<LocaleListener>> iterator = listeners.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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() {
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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<BlockRender> {
|
||||
public class BlockRenderRegistry extends NamespacedInstanceRegistry<BlockRender> {
|
||||
|
||||
private static final BlockRenderRegistry INSTANCE =
|
||||
new BlockRenderRegistry();
|
||||
|
@ -36,12 +36,12 @@ implements OpaqueCube {
|
||||
private final Map<BlockFace, Texture> 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);
|
||||
|
@ -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
|
||||
|
@ -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<ChunkRenderOptimizer> supplier
|
||||
) {
|
||||
return new ChunkRenderOptimizerSupplier(namespace, name) {
|
||||
return new ChunkRenderOptimizerSupplier(id) {
|
||||
@Override
|
||||
public ChunkRenderOptimizer createOptimizer() {
|
||||
return supplier.get();
|
||||
|
@ -30,7 +30,7 @@ public class ChunkRenderOptimizers {
|
||||
|
||||
static {
|
||||
register(ChunkRenderOptimizerSupplier.of(
|
||||
"Default", "OpaqueCube",
|
||||
"Default:OpaqueCube",
|
||||
ChunkRenderOptimizerCube::new
|
||||
));
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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<EntityRender> {
|
||||
public class EntityRenderRegistry extends NamespacedInstanceRegistry<EntityRender> {
|
||||
|
||||
private static final EntityRenderRegistry INSTANCE =
|
||||
new EntityRenderRegistry();
|
||||
@ -28,7 +29,8 @@ public class EntityRenderRegistry extends NamespacedRegistry<EntityRender> {
|
||||
).getData()
|
||||
);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
CrashReports.report(e, "Could not load entity texture %s", name);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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,34 +24,20 @@ 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();
|
||||
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 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;
|
||||
}
|
||||
public float getWalkingSwing() {
|
||||
return walkingSwing;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private void evaluateVelocityCoeff() {
|
||||
if (velocity * velocityCutoff > 1) {
|
||||
velocityCoeff = 1;
|
||||
} else {
|
||||
velocityCoeff = velocity * velocityCutoff;
|
||||
velocityCoeff *= velocityCoeff;
|
||||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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<TileRender> {
|
||||
public class TileRenderRegistry extends NamespacedInstanceRegistry<TileRender> {
|
||||
|
||||
private static final TileRenderRegistry INSTANCE = new TileRenderRegistry();
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
56
src/main/java/ru/windcorp/progressia/common/Units.java
Normal file
56
src/main/java/ru/windcorp/progressia/common/Units.java
Normal file
@ -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;
|
||||
|
||||
}
|
@ -1,35 +1,63 @@
|
||||
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
|
||||
* <a href="https://en.wikipedia.org/wiki/Minimum_bounding_box#Axis-aligned_minimum_bounding_box">Axis-Aligned Bounding Box</a>.
|
||||
* @author javapony
|
||||
*/
|
||||
public class AABB implements AABBoid {
|
||||
|
||||
private final Map<BlockFace, CollisionWall> 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),
|
||||
private class AABBWallImpl implements Wall {
|
||||
|
||||
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)
|
||||
);
|
||||
private final Vec3 originOffset = new Vec3();
|
||||
private final Vec3 widthSelector = new Vec3();
|
||||
private final Vec3 heightSelector = new Vec3();
|
||||
|
||||
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(
|
||||
@ -38,16 +66,6 @@ public class AABB implements CollisionModel {
|
||||
) {
|
||||
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<CollisionWall> getFaces() {
|
||||
return faces.values();
|
||||
}
|
||||
|
||||
public Vec3 getOrigin() {
|
||||
@ -55,20 +73,17 @@ public class AABB implements CollisionModel {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOrigin(Vec3 origin) {
|
||||
for (CollisionWall wall : getFaces()) {
|
||||
wall.getOrigin().sub(this.origin).add(origin);
|
||||
public void getOrigin(Vec3 output) {
|
||||
output.set(origin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOrigin(Vec3 origin) {
|
||||
this.origin.set(origin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveOrigin(Vec3 displacement) {
|
||||
for (CollisionWall wall : getFaces()) {
|
||||
wall.getOrigin().add(displacement);
|
||||
}
|
||||
|
||||
this.origin.add(displacement);
|
||||
}
|
||||
|
||||
@ -76,18 +91,23 @@ public class AABB implements CollisionModel {
|
||||
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];
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package ru.windcorp.progressia.common.collision;
|
||||
|
||||
public interface CollisionClock {
|
||||
|
||||
float getTime();
|
||||
void advanceTime(float change);
|
||||
|
||||
}
|
@ -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<Vec3i> 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<Vec3i> 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<Vec3i> 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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -8,9 +8,9 @@ import glm.vec._3.Vec3;
|
||||
|
||||
public class CompoundCollisionModel implements CollisionModel {
|
||||
|
||||
private final Collection<CollisionModel> models;
|
||||
private final Collection<? extends CollisionModel> models;
|
||||
|
||||
public CompoundCollisionModel(Collection<CollisionModel> models) {
|
||||
public CompoundCollisionModel(Collection<? extends CollisionModel> models) {
|
||||
this.models = models;
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ public class CompoundCollisionModel implements CollisionModel {
|
||||
this(ImmutableList.copyOf(models));
|
||||
}
|
||||
|
||||
public Collection<CollisionModel> getModels() {
|
||||
public Collection<? extends CollisionModel> getModels() {
|
||||
return models;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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<TranslatedAABB> activeBlockModels = new ArrayList<>();
|
||||
private final CollisionModel model = new CompoundCollisionModel(activeBlockModels);
|
||||
private final LowOverheadCache<TranslatedAABB> 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;
|
||||
}
|
||||
|
||||
}
|
@ -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() {}
|
||||
|
||||
}
|
@ -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,
|
||||
* <p>
|
||||
* When {@linkplain #advanceTime(Collection, Collision, WorldData, float) advancing time},
|
||||
* time step for all entities <em>except</em> currently colliding bodies is the current
|
||||
* collisions's timestamp relative to now. However, currently colliding bodies
|
||||
* (Collision.a and Collision.b) have a smaller time step. This is done to make sure
|
||||
* they don't intersect due to rounding errors.
|
||||
* <p>
|
||||
* Today I learned that bad code has nothing to do with friendship, although lemme tell ya:
|
||||
* it's got some dank magic.
|
||||
* <p>
|
||||
* Your faithful student,<br />
|
||||
* Kostyl.
|
||||
*/
|
||||
private static final float TIME_STEP_COEFFICIENT_FOR_CURRENTLY_COLLIDING_BODIES = 1e-1f;
|
||||
|
||||
public static void performCollisions(
|
||||
List<? extends Collideable> 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<? extends Collideable> 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<? extends Collideable> 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<? extends Collideable> 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);
|
||||
}
|
||||
|
||||
@ -329,6 +335,8 @@ public class Collider {
|
||||
|
||||
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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<ControlData> {
|
||||
public class ControlDataRegistry extends NamespacedFactoryRegistry<ControlData> {
|
||||
|
||||
private static final ControlDataRegistry INSTANCE = new ControlDataRegistry();
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -30,6 +30,7 @@ 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 {
|
||||
|
||||
@ -50,7 +51,8 @@ public class Resource extends Named {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,7 +61,8 @@ public class Resource extends Named {
|
||||
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) {
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -35,7 +35,7 @@ extends AbstractStatefulObjectLayout {
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateFieldBuilder getBuilder(String namespace, String name) {
|
||||
public StateFieldBuilder getBuilder(String id) {
|
||||
return new RetrieverStateFieldBuilder();
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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++;
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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<T extends StatefulObject> {
|
||||
|
||||
private final AtomicBoolean isRegistered = new AtomicBoolean(false);
|
||||
|
||||
public Type(String namespace, String name, Factory<T> factory) {
|
||||
super(namespace, name);
|
||||
public Type(String id, Factory<T> factory) {
|
||||
super(id);
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
@ -45,8 +45,8 @@ public class StatefulObjectRegistry<T extends StatefulObject> {
|
||||
|
||||
}
|
||||
|
||||
private final NamespacedRegistry<Type<T>> registry =
|
||||
new NamespacedRegistry<Type<T>>() {
|
||||
private final NamespacedInstanceRegistry<Type<T>> registry =
|
||||
new NamespacedInstanceRegistry<Type<T>>() {
|
||||
@Override
|
||||
public void register(Type<T> element) {
|
||||
super.register(element);
|
||||
@ -91,8 +91,8 @@ public class StatefulObjectRegistry<T extends StatefulObject> {
|
||||
return registry.get(id).build();
|
||||
}
|
||||
|
||||
public void register(String namespace, String name, Factory<T> factory) {
|
||||
registry.register(new Type<>(namespace, name, factory));
|
||||
public void register(String id, Factory<T> factory) {
|
||||
registry.register(new Type<>(id, factory));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<Class<?>, LowOverheadCache<?>> caches = new HashMap<>();
|
||||
|
||||
public <T> MultiLOC addClass(Class<T> clazz, Supplier<T> generator) {
|
||||
caches.put(clazz, new LowOverheadCache<>(generator));
|
||||
return this;
|
||||
}
|
||||
|
||||
public <T> T grab(Class<T> clazz) {
|
||||
return clazz.cast(caches.get(clazz).grab());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void release(Object obj) {
|
||||
((LowOverheadCache<Object>) caches.get(obj.getClass())).release(obj);
|
||||
}
|
||||
|
||||
}
|
@ -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<String> 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() {}
|
||||
|
||||
}
|
@ -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() {}
|
||||
|
||||
}
|
||||
|
@ -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<Vec3i> VEC3IS =
|
||||
new LowOverheadCache<>(Vec3i::new);
|
||||
|
||||
|
@ -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();
|
||||
|
||||
}
|
@ -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<String, String> 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();
|
||||
}
|
@ -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<ContextProvider> PROVIDERS = Collections.synchronizedCollection(new ArrayList<>());
|
||||
|
||||
private static final Collection<Analyzer> ANALYZERS = Collections.synchronizedCollection(new ArrayList<>());
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("crash");
|
||||
|
||||
/**
|
||||
* <em>This method never returns.</em>
|
||||
* <p>
|
||||
* 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<String, String> buf = new HashMap<>();
|
||||
provider.provideContext(buf);
|
||||
|
||||
if (!buf.isEmpty()) {
|
||||
output.append("Provider name: ").append(provider.getName()).append("\n");
|
||||
for (Map.Entry<String, String> 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");
|
||||
}
|
||||
}
|
@ -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";
|
||||
}
|
||||
}
|
@ -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<String, String> 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";
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -15,50 +15,27 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*******************************************************************************/
|
||||
package ru.windcorp.progressia.common.util;
|
||||
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 {
|
||||
|
||||
public abstract class Namespaced extends Named {
|
||||
|
||||
private static final char SEPARATOR = ':';
|
||||
|
||||
private static final String PART_REGEX = "^[A-Z][a-zA-Z0-9]{2,}$";
|
||||
|
||||
private static final Predicate<String> 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
|
||||
);
|
||||
public Namespaced(String id) {
|
||||
NamespacedUtil.checkId(id);
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
if (!PART_CHECKER.test(namespace)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Namespace \"" + namespace + "\" is invalid. "
|
||||
+ "Allowed is: " + PART_REGEX
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
@ -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<E extends Namespaced>
|
||||
implements Map<String, NamespacedFactoryRegistry.Factory<E>> {
|
||||
|
||||
@FunctionalInterface
|
||||
public static interface Factory<E> {
|
||||
E build(String id);
|
||||
}
|
||||
|
||||
private final Map<String, Factory<E>> backingMap =
|
||||
Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
private final Logger logger = LogManager.getLogger(getClass());
|
||||
|
||||
public void register(String id, Factory<E> 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<E> get(Object key) {
|
||||
return backingMap.get(key);
|
||||
}
|
||||
|
||||
public E create(String id) {
|
||||
Factory<E> 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<E> put(String id, Factory<E> factory) {
|
||||
register(id, factory);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<? extends String, ? extends Factory<E>> m) {
|
||||
synchronized (backingMap) {
|
||||
m.entrySet().forEach(e -> register(e.getKey(), e.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Factory<E> remove(Object key) {
|
||||
return backingMap.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
backingMap.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> keySet() {
|
||||
return backingMap.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Factory<E>> values() {
|
||||
return backingMap.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<String, Factory<E>>> entrySet() {
|
||||
return backingMap.entrySet();
|
||||
}
|
||||
|
||||
}
|
@ -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<E extends Namespaced>
|
||||
public class NamespacedInstanceRegistry<E extends Namespaced>
|
||||
implements Map<String, E> {
|
||||
|
||||
private final Map<String, E> 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<String, E> {
|
||||
@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<String, E> {
|
||||
@DoNotCall @Deprecated
|
||||
public void putAll(Map<? extends String, ? extends E> m) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Use NamespacedRegistry.registerAll(Collection<? extends E>)"
|
||||
"Use NamespacedInstanceRegistry.registerAll(Collection<? extends E>)"
|
||||
);
|
||||
}
|
||||
|
@ -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() {}
|
||||
|
||||
}
|
148
src/main/java/ru/windcorp/progressia/common/world/BlockRay.java
Normal file
148
src/main/java/ru/windcorp/progressia/common/world/BlockRay.java
Normal file
@ -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 <i>a</i> such that <i>a</i> > <i>c</i>.
|
||||
* @param c the number to compute strict ceiling of
|
||||
* @return the strict ceiling of <i>c</i>
|
||||
*/
|
||||
private static float strictCeil(float c) {
|
||||
return (float) (floor(c) + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a largest integer <i>a</i> such that <i>a</i> < <i>c</i>.
|
||||
* @param c the number to compute strict ceiling of
|
||||
* @return the strict ceiling of <i>c</i>
|
||||
*/
|
||||
private static float strictFloor(float c) {
|
||||
return (float) (ceil(c) - 1);
|
||||
}
|
||||
|
||||
}
|
@ -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<EntityData> entities =
|
||||
Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
public ChunkData(int x, int y, int z, WorldData world) {
|
||||
this.position.set(x, y, z);
|
||||
private final Collection<ChunkDataListener> listeners =
|
||||
Collections.synchronizedCollection(new ArrayList<>());
|
||||
|
||||
public ChunkData(Vec3i position, WorldData world) {
|
||||
this.position.set(position.x, position.y, position.z);
|
||||
this.world = world;
|
||||
|
||||
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(
|
||||
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(javapony);
|
||||
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<TileData> 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<ChunkDataListener> getListeners() {
|
||||
return listeners;
|
||||
}
|
||||
|
||||
public void addListener(ChunkDataListener listener) {
|
||||
this.listeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeListener(ChunkDataListener listener) {
|
||||
this.listeners.remove(listener);
|
||||
}
|
||||
|
||||
private static String str(Vec3i v) {
|
||||
return "(" + v.x + "; " + v.y + "; " + v.z + ")";
|
||||
}
|
||||
|
||||
protected void onLoaded() {
|
||||
getListeners().forEach(l -> l.onChunkLoaded(this));
|
||||
}
|
||||
|
||||
protected void beforeUnloaded() {
|
||||
getListeners().forEach(l -> l.beforeChunkUnloaded(this));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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) {}
|
||||
|
||||
}
|
@ -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<ChunkDataListener> listenerSupplier) {
|
||||
return new WorldDataListener() {
|
||||
@Override
|
||||
public void getChunkListeners(WorldData world, Vec3i chunk, Consumer<ChunkDataListener> chunkListenerSink) {
|
||||
chunkListenerSink.accept(listenerSupplier.get());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static WorldDataListener createAdder(ChunkDataListener listener) {
|
||||
return createAdder(() -> listener);
|
||||
}
|
||||
|
||||
private ChunkDataListeners() {}
|
||||
|
||||
}
|
@ -11,18 +11,17 @@ import glm.vec._3.i.Vec3i;
|
||||
* Three types of coordinates are used in Progressia:
|
||||
* <ul>
|
||||
*
|
||||
* <li><em>World coordinates</em>, in code referred to as {@code blockInWorld} -
|
||||
* <li id="blockInWorld"><em>World coordinates</em>, in code referred to as {@code blockInWorld} -
|
||||
* coordinates relative to world origin. Every block in the world has unique
|
||||
* world coordinates.</li>
|
||||
*
|
||||
* <li><em>Chunk coordinates</em>, in code referred to as {@code blockInChunk} -
|
||||
* <li id="blockInChunk"><em>Chunk coordinates</em>, 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 <tt>[0; {@link #BLOCKS_PER_CHUNK})
|
||||
* </tt>.</li>
|
||||
* reference. Chunk coordinates are always <tt>[0; {@link #BLOCKS_PER_CHUNK})</tt>.</li>
|
||||
*
|
||||
* <li><em>Coordinates of chunk</em>, in code referred to as {@code chunk} -
|
||||
* <li id="chunk"><em>Coordinates of chunk</em>, in code referred to as {@code chunk} -
|
||||
* chunk coordinates relative to world origin. Every chunk in the world has
|
||||
* unique coordinates of chunk.</li>
|
||||
*
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user