Merge branch 'master' into audio

This commit is contained in:
Евгений Смирнов 2020-11-22 14:34:03 +03:00
commit 104d64ff9a
142 changed files with 4769 additions and 1684 deletions

View File

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

0
gradlew vendored Normal file → Executable file
View File

View File

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

View File

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

View File

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

View File

@ -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() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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) {
int width = getWidth(declar);
checkDeclaration(declar, width);
try {
char c = getChar(declar);
int width = getWidth(declar);
checkDeclaration(declar, width);
TextureDataEditor editor = new TextureDataEditor(
width, GNUUnifont.HEIGHT,
width, GNUUnifont.HEIGHT,
TEXTURE_SETTINGS
);
char c = getChar(declar);
for (int y = 0; y < GNUUnifont.HEIGHT; ++y) {
for (int x = 0; x < width; ++x) {
int bit = x + y * width;
TextureDataEditor editor = new TextureDataEditor(width, GNUUnifont.HEIGHT, width, GNUUnifont.HEIGHT,
TEXTURE_SETTINGS);
editor.setPixel(
x, GNUUnifont.HEIGHT - y - 1,
getBit(declar, bit) ? 0xFFFFFFFF : 0x00000000
);
for (int y = 0; y < GNUUnifont.HEIGHT; ++y) {
for (int x = 0; x < width; ++x) {
int bit = x + y * width;
editor.setPixel(x, GNUUnifont.HEIGHT - y - 1, getBit(declar, bit) ? 0xFFFFFFFF : 0x00000000);
}
}
}
return new ParsedGlyph(c, editor);
return new ParsedGlyph(c, editor);
} catch (IOException e) {
CrashReports.report(e, "Could not load GNUUnifont: could not load character \"%s\"", declar);
return null;
}
}
private static char getChar(String declar) {
int result = 0;
for (int i = 0; i < 4; ++i) {
result =
(result << BITS_PER_HEX_DIGIT) |
getHexValue(declar.charAt(i));
result = (result << BITS_PER_HEX_DIGIT) | getHexValue(declar.charAt(i));
}
return (char) result;
@ -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() {
}
}

View File

@ -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,11 +345,12 @@ public class Component extends Named {
}
private Component getNextFocusCandidate(boolean canUseChildren) {
if (canUseChildren) synchronized (getChildren()) {
if (!getChildren().isEmpty()) {
return getChild(0);
if (canUseChildren)
synchronized (getChildren()) {
if (!getChildren().isEmpty()) {
return getChild(0);
}
}
}
Component parent = getParent();
if (parent != null) {
@ -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();
}
@ -505,27 +505,29 @@ public class Component extends Named {
public void dispatchInput(Input input) {
try {
switch (input.getTarget()) {
case FOCUSED:
dispatchInputToFocused(input);
break;
case HOVERED:
dispatchInputToHovered(input);
break;
case ALL:
default:
dispatchInputToAll(input);
break;
case FOCUSED:
dispatchInputToFocused(input);
break;
case HOVERED:
dispatchInputToHovered(input);
break;
case ALL:
default:
dispatchInputToAll(input);
break;
}
} catch (Exception e) {
throw new RuntimeException(e);
CrashReports.report(e, "Could not dispatch input to Component %s", this);
}
}
private void dispatchInputToFocused(Input input) {
Component c = findFocused();
if (c == null) return;
if (attemptFocusTransfer(input, c)) return;
if (c == null)
return;
if (attemptFocusTransfer(input, c))
return;
while (c != null) {
c.handleInput(input);
@ -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);
// }
}

View File

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

View File

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

View File

@ -31,28 +31,25 @@ 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
"GLFW_MOUSE_BUTTON_3" // Alias for MIDDLE
));
static {
@ -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('_', ' ');

View File

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

View File

@ -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;
@ -87,7 +82,7 @@ public class Atlases {
// Wrapping
nextY += rowHeight; // Move to next row
rowHeight = height; // Next row is at least 'height' high
nextX = 0; // Start the row over
nextX = 0; // Start the row over
} else {
// Not wrapping
@ -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() {
}
}

View File

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

View File

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

View File

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

View File

@ -272,6 +272,10 @@ public class Camera {
}
}
public int getCurrentModeIndex() {
return currentModeIndex;
}
public float getLastAnchorYaw() {
return lastAnchorYaw;
}

View File

@ -20,45 +20,36 @@ package ru.windcorp.progressia.client.graphics.world;
import java.util.ArrayList;
import java.util.List;
import org.lwjgl.glfw.GLFW;
import glm.Glm;
import glm.mat._3.Mat3;
import glm.vec._2.Vec2;
import glm.mat._4.Mat4;
import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.Client;
import ru.windcorp.progressia.client.ClientState;
import ru.windcorp.progressia.client.comms.controls.InputBasedControls;
import ru.windcorp.progressia.client.graphics.Layer;
import ru.windcorp.progressia.client.graphics.backend.FaceCulling;
import ru.windcorp.progressia.client.graphics.backend.GraphicsBackend;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.input.CursorMoveEvent;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.graphics.input.bus.Input;
import ru.windcorp.progressia.common.collision.AABB;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
import ru.windcorp.progressia.client.graphics.model.Shapes.PppBuilder;
import ru.windcorp.progressia.client.graphics.model.StaticModel;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.common.Units;
import ru.windcorp.progressia.common.collision.Collideable;
import ru.windcorp.progressia.common.collision.CollisionClock;
import ru.windcorp.progressia.common.collision.CollisionModel;
import ru.windcorp.progressia.common.collision.CompoundCollisionModel;
import ru.windcorp.progressia.common.collision.colliders.Collider;
import ru.windcorp.progressia.common.util.FloatMathUtils;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.test.AABBRenderer;
import ru.windcorp.progressia.test.CollisionModelRenderer;
import ru.windcorp.progressia.test.TestPlayerControls;
public class LayerWorld extends Layer {
private final Mat3 angMat = new Mat3();
private int movementForward = 0;
private int movementRight = 0;
private int movementUp = 0;
private final WorldRenderHelper helper = new WorldRenderHelper();
private final Client client;
private final InputBasedControls inputBasedControls;
private final TestPlayerControls tmp_testControls = TestPlayerControls.getInstance();
public LayerWorld(Client client) {
super("World");
@ -79,38 +70,14 @@ public class LayerWorld extends Layer {
@Override
protected void doRender() {
if (client.getLocalPlayer() != null) {
tmp_handleControls();
}
Camera camera = client.getCamera();
if (camera.hasAnchor()) {
renderWorld();
}
}
private void tmp_handleControls() {
EntityData player = client.getLocalPlayer();
angMat.identity().rotateZ(player.getYaw());
Vec3 movement = Vectors.grab3();
// Horizontal and vertical max control speed
final float movementSpeed = 0.1f * 60.0f;
// (0; 1], 1 is instant change, 0 is no control authority
final float controlAuthority = 0.1f;
movement.set(movementForward, -movementRight, 0);
if (movementForward != 0 && movementRight != 0) movement.normalize();
angMat.mul_(movement); // bug in jglm, .mul() and mul_() are swapped
movement.z = movementUp;
movement.mul(movementSpeed);
movement.sub(player.getVelocity());
movement.mul(controlAuthority);
player.getVelocity().add(movement);
Vectors.release(movement);
if (client.getLocalPlayer() != null) {
client.getLocalPlayer().update(client.getWorld());
}
}
private void renderWorld() {
@ -128,150 +95,117 @@ public class LayerWorld extends Layer {
private final Collider.ColliderWorkspace tmp_colliderWorkspace = new Collider.ColliderWorkspace();
private final List<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) {
for (EntityData data : this.client.getWorld().getData().getEntities()) {
CollisionModel model = data.getCollisionModel();
if (model instanceof AABB) {
AABBRenderer.renderAABB((AABB) model, helper);
} else if (model instanceof CompoundCollisionModel) {
AABBRenderer.renderAABBsInCompound((CompoundCollisionModel) model, helper);
}
}
}
tmp_performCollisions(tickLength);
tmp_drawSelectionBox();
tmp_collideableList.clear();
tmp_collideableList.addAll(this.client.getWorld().getData().getEntities());
Collider.performCollisions(
tmp_collideableList,
new CollisionClock() {
private float t = 0;
@Override
public float getTime() {
return t;
}
@Override
public void advanceTime(float change) {
t += change;
}
},
(float) GraphicsInterface.getFrameLength(),
tmp_colliderWorkspace
);
final float frictionCoeff = 1 - 1e-2f;
tmp_testControls.applyPlayerControls();
for (EntityData data : this.client.getWorld().getData().getEntities()) {
data.getVelocity().mul(frictionCoeff);
tmp_applyFriction(data);
tmp_applyGravity(data, tickLength);
tmp_renderCollisionModel(data);
}
} catch (Exception e) {
} catch (Throwable e) {
e.printStackTrace();
System.err.println("OLEGSHA is to blame. Tell him he vry stupiDD!!");
System.exit(31337);
}
}
private void tmp_renderCollisionModel(EntityData entity) {
if (RENDER_COLLISION_MODELS) {
CollisionModelRenderer.renderCollisionModel(entity.getCollisionModel(), helper);
}
}
private void tmp_performCollisions(float tickLength) {
tmp_collideableList.clear();
tmp_collideableList.addAll(this.client.getWorld().getData().getEntities());
Collider.performCollisions(
tmp_collideableList,
this.client.getWorld().getData(),
tickLength,
tmp_colliderWorkspace
);
}
private static final Renderable SELECTION_BOX = tmp_createSelectionBox();
private void tmp_drawSelectionBox() {
LocalPlayer player = client.getLocalPlayer();
if (player == null) return;
Vec3i selection = player.getSelection().getBlock();
if (selection == null) return;
helper.pushTransform().translate(selection.x, selection.y, selection.z);
SELECTION_BOX.render(helper);
helper.popTransform();
}
private static Renderable tmp_createSelectionBox() {
StaticModel.Builder b = StaticModel.builder();
ShapeRenderProgram p = WorldRenderProgram.getDefault();
final float f = 1e-2f;
final float scale = 1 - f/2;
final Vec3 color = new Vec3(1, 1, 1).mul(0);
for (float phi = 0; phi < 2*FloatMathUtils.PI_F; phi += FloatMathUtils.PI_F/2) {
Mat4 rot = new Mat4().identity().rotateZ(phi).scale(scale);
b.addPart(new PppBuilder(p, (Texture) null).setOrigin(
new Vec3(-f - 0.5f, -f - 0.5f, -f - 0.5f)
).setSize(f, f, 2*f + 1).setColorMultiplier(color).create(), rot);
b.addPart(new PppBuilder(p, (Texture) null).setOrigin(
new Vec3(-f - 0.5f, - 0.5f, -f - 0.5f)
).setSize(f, 1, f).setColorMultiplier(color).create(), rot);
b.addPart(new PppBuilder(p, (Texture) null).setOrigin(
new Vec3(-f - 0.5f, - 0.5f, + 0.5f)
).setSize(f, 1, f).setColorMultiplier(color).create(), rot);
}
return new StaticModel(b);
}
private void tmp_applyFriction(EntityData entity) {
final float frictionCoeff = 1 - 1e-5f;
entity.getVelocity().mul(frictionCoeff);
}
private void tmp_applyGravity(EntityData entity, float tickLength) {
if (ClientState.getInstance().getLocalPlayer().getEntity() == entity && tmp_testControls.isFlying()) {
return;
}
final float gravitationalAcceleration;
if (tmp_testControls.useMinecraftGravity()) {
gravitationalAcceleration = 32f * Units.METERS_PER_SECOND_SQUARED; // plz dont sue me M$
} else {
gravitationalAcceleration = 9.81f * Units.METERS_PER_SECOND_SQUARED;
}
entity.getVelocity().add(0, 0, -gravitationalAcceleration * tickLength);
}
@Override
protected void handleInput(Input input) {
if (input.isConsumed()) return;
InputEvent event = input.getEvent();
if (event instanceof KeyEvent) {
if (onKeyEvent((KeyEvent) event)) {
input.consume();
}
} else if (event instanceof CursorMoveEvent) {
onMouseMoved((CursorMoveEvent) event);
input.consume();
}
tmp_testControls.handleInput(input);
if (!input.isConsumed()) {
inputBasedControls.handleInput(input);
}
}
private boolean flag = true;
private boolean onKeyEvent(KeyEvent event) {
if (event.isRepeat()) return false;
int multiplier = event.isPress() ? 1 : -1;
switch (event.getKey()) {
case GLFW.GLFW_KEY_W:
movementForward += +1 * multiplier;
break;
case GLFW.GLFW_KEY_S:
movementForward += -1 * multiplier;
break;
case GLFW.GLFW_KEY_A:
movementRight += -1 * multiplier;
break;
case GLFW.GLFW_KEY_D:
movementRight += +1 * multiplier;
break;
case GLFW.GLFW_KEY_SPACE:
movementUp += +1 * multiplier;
break;
case GLFW.GLFW_KEY_LEFT_SHIFT:
movementUp += -1 * multiplier;
break;
case GLFW.GLFW_KEY_ESCAPE:
if (!event.isPress()) return false;
if (flag) {
GLFW.glfwSetInputMode(GraphicsBackend.getWindowHandle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_NORMAL);
} else {
GLFW.glfwSetInputMode(GraphicsBackend.getWindowHandle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED);
}
flag = !flag;
break;
case GLFW.GLFW_KEY_F5:
if (!event.isPress()) return false;
if (client.getCamera().hasAnchor()) {
client.getCamera().selectNextMode();
}
break;
default:
return false;
}
return true;
}
private void onMouseMoved(CursorMoveEvent event) {
if (!flag) return;
final float yawScale = -0.002f;
final float pitchScale = yawScale;
EntityData player = client.getLocalPlayer();
if (player != null) {
normalizeAngles(player.getDirection().add(
(float) (event.getChangeX() * yawScale),
(float) (event.getChangeY() * pitchScale)
));
}
}
private void normalizeAngles(Vec2 dir) {
// Normalize yaw
dir.x = FloatMathUtils.normalizeAngle(dir.x);
// Clamp pitch
dir.y = Glm.clamp(
dir.y, -FloatMathUtils.PI_F/2, +FloatMathUtils.PI_F/2
);
}
}

View File

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

View File

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

View File

@ -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()) {

View File

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

View File

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

View File

@ -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() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -30,7 +30,7 @@ public class ChunkRenderOptimizers {
static {
register(ChunkRenderOptimizerSupplier.of(
"Default", "OpaqueCube",
"Default:OpaqueCube",
ChunkRenderOptimizerCube::new
));
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
}
}
bodyYaw = normalizeAngle(bodyYaw);
headPitch = Glm.clamp(
getData().getPitch(),
-head.maxPitch, head.maxPitch
);
public float getWalkingSwing() {
return walkingSwing;
}
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;
}
}

View File

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

View File

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

View File

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

View File

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

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

View File

@ -1,53 +1,71 @@
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(
float ox, float oy, float oz,
float ox, float oy, float oz,
float xSize, float ySize, float zSize
) {
this.origin.set(ox, oy, oz);
this.size.set(xSize, ySize, zSize);
for (CollisionWall wall : getFaces()) {
wall.moveOrigin(ox, oy, oz);
wall.getWidth().mul(xSize, ySize, zSize);
wall.getHeight().mul(xSize, ySize, zSize);
}
}
public Collection<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];
}
}

View File

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

View File

@ -1,8 +0,0 @@
package ru.windcorp.progressia.common.collision;
public interface CollisionClock {
float getTime();
void advanceTime(float change);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -35,7 +35,7 @@ extends AbstractStatefulObjectLayout {
}
@Override
public StateFieldBuilder getBuilder(String namespace, String name) {
public StateFieldBuilder getBuilder(String id) {
return new RetrieverStateFieldBuilder();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
);
}
if (!PART_CHECKER.test(namespace)) {
throw new IllegalArgumentException(
"Namespace \"" + namespace + "\" is invalid. "
+ "Allowed is: " + PART_REGEX
);
}
public Namespaced(String id) {
NamespacedUtil.checkId(id);
this.id = id;
}
public String getId() {
public final String getId() {
return id;
}
public String getNamespace() {
return namespace;
return NamespacedUtil.getNamespace(getId());
}
public String getName() {
return NamespacedUtil.getName(getId());
}
@Override
@ -75,15 +52,10 @@ public abstract class Namespaced extends Named {
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
Namespaced other = (Namespaced) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
if (!id.equals(other.id))
return false;
return true;
}

View File

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

View File

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

View File

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

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

View File

@ -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(
(float) Math.toRadians(40), (float) Math.toRadians(45)
));
getEntities().add(javapony);
if (!getPosition().any()) {
EntityData player = EntityDataRegistry.getInstance().create("Test:Player");
player.setEntityId(0x42);
player.setPosition(new Vec3(-6, -6, 20));
player.setDirection(new Vec2(
(float) Math.toRadians(40), (float) Math.toRadians(45)
));
getEntities().add(player);
EntityData statie = EntityDataRegistry.getInstance().create("Test:Statie");
statie.setEntityId(0xDEADBEEF);
statie.setPosition(new Vec3(0, 15, 16));
getEntities().add(statie);
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));
}
}

View File

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

View File

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

View File

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

View File

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