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' implementation 'ru.windcorp.fork.io.github.java-graphics:glm:1.0.1'
// log4j // log4j
compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.13.3' implementation 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-core', version: '2.13.3'
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
// See also LWJGL dependencies below
} }
/* /*
@ -77,3 +79,22 @@ dependencies {
} }
// LWJGL END // 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; 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) { public static int firstIndexOf(short[] array, short element) {
for (int i = 0; i < array.length; ++i) { for (int i = 0; i < array.length; ++i) {
if (array[i] == element) { if (array[i] == element) {
@ -107,6 +119,18 @@ public class ArrayUtil {
return -1; 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) { public static int firstIndexOf(int[] array, int element) {
for (int i = 0; i < array.length; ++i) { for (int i = 0; i < array.length; ++i) {
if (array[i] == element) { if (array[i] == element) {
@ -148,6 +172,18 @@ public class ArrayUtil {
return -1; 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) { public static int firstIndexOf(long[] array, long element) {
for (int i = 0; i < array.length; ++i) { for (int i = 0; i < array.length; ++i) {
if (array[i] == element) { if (array[i] == element) {
@ -189,6 +225,18 @@ public class ArrayUtil {
return -1; 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) { public static int firstIndexOf(float[] array, float element) {
for (int i = 0; i < array.length; ++i) { for (int i = 0; i < array.length; ++i) {
if (array[i] == element) { if (array[i] == element) {
@ -229,6 +277,18 @@ public class ArrayUtil {
return -1; 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) { public static int firstIndexOf(double[] array, double element) {
for (int i = 0; i < array.length; ++i) { for (int i = 0; i < array.length; ++i) {
@ -271,6 +331,18 @@ public class ArrayUtil {
return -1; 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) { public static int firstIndexOf(boolean[] array, boolean element) {
for (int i = 0; i < array.length; ++i) { for (int i = 0; i < array.length; ++i) {
if (array[i] == element) { if (array[i] == element) {
@ -340,6 +412,18 @@ public class ArrayUtil {
return -1; 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) { public static int firstIndexOf(Object[] array, Object element) {
for (int i = 0; i < array.length; ++i) { for (int i = 0; i < array.length; ++i) {
if (array[i] == element) { if (array[i] == element) {
@ -422,6 +506,20 @@ public class ArrayUtil {
return -1; 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) { public static long sum(byte[] array, int start, int length) {
long s = 0; long s = 0;
length += start; length += start;

View File

@ -26,8 +26,11 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import java.util.Objects;
import java.util.function.IntFunction; import java.util.function.IntFunction;
import ru.windcorp.jputil.ArrayUtil;
public class StringUtil { public class StringUtil {
private StringUtil() {} private StringUtil() {}
@ -368,6 +371,106 @@ public class StringUtil {
return result; 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) { private static IllegalArgumentException illegalArrayLength(int length) {
return new IllegalArgumentException("arrayLength must be non-negative (" + length + ")"); return new IllegalArgumentException("arrayLength must be non-negative (" + length + ")");
} }
@ -775,4 +878,35 @@ public class StringUtil {
else return (char) ('A' - 0xA + value); 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; 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 class ProgressiaLauncher {
public static void launch(String[] args, Proxy proxy) { public static void launch(String[] args, Proxy proxy) {
setupCrashReports();
proxy.initialize(); 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.DefaultClientCommsListener;
import ru.windcorp.progressia.client.comms.ServerCommsChannel; import ru.windcorp.progressia.client.comms.ServerCommsChannel;
import ru.windcorp.progressia.client.graphics.world.Camera; 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.client.world.WorldRender;
import ru.windcorp.progressia.common.world.WorldData; import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.common.world.entity.EntityData; import ru.windcorp.progressia.common.world.entity.EntityData;
@ -10,7 +11,7 @@ import ru.windcorp.progressia.common.world.entity.EntityData;
public class Client { public class Client {
private final WorldRender world; private final WorldRender world;
private EntityData localPlayer; private LocalPlayer localPlayer;
private final Camera camera = new Camera((float) Math.toRadians(70)); private final Camera camera = new Camera((float) Math.toRadians(70));
@ -27,12 +28,12 @@ public class Client {
return world; return world;
} }
public EntityData getLocalPlayer() { public LocalPlayer getLocalPlayer() {
return localPlayer; return localPlayer;
} }
public void setLocalPlayer(EntityData localPlayer) { public void setLocalPlayer(EntityData localPlayer) {
this.localPlayer = localPlayer; this.localPlayer = new LocalPlayer(localPlayer);
} }
public Camera getCamera() { 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.texture.Atlases;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram; import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.common.resource.ResourceManager; 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.server.ServerState;
import ru.windcorp.progressia.test.TestContent; import ru.windcorp.progressia.test.TestContent;
@ -39,8 +40,7 @@ public class ClientProxy implements Proxy {
RenderTaskQueue.waitAndInvoke(WorldRenderProgram::init); RenderTaskQueue.waitAndInvoke(WorldRenderProgram::init);
RenderTaskQueue.waitAndInvoke(() -> Typefaces.setDefault(GNUUnifontLoader.load(ResourceManager.getResource("assets/unifont-13.0.03.hex.gz")))); RenderTaskQueue.waitAndInvoke(() -> Typefaces.setDefault(GNUUnifontLoader.load(ResourceManager.getResource("assets/unifont-13.0.03.hex.gz"))));
} catch (InterruptedException e) { } catch (InterruptedException e) {
// TODO Auto-generated catch block CrashReports.report(e, "ClientProxy failed");
e.printStackTrace();
} }
TestContent.registerContent(); 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.comms.localhost.LocalServerCommsChannel;
import ru.windcorp.progressia.client.graphics.GUI; import ru.windcorp.progressia.client.graphics.GUI;
import ru.windcorp.progressia.client.graphics.flat.LayerTestUI; 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.client.graphics.world.LayerWorld;
import ru.windcorp.progressia.common.world.WorldData; import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.server.ServerState; import ru.windcorp.progressia.server.ServerState;
import ru.windcorp.progressia.test.LayerTestGUI;
public class ClientState { public class ClientState {
@ -30,6 +30,8 @@ public class ClientState {
Client client = new Client(world, channel); Client client = new Client(world, channel);
world.tmp_generate();
channel.connect(); channel.connect();
setInstance(client); setInstance(client);

View File

@ -26,5 +26,5 @@ public class ProgressiaClientMain {
ALTest.execute(); ALTest.execute();
ProgressiaLauncher.launch(args, new ClientProxy()); ProgressiaLauncher.launch(args, new ClientProxy());
} }
} }

View File

@ -1,19 +1,20 @@
package ru.windcorp.progressia.client.comms; package ru.windcorp.progressia.client.comms;
import java.io.IOException; import java.io.IOException;
import ru.windcorp.jputil.chars.StringUtil;
import ru.windcorp.progressia.client.Client; import ru.windcorp.progressia.client.Client;
import ru.windcorp.progressia.client.graphics.world.EntityAnchor; 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.CommsListener;
import ru.windcorp.progressia.common.comms.packets.Packet; import ru.windcorp.progressia.common.comms.packets.Packet;
import ru.windcorp.progressia.common.comms.packets.PacketSetLocalPlayer; import ru.windcorp.progressia.common.comms.packets.PacketSetLocalPlayer;
import ru.windcorp.progressia.common.comms.packets.PacketWorldChange; 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.EntityData;
import ru.windcorp.progressia.common.world.entity.PacketEntityChange;
// TODO refactor with no mercy // TODO refactor with no mercy
public class DefaultClientCommsListener implements CommsListener { public class DefaultClientCommsListener implements CommsListener {
private final Client client; private final Client client;
public DefaultClientCommsListener(Client client) { public DefaultClientCommsListener(Client client) {
@ -26,10 +27,6 @@ public class DefaultClientCommsListener implements CommsListener {
((PacketWorldChange) packet).apply( ((PacketWorldChange) packet).apply(
getClient().getWorld().getData() getClient().getWorld().getData()
); );
if (!(packet instanceof PacketEntityChange)) {
tmp_reassembleWorld();
}
} else if (packet instanceof PacketSetLocalPlayer) { } else if (packet instanceof PacketSetLocalPlayer) {
setLocalPlayer((PacketSetLocalPlayer) packet); setLocalPlayer((PacketSetLocalPlayer) packet);
} }
@ -41,24 +38,24 @@ public class DefaultClientCommsListener implements CommsListener {
); );
if (entity == null) { if (entity == null) {
throw new RuntimeException(""); CrashReports.report(
null,
"Player entity with ID %s not found",
new String(StringUtil.toFullHex(packet.getLocalPlayerEntityId()))
);
} }
getClient().setLocalPlayer(entity); getClient().setLocalPlayer(entity);
getClient().getCamera().setAnchor(new EntityAnchor( getClient().getCamera().setAnchor(new EntityAnchor(
getClient().getWorld().getEntityRenderable(entity) getClient().getWorld().getEntityRenderable(entity)
)); ));
} }
private void tmp_reassembleWorld() {
getClient().getWorld().getChunks().forEach(ChunkRender::markForUpdate);
}
@Override @Override
public void onIOError(IOException reason) { public void onIOError(IOException reason) {
// TODO implement // TODO implement
} }
public Client getClient() { public Client getClient() {
return client; return client;
} }

View File

@ -1,11 +1,11 @@
package ru.windcorp.progressia.client.comms.controls; 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 abstract class ControlTrigger extends Namespaced {
public ControlTrigger(String namespace, String name) { public ControlTrigger(String id) {
super(namespace, name); super(id);
} }
} }

View File

@ -5,8 +5,8 @@ import ru.windcorp.progressia.common.comms.controls.PacketControl;
public abstract class ControlTriggerInputBased extends ControlTrigger { public abstract class ControlTriggerInputBased extends ControlTrigger {
public ControlTriggerInputBased(String namespace, String name) { public ControlTriggerInputBased(String id) {
super(namespace, name); super(id);
} }
public abstract PacketControl onInputEvent(InputEvent event); 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; 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 = private static final ControlTriggerRegistry INSTANCE =
new ControlTriggerRegistry(); 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,37 +24,38 @@ import ru.windcorp.progressia.client.graphics.backend.OpenGLObjectTracker;
import ru.windcorp.progressia.client.graphics.backend.OpenGLObjectTracker.OpenGLDeletable; import ru.windcorp.progressia.client.graphics.backend.OpenGLObjectTracker.OpenGLDeletable;
import ru.windcorp.progressia.client.graphics.backend.shaders.attributes.Attribute; import ru.windcorp.progressia.client.graphics.backend.shaders.attributes.Attribute;
import ru.windcorp.progressia.client.graphics.backend.shaders.uniforms.Uniform; import ru.windcorp.progressia.client.graphics.backend.shaders.uniforms.Uniform;
import ru.windcorp.progressia.common.util.crash.CrashReports;
public class Program implements OpenGLDeletable { public class Program implements OpenGLDeletable {
private int handle; private int handle;
public Program(Shader vertexShader, Shader fragmentShader) { public Program(Shader vertexShader, Shader fragmentShader) {
handle = glCreateProgram(); handle = glCreateProgram();
OpenGLObjectTracker.register(this); OpenGLObjectTracker.register(this);
glAttachShader(handle, vertexShader.getHandle()); glAttachShader(handle, vertexShader.getHandle());
glAttachShader(handle, fragmentShader.getHandle()); glAttachShader(handle, fragmentShader.getHandle());
glLinkProgram(handle); glLinkProgram(handle);
if (glGetProgrami(handle, GL_LINK_STATUS) == GL_FALSE) { if (glGetProgrami(handle, GL_LINK_STATUS) == GL_FALSE) {
throw new RuntimeException("Bad program:\n" + glGetProgramInfoLog(handle)); CrashReports.report(null, "Bad program:\n%s", glGetProgramInfoLog(handle));
} }
} }
public Attribute getAttribute(String name) { public Attribute getAttribute(String name) {
return new Attribute(glGetAttribLocation(handle, name), this); return new Attribute(glGetAttribLocation(handle, name), this);
} }
public Uniform getUniform(String name) { public Uniform getUniform(String name) {
return new Uniform(glGetUniformLocation(handle, name), this); return new Uniform(glGetUniformLocation(handle, name), this);
} }
public void use() { public void use() {
glUseProgram(handle); glUseProgram(handle);
} }
@Override @Override
public void delete() { public void delete() {
glDeleteProgram(handle); glDeleteProgram(handle);

View File

@ -26,26 +26,26 @@ import ru.windcorp.progressia.client.graphics.backend.OpenGLObjectTracker;
import ru.windcorp.progressia.client.graphics.backend.OpenGLObjectTracker.OpenGLDeletable; import ru.windcorp.progressia.client.graphics.backend.OpenGLObjectTracker.OpenGLDeletable;
import ru.windcorp.progressia.common.resource.Resource; import ru.windcorp.progressia.common.resource.Resource;
import ru.windcorp.progressia.common.resource.ResourceManager; import ru.windcorp.progressia.common.resource.ResourceManager;
import ru.windcorp.progressia.common.util.crash.CrashReports;
public class Shader implements OpenGLDeletable { public class Shader implements OpenGLDeletable {
public static enum ShaderType { public static enum ShaderType {
VERTEX(GL_VERTEX_SHADER), VERTEX(GL_VERTEX_SHADER), FRAGMENT(GL_FRAGMENT_SHADER);
FRAGMENT(GL_FRAGMENT_SHADER);
private final int glCode; private final int glCode;
private ShaderType(int glCode) { private ShaderType(int glCode) {
this.glCode = glCode; this.glCode = glCode;
} }
public int getGlCode() { public int getGlCode() {
return glCode; return glCode;
} }
public static ShaderType guessByResourceName(String resource) { public static ShaderType guessByResourceName(String resource) {
resource = resource.toLowerCase(Locale.ENGLISH); resource = resource.toLowerCase(Locale.ENGLISH);
if (resource.contains("vertex")) return VERTEX; if (resource.contains("vertex")) return VERTEX;
if (resource.contains("fragment")) return FRAGMENT; if (resource.contains("fragment")) return FRAGMENT;
if (resource.contains("vsh")) return VERTEX; if (resource.contains("vsh")) return VERTEX;
@ -57,48 +57,48 @@ public class Shader implements OpenGLDeletable {
); );
} }
} }
private static final String SHADER_ASSETS_PREFIX = "assets/shaders/"; private static final String SHADER_ASSETS_PREFIX = "assets/shaders/";
protected static Resource getShaderResource(String name) { protected static Resource getShaderResource(String name) {
return ResourceManager.getResource(SHADER_ASSETS_PREFIX + name); return ResourceManager.getResource(SHADER_ASSETS_PREFIX + name);
} }
private final int handle; private final int handle;
private final ShaderType type; private final ShaderType type;
public Shader(ShaderType type, String source) { public Shader(ShaderType type, String source) {
handle = glCreateShader(type.getGlCode()); handle = glCreateShader(type.getGlCode());
OpenGLObjectTracker.register(this); OpenGLObjectTracker.register(this);
this.type = type; this.type = type;
glShaderSource(handle, source); glShaderSource(handle, source);
glCompileShader(handle); glCompileShader(handle);
if (glGetShaderi(handle, GL_COMPILE_STATUS) == GL_FALSE) { if (glGetShaderi(handle, GL_COMPILE_STATUS) == GL_FALSE) {
System.out.println("***************** ERROR ******************"); System.out.println("***************** ERROR ******************");
System.out.println(source); System.out.println(source);
throw new RuntimeException("Bad shader:\n" + glGetShaderInfoLog(handle)); CrashReports.report(null, "Bad shader:\n %s", glGetShaderInfoLog(handle));
} }
} }
public Shader(String resource) { public Shader(String resource) {
this( this(
ShaderType.guessByResourceName(resource), ShaderType.guessByResourceName(resource),
getShaderResource(resource).readAsString() getShaderResource(resource).readAsString()
); );
} }
@Override @Override
public void delete() { public void delete() {
glDeleteShader(handle); glDeleteShader(handle);
} }
public int getHandle() { public int getHandle() {
return handle; return handle;
} }
public ShaderType getType() { public ShaderType getType() {
return type; return type;
} }

View File

@ -18,29 +18,30 @@
package ru.windcorp.progressia.client.graphics.backend.shaders.attributes; package ru.windcorp.progressia.client.graphics.backend.shaders.attributes;
import ru.windcorp.progressia.client.graphics.backend.shaders.Program; import ru.windcorp.progressia.client.graphics.backend.shaders.Program;
import ru.windcorp.progressia.common.util.crash.CrashReports;
public class Attribute { public class Attribute {
protected final int handle; protected final int handle;
private final Program program; private final Program program;
public Attribute(int handle, Program program) { public Attribute(int handle, Program program) {
if (handle < 0) { if (handle < 0) {
throw new RuntimeException("Bad handle: " + handle); CrashReports.report(null, "Bad handle: %d", handle);
} }
this.handle = handle; this.handle = handle;
this.program = program; this.program = program;
} }
public int getHandle() { public int getHandle() {
return handle; return handle;
} }
public Program getProgram() { public Program getProgram() {
return program; return program;
} }
public AttributeVertexArray asVertexArray() { public AttributeVertexArray asVertexArray() {
return new AttributeVertexArray(handle, program); return new AttributeVertexArray(handle, program);
} }

View File

@ -18,69 +18,70 @@
package ru.windcorp.progressia.client.graphics.backend.shaders.uniforms; package ru.windcorp.progressia.client.graphics.backend.shaders.uniforms;
import ru.windcorp.progressia.client.graphics.backend.shaders.Program; import ru.windcorp.progressia.client.graphics.backend.shaders.Program;
import ru.windcorp.progressia.common.util.crash.CrashReports;
public class Uniform { public class Uniform {
protected final int handle; protected final int handle;
private final Program program; private final Program program;
public Uniform(int handle, Program program) { public Uniform(int handle, Program program) {
if (handle < 0) { if (handle < 0) {
throw new RuntimeException("Bad handle: " + handle); CrashReports.report(null, "Bad handle: %d", handle);
} }
this.handle = handle; this.handle = handle;
this.program = program; this.program = program;
} }
public int getHandle() { public int getHandle() {
return handle; return handle;
} }
public Program getProgram() { public Program getProgram() {
return program; return program;
} }
public Uniform1Float as1Float() { public Uniform1Float as1Float() {
return new Uniform1Float(handle, program); return new Uniform1Float(handle, program);
} }
public Uniform1Int as1Int() { public Uniform1Int as1Int() {
return new Uniform1Int(handle, program); return new Uniform1Int(handle, program);
} }
public Uniform2Float as2Float() { public Uniform2Float as2Float() {
return new Uniform2Float(handle, program); return new Uniform2Float(handle, program);
} }
public Uniform2Int as2Int() { public Uniform2Int as2Int() {
return new Uniform2Int(handle, program); return new Uniform2Int(handle, program);
} }
public Uniform3Float as3Float() { public Uniform3Float as3Float() {
return new Uniform3Float(handle, program); return new Uniform3Float(handle, program);
} }
public Uniform3Int as3Int() { public Uniform3Int as3Int() {
return new Uniform3Int(handle, program); return new Uniform3Int(handle, program);
} }
public Uniform4Float as4Float() { public Uniform4Float as4Float() {
return new Uniform4Float(handle, program); return new Uniform4Float(handle, program);
} }
public Uniform4Int as4Int() { public Uniform4Int as4Int() {
return new Uniform4Int(handle, program); return new Uniform4Int(handle, program);
} }
public Uniform2Matrix as2Matrix() { public Uniform2Matrix as2Matrix() {
return new Uniform2Matrix(handle, program); return new Uniform2Matrix(handle, program);
} }
public Uniform3Matrix as3Matrix() { public Uniform3Matrix as3Matrix() {
return new Uniform3Matrix(handle, program); return new Uniform3Matrix(handle, program);
} }
public Uniform4Matrix as4Matrix() { public Uniform4Matrix as4Matrix() {
return new Uniform4Matrix(handle, program); return new Uniform4Matrix(handle, program);
} }

View File

@ -21,152 +21,142 @@ import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.graphics.texture.TextureDataEditor; import ru.windcorp.progressia.client.graphics.texture.TextureDataEditor;
import ru.windcorp.progressia.client.graphics.texture.TextureSettings; import ru.windcorp.progressia.client.graphics.texture.TextureSettings;
import ru.windcorp.progressia.common.resource.Resource; import ru.windcorp.progressia.common.resource.Resource;
import ru.windcorp.progressia.common.util.crash.CrashReports;
public class GNUUnifontLoader { public class GNUUnifontLoader {
private static final AtlasGroup ATLAS_GROUP_GNU_UNIFONT = private static final AtlasGroup ATLAS_GROUP_GNU_UNIFONT = new AtlasGroup("GNUUnifont", 1 << 12);
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 BITS_PER_HEX_DIGIT = 4;
private static final int PREFIX_LENGTH = "0000:".length(); private static final int PREFIX_LENGTH = "0000:".length();
private static class ParsedGlyph { private static class ParsedGlyph {
final char c; final char c;
final TextureDataEditor data; final TextureDataEditor data;
ParsedGlyph(char c, TextureDataEditor data) { ParsedGlyph(char c, TextureDataEditor data) {
this.c = c; this.c = c;
this.data = data; this.data = data;
} }
} }
private static class AtlasGlyph { private static class AtlasGlyph {
final char c; final char c;
final Texture texture; final Texture texture;
AtlasGlyph(char c, Texture texture) { AtlasGlyph(char c, Texture texture) {
this.c = c; this.c = c;
this.texture = texture; this.texture = texture;
} }
} }
public static GNUUnifont load(Resource resource) { public static GNUUnifont load(Resource resource) {
try (BufferedReader reader = createReader(resource)) { try (BufferedReader reader = createReader(resource)) {
return createStream(reader) return createStream(reader).map(GNUUnifontLoader::parse).map(GNUUnifontLoader::addToAtlas)
.map(GNUUnifontLoader::parse) .collect(Collectors.collectingAndThen(createMapper(), GNUUnifont::new));
.map(GNUUnifontLoader::addToAtlas)
.collect(Collectors.collectingAndThen(
createMapper(),
GNUUnifont::new
));
} catch (IOException | UncheckedIOException e) { } catch (IOException | UncheckedIOException e) {
throw new RuntimeException(e); CrashReports.report(e, "Could not load GNUUnifont");
return null;
} }
} }
private static BufferedReader createReader(Resource resource) private static BufferedReader createReader(Resource resource) throws IOException {
throws IOException
{
return new BufferedReader( return new BufferedReader(
new InputStreamReader( new InputStreamReader(new GZIPInputStream(resource.getInputStream()), StandardCharsets.UTF_8));
new GZIPInputStream(
resource.getInputStream()
),
StandardCharsets.UTF_8
)
);
} }
private static Stream<String> createStream(BufferedReader reader) { private static Stream<String> createStream(BufferedReader reader) {
return reader.lines(); return reader.lines();
} }
private static ParsedGlyph parse(String declar) { private static ParsedGlyph parse(String declar) {
int width = getWidth(declar); try {
checkDeclaration(declar, width);
int width = getWidth(declar);
checkDeclaration(declar, width);
char c = getChar(declar); char c = getChar(declar);
TextureDataEditor editor = new TextureDataEditor( TextureDataEditor editor = new TextureDataEditor(width, GNUUnifont.HEIGHT, width, GNUUnifont.HEIGHT,
width, GNUUnifont.HEIGHT, TEXTURE_SETTINGS);
width, GNUUnifont.HEIGHT,
TEXTURE_SETTINGS
);
for (int y = 0; y < GNUUnifont.HEIGHT; ++y) { for (int y = 0; y < GNUUnifont.HEIGHT; ++y) {
for (int x = 0; x < width; ++x) { for (int x = 0; x < width; ++x) {
int bit = x + y * width; int bit = x + y * width;
editor.setPixel( editor.setPixel(x, GNUUnifont.HEIGHT - y - 1, getBit(declar, bit) ? 0xFFFFFFFF : 0x00000000);
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) { private static char getChar(String declar) {
int result = 0; int result = 0;
for (int i = 0; i < 4; ++i) { for (int i = 0; i < 4; ++i) {
result = result = (result << BITS_PER_HEX_DIGIT) | getHexValue(declar.charAt(i));
(result << BITS_PER_HEX_DIGIT) |
getHexValue(declar.charAt(i));
} }
return (char) result; return (char) result;
} }
private static boolean getBit(String declar, int bit) { private static boolean getBit(String declar, int bit) {
int character = PREFIX_LENGTH + (bit / BITS_PER_HEX_DIGIT); int character = PREFIX_LENGTH + (bit / BITS_PER_HEX_DIGIT);
bit = bit % BITS_PER_HEX_DIGIT; bit = bit % BITS_PER_HEX_DIGIT;
char c = declar.charAt(character); char c = declar.charAt(character);
int value = getHexValue(c); int value = getHexValue(c);
return (value & (1 << (BITS_PER_HEX_DIGIT - bit - 1))) != 0; return (value & (1 << (BITS_PER_HEX_DIGIT - bit - 1))) != 0;
} }
private static int getWidth(String declar) { private static int getWidth(String declar) {
int meaningfulChars = declar.length() - PREFIX_LENGTH; int meaningfulChars = declar.length() - PREFIX_LENGTH;
final int charsPerColumn = GNUUnifont.HEIGHT / BITS_PER_HEX_DIGIT; final int charsPerColumn = GNUUnifont.HEIGHT / BITS_PER_HEX_DIGIT;
return meaningfulChars / charsPerColumn; 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)) { 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) { 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) { for (int i = 0; i < declar.length(); ++i) {
if (i == BITS_PER_HEX_DIGIT) { if (i == BITS_PER_HEX_DIGIT) {
if (declar.charAt(i) != ':') { 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 { } else {
char c = declar.charAt(i); char c = declar.charAt(i);
if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F'))) { 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) { private static int getHexValue(char digit) {
if (digit < '0') throw new NumberFormatException(digit + " is not a hex digit (0-9A-F expected)"); if (digit < '0')
if (digit <= '9') return digit - '0'; throw new NumberFormatException(digit + " is not a hex digit (0-9A-F expected)");
if (digit < 'A') throw new NumberFormatException(digit + " is not a hex digit (0-9A-F expected)"); if (digit <= '9')
if (digit <= 'F') return digit - 'A' + 0xA; 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)"); throw new NumberFormatException(digit + " is not a hex digit (0-9A-F expected)");
} }
@ -174,24 +164,21 @@ public class GNUUnifontLoader {
Sprite sprite = Atlases.loadSprite(glyph.data.getData(), ATLAS_GROUP_GNU_UNIFONT); Sprite sprite = Atlases.loadSprite(glyph.data.getData(), ATLAS_GROUP_GNU_UNIFONT);
return new AtlasGlyph(glyph.c, new SimpleTexture(sprite)); return new AtlasGlyph(glyph.c, new SimpleTexture(sprite));
} }
private static private static Collector<AtlasGlyph, ?, TCharObjectMap<Texture>> createMapper() {
Collector<AtlasGlyph, ?, TCharObjectMap<Texture>> return Collector.of(TCharObjectHashMap<Texture>::new,
createMapper() {
return Collector.of(
TCharObjectHashMap<Texture>::new,
(map, glyph) -> map.put(glyph.c, glyph.texture), (map, glyph) -> map.put(glyph.c, glyph.texture),
(a, b) -> { (a, b) -> {
a.putAll(b); a.putAll(b);
return a; return a;
}, },
Characteristics.UNORDERED Characteristics.UNORDERED);
);
} }
private GNUUnifontLoader() {} private GNUUnifontLoader() {
}
} }

View File

@ -39,40 +39,40 @@ import ru.windcorp.progressia.client.graphics.input.bus.Input;
import ru.windcorp.progressia.client.graphics.input.bus.InputBus; import ru.windcorp.progressia.client.graphics.input.bus.InputBus;
import ru.windcorp.progressia.client.graphics.input.bus.InputListener; import ru.windcorp.progressia.client.graphics.input.bus.InputListener;
import ru.windcorp.progressia.common.util.Named; import ru.windcorp.progressia.common.util.Named;
import ru.windcorp.progressia.common.util.crash.CrashReports;
public class Component extends Named { public class Component extends Named {
private final List<Component> children = private final List<Component> children = Collections.synchronizedList(new CopyOnWriteArrayList<>());
Collections.synchronizedList(new CopyOnWriteArrayList<>());
private Component parent = null; private Component parent = null;
private EventBus eventBus = null; private EventBus eventBus = null;
private InputBus inputBus = null; private InputBus inputBus = null;
private int x, y; private int x, y;
private int width, height; private int width, height;
private boolean valid = false; private boolean valid = false;
private Vec2i preferredSize = null; private Vec2i preferredSize = null;
private Object layoutHint = null; private Object layoutHint = null;
private Layout layout = null; private Layout layout = null;
private boolean isFocusable = false; private boolean isFocusable = false;
private boolean isFocused = false; private boolean isFocused = false;
private boolean isHovered = false; private boolean isHovered = false;
public Component(String name) { public Component(String name) {
super(name); super(name);
} }
public Component getParent() { public Component getParent() {
return parent; return parent;
} }
protected void setParent(Component parent) { protected void setParent(Component parent) {
if (this.parent != parent) { if (this.parent != parent) {
Component previousParent = this.parent; Component previousParent = this.parent;
@ -81,68 +81,71 @@ public class Component extends Named {
dispatchEvent(new ParentChangedEvent(this, previousParent, parent)); dispatchEvent(new ParentChangedEvent(this, previousParent, parent));
} }
} }
public List<Component> getChildren() { public List<Component> getChildren() {
return children; return children;
} }
public Component getChild(int index) { public Component getChild(int index) {
synchronized (getChildren()) { synchronized (getChildren()) {
if (index < 0 || index >= getChildren().size()) return null; if (index < 0 || index >= getChildren().size())
return null;
return getChildren().get(index); return getChildren().get(index);
} }
} }
public int getChildIndex(Component child) { public int getChildIndex(Component child) {
return getChildren().indexOf(child); return getChildren().indexOf(child);
} }
public int getOwnIndex() { public int getOwnIndex() {
Component parent = getParent(); Component parent = getParent();
if (parent != null) { if (parent != null) {
return parent.getChildIndex(this); return parent.getChildIndex(this);
} }
return -1; return -1;
} }
public void moveChild(Component child, int newIndex) { 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)) { if (getChildren().remove(child)) {
getChildren().add(newIndex, child); getChildren().add(newIndex, child);
invalidate(); invalidate();
} }
} }
public void moveSelf(int newIndex) { public void moveSelf(int newIndex) {
Component parent = getParent(); Component parent = getParent();
if (parent != null) { if (parent != null) {
parent.moveChild(this, newIndex); parent.moveChild(this, newIndex);
} }
} }
public Component addChild(Component child, int index) { public Component addChild(Component child, int index) {
if (index == -1) index = getChildren().size(); if (index == -1)
index = getChildren().size();
invalidate(); invalidate();
getChildren().add(index, child); getChildren().add(index, child);
child.setParent(this); child.setParent(this);
dispatchEvent(new ChildAddedEvent(this, child)); dispatchEvent(new ChildAddedEvent(this, child));
return this; return this;
} }
public Component addChild(Component child) { public Component addChild(Component child) {
return addChild(child, -1); return addChild(child, -1);
} }
public Component removeChild(Component child) { public Component removeChild(Component child) {
if (!getChildren().contains(child)) { if (!getChildren().contains(child)) {
return this; return this;
} }
if (child.isFocused()) { if (child.isFocused()) {
child.focusNext(); child.focusNext();
} }
@ -150,204 +153,205 @@ public class Component extends Named {
invalidate(); invalidate();
getChildren().remove(child); getChildren().remove(child);
child.setParent(null); child.setParent(null);
dispatchEvent(new ChildRemovedEvent(this, child)); dispatchEvent(new ChildRemovedEvent(this, child));
return this; return this;
} }
public synchronized int getX() { public synchronized int getX() {
return x; return x;
} }
public synchronized int getY() { public synchronized int getY() {
return y; return y;
} }
public synchronized Component setPosition(int x, int y) { public synchronized Component setPosition(int x, int y) {
invalidate(); invalidate();
this.x = x; this.x = x;
this.y = y; this.y = y;
return this; return this;
} }
public synchronized int getWidth() { public synchronized int getWidth() {
return width; return width;
} }
public synchronized int getHeight() { public synchronized int getHeight() {
return height; return height;
} }
public synchronized Component setSize(int width, int height) { public synchronized Component setSize(int width, int height) {
invalidate(); invalidate();
this.width = width; this.width = width;
this.height = height; this.height = height;
return this; return this;
} }
public Component setSize(Vec2i size) { public Component setSize(Vec2i size) {
return setSize(size.x, size.y); return setSize(size.x, size.y);
} }
public synchronized Component setBounds(int x, int y, int width, int height) { public synchronized Component setBounds(int x, int y, int width, int height) {
setPosition(x, y); setPosition(x, y);
setSize(width, height); setSize(width, height);
return this; return this;
} }
public Component setBounds(int x, int y, Vec2i size) { public Component setBounds(int x, int y, Vec2i size) {
return setBounds(x, y, size.x, size.y); return setBounds(x, y, size.x, size.y);
} }
public boolean isValid() { public boolean isValid() {
return valid; return valid;
} }
public synchronized void invalidate() { public synchronized void invalidate() {
valid = false; valid = false;
getChildren().forEach(child -> child.invalidate()); getChildren().forEach(child -> child.invalidate());
} }
public synchronized void validate() { public synchronized void validate() {
Component parent = getParent(); Component parent = getParent();
invalidate(); invalidate();
if (parent == null) { if (parent == null) {
layoutSelf(); layoutSelf();
} else { } else {
parent.validate(); parent.validate();
} }
} }
protected synchronized void layoutSelf() { protected synchronized void layoutSelf() {
try { try {
if (getLayout() != null) { if (getLayout() != null) {
getLayout().layout(this); getLayout().layout(this);
} }
getChildren().forEach(child -> { getChildren().forEach(child -> {
child.layoutSelf(); child.layoutSelf();
}); });
valid = true; valid = true;
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); CrashReports.report(e, "Could not layout Component %s", this);
} }
} }
public synchronized Vec2i getPreferredSize() { public synchronized Vec2i getPreferredSize() {
if (preferredSize != null) { if (preferredSize != null) {
return preferredSize; return preferredSize;
} }
if (getLayout() != null) { if (getLayout() != null) {
try { try {
return getLayout().calculatePreferredSize(this); return getLayout().calculatePreferredSize(this);
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); CrashReports.report(e, "Could not calculate preferred size for Component %s", this);
} }
} }
return new Vec2i(0, 0); return new Vec2i(0, 0);
} }
public synchronized Component setPreferredSize(Vec2i preferredSize) { public synchronized Component setPreferredSize(Vec2i preferredSize) {
this.preferredSize = preferredSize; this.preferredSize = preferredSize;
return this; return this;
} }
public Component setPreferredSize(int width, int height) { public Component setPreferredSize(int width, int height) {
return setPreferredSize(new Vec2i(width, height)); return setPreferredSize(new Vec2i(width, height));
} }
public Layout getLayout() { public Layout getLayout() {
return layout; return layout;
} }
public synchronized Component setLayout(Layout layout) { public synchronized Component setLayout(Layout layout) {
invalidate(); invalidate();
this.layout = layout; this.layout = layout;
return this; return this;
} }
public Object getLayoutHint() { public Object getLayoutHint() {
return layoutHint; return layoutHint;
} }
public Component setLayoutHint(Object hint) { public Component setLayoutHint(Object hint) {
this.layoutHint = hint; this.layoutHint = hint;
return this; return this;
} }
public boolean isFocusable() { public boolean isFocusable() {
return isFocusable; return isFocusable;
} }
public Component setFocusable(boolean focusable) { public Component setFocusable(boolean focusable) {
this.isFocusable = focusable; this.isFocusable = focusable;
return this; return this;
} }
public boolean isFocused() { public boolean isFocused() {
return isFocused; return isFocused;
} }
protected synchronized void setFocused(boolean focus) { protected synchronized void setFocused(boolean focus) {
if (focus != this.isFocused) { if (focus != this.isFocused) {
dispatchEvent(new FocusEvent(this, focus)); dispatchEvent(new FocusEvent(this, focus));
this.isFocused = focus; this.isFocused = focus;
} }
} }
public Component takeFocus() { public Component takeFocus() {
if (isFocused()) { if (isFocused()) {
return this; return this;
} }
Component comp = this; Component comp = this;
Component focused = null; Component focused = null;
while (comp != null) { while (comp != null) {
if ((focused = comp.findFocused()) != null) { if ((focused = comp.findFocused()) != null) {
focused.setFocused(false); focused.setFocused(false);
setFocused(true); setFocused(true);
return this; return this;
} }
comp = comp.getParent(); comp = comp.getParent();
} }
setFocused(true); setFocused(true);
return this; return this;
} }
public void focusNext() { public void focusNext() {
Component component = this; Component component = this;
while (true) { while (true) {
component = component.getNextFocusCandidate(true); component = component.getNextFocusCandidate(true);
if (component == this) { if (component == this) {
return; return;
} }
if (component.isFocusable()) { if (component.isFocusable()) {
setFocused(false); setFocused(false);
component.setFocused(true); component.setFocused(true);
return; return;
} }
} }
} }
private Component getNextFocusCandidate(boolean canUseChildren) { private Component getNextFocusCandidate(boolean canUseChildren) {
if (canUseChildren) synchronized (getChildren()) { if (canUseChildren)
if (!getChildren().isEmpty()) { synchronized (getChildren()) {
return getChild(0); if (!getChildren().isEmpty()) {
return getChild(0);
}
} }
}
Component parent = getParent(); Component parent = getParent();
if (parent != null) { if (parent != null) {
synchronized (parent.getChildren()) { synchronized (parent.getChildren()) {
@ -356,32 +360,32 @@ public class Component extends Named {
return parent.getChild(ownIndex + 1); return parent.getChild(ownIndex + 1);
} }
} }
return parent.getNextFocusCandidate(false); return parent.getNextFocusCandidate(false);
} }
return this; return this;
} }
public void focusPrevious() { public void focusPrevious() {
Component component = this; Component component = this;
while (true) { while (true) {
component = component.getPreviousFocusCandidate(); component = component.getPreviousFocusCandidate();
if (component == this) { if (component == this) {
return; return;
} }
if (component.isFocusable()) { if (component.isFocusable()) {
setFocused(false); setFocused(false);
component.setFocused(true); component.setFocused(true);
return; return;
} }
} }
} }
private Component getPreviousFocusCandidate() { private Component getPreviousFocusCandidate() {
Component parent = getParent(); Component parent = getParent();
if (parent != null) { if (parent != null) {
@ -391,30 +395,30 @@ public class Component extends Named {
return parent.getChild(ownIndex - 1).getLastDeepChild(); return parent.getChild(ownIndex - 1).getLastDeepChild();
} }
} }
return parent; return parent;
} }
return getLastDeepChild(); return getLastDeepChild();
} }
private Component getLastDeepChild() { private Component getLastDeepChild() {
synchronized (getChildren()) { synchronized (getChildren()) {
if (!getChildren().isEmpty()) { if (!getChildren().isEmpty()) {
return getChild(getChildren().size() - 1).getLastDeepChild(); return getChild(getChildren().size() - 1).getLastDeepChild();
} }
return this; return this;
} }
} }
public synchronized Component findFocused() { public synchronized Component findFocused() {
if (isFocused()) { if (isFocused()) {
return this; return this;
} }
Component result; Component result;
synchronized (getChildren()) { synchronized (getChildren()) {
for (Component c : getChildren()) { for (Component c : getChildren()) {
result = c.findFocused(); result = c.findFocused();
@ -423,10 +427,10 @@ public class Component extends Named {
} }
} }
} }
return null; return null;
} }
public boolean isHovered() { public boolean isHovered() {
return isHovered; return isHovered;
} }
@ -434,9 +438,9 @@ public class Component extends Named {
protected void setHovered(boolean isHovered) { protected void setHovered(boolean isHovered) {
if (this.isHovered != isHovered) { if (this.isHovered != isHovered) {
this.isHovered = isHovered; this.isHovered = isHovered;
if (!isHovered && !getChildren().isEmpty()) { if (!isHovered && !getChildren().isEmpty()) {
getChildren().forEach(child -> { getChildren().forEach(child -> {
if (child.isHovered()) { if (child.isHovered()) {
child.setHovered(false); child.setHovered(false);
@ -444,7 +448,7 @@ public class Component extends Named {
} }
}); });
} }
dispatchEvent(new HoverEvent(this, isHovered)); dispatchEvent(new HoverEvent(this, isHovered));
} }
} }
@ -453,40 +457,36 @@ public class Component extends Named {
if (eventBus == null) { if (eventBus == null) {
eventBus = new EventBus(getName()); eventBus = new EventBus(getName());
} }
eventBus.register(listener); eventBus.register(listener);
} }
public void removeListener(Object listener) { public void removeListener(Object listener) {
if (eventBus == null) return; if (eventBus == null)
return;
eventBus.unregister(listener); eventBus.unregister(listener);
} }
public void dispatchEvent(Object event) { public void dispatchEvent(Object event) {
if (eventBus == null) return; if (eventBus == null)
return;
eventBus.post(event); eventBus.post(event);
} }
public <T extends InputEvent> void addListener( public <T extends InputEvent> void addListener(Class<? extends T> type, boolean handlesConsumed,
Class<? extends T> type, InputListener<T> listener) {
boolean handlesConsumed,
InputListener<T> listener
) {
if (inputBus == null) { if (inputBus == null) {
inputBus = new InputBus(); inputBus = new InputBus();
} }
inputBus.register(type, handlesConsumed, listener); inputBus.register(type, handlesConsumed, listener);
} }
public <T extends InputEvent> void addListener( public <T extends InputEvent> void addListener(Class<? extends T> type, InputListener<T> listener) {
Class<? extends T> type,
InputListener<T> listener
) {
if (inputBus == null) { if (inputBus == null) {
inputBus = new InputBus(); inputBus = new InputBus();
} }
inputBus.register(type, listener); inputBus.register(type, listener);
} }
@ -501,43 +501,45 @@ public class Component extends Named {
inputBus.dispatch(input); inputBus.dispatch(input);
} }
} }
public void dispatchInput(Input input) { public void dispatchInput(Input input) {
try { try {
switch (input.getTarget()) { switch (input.getTarget()) {
case FOCUSED: case FOCUSED:
dispatchInputToFocused(input); dispatchInputToFocused(input);
break; break;
case HOVERED: case HOVERED:
dispatchInputToHovered(input); dispatchInputToHovered(input);
break; break;
case ALL: case ALL:
default: default:
dispatchInputToAll(input); dispatchInputToAll(input);
break; break;
} }
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); CrashReports.report(e, "Could not dispatch input to Component %s", this);
} }
} }
private void dispatchInputToFocused(Input input) { private void dispatchInputToFocused(Input input) {
Component c = findFocused(); Component c = findFocused();
if (c == null) return; if (c == null)
if (attemptFocusTransfer(input, c)) return; return;
if (attemptFocusTransfer(input, c))
return;
while (c != null) { while (c != null) {
c.handleInput(input); c.handleInput(input);
c = c.getParent(); c = c.getParent();
} }
} }
private void dispatchInputToHovered(Input input) { private void dispatchInputToHovered(Input input) {
getChildren().forEach(child -> { getChildren().forEach(child -> {
if (child.containsCursor()) { if (child.containsCursor()) {
child.setHovered(true); child.setHovered(true);
if (!input.isConsumed()) { if (!input.isConsumed()) {
child.dispatchInput(input); child.dispatchInput(input);
} }
@ -555,11 +557,13 @@ public class Component extends Named {
} }
private boolean attemptFocusTransfer(Input input, Component focused) { private boolean attemptFocusTransfer(Input input, Component focused) {
if (input.isConsumed()) return false; if (input.isConsumed())
if (!(input.getEvent() instanceof KeyEvent)) return false; return false;
if (!(input.getEvent() instanceof KeyEvent))
return false;
KeyEvent keyInput = (KeyEvent) input.getEvent(); KeyEvent keyInput = (KeyEvent) input.getEvent();
if (keyInput.getKey() == GLFW.GLFW_KEY_TAB && !keyInput.isRelease()) { if (keyInput.getKey() == GLFW.GLFW_KEY_TAB && !keyInput.isRelease()) {
input.consume(); input.consume();
if (keyInput.hasShift()) { if (keyInput.hasShift()) {
@ -569,23 +573,18 @@ public class Component extends Named {
} }
return true; return true;
} }
return false; return false;
} }
public synchronized boolean contains(int x, int y) { public synchronized boolean contains(int x, int y) {
return return x >= getX() && x < getX() + getWidth() && y >= getY() && y < getY() + getHeight();
x >= getX() && x < getX() + getWidth() &&
y >= getY() && y < getY() + getHeight();
} }
public boolean containsCursor() { public boolean containsCursor() {
return contains( return contains((int) InputTracker.getCursorX(), (int) InputTracker.getCursorY());
(int) InputTracker.getCursorX(),
(int) InputTracker.getCursorY()
);
} }
public void requestReassembly() { public void requestReassembly() {
if (parent != null) { if (parent != null) {
parent.requestReassembly(); parent.requestReassembly();
@ -602,60 +601,60 @@ public class Component extends Named {
if (width == 0 || height == 0) { if (width == 0 || height == 0) {
return; return;
} }
if (!isValid()) { if (!isValid()) {
validate(); validate();
} }
try { try {
assembleSelf(target); assembleSelf(target);
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); CrashReports.report(e, "Could not assemble Component %s", this);
} }
assembleChildren(target); assembleChildren(target);
try { try {
postAssembleSelf(target); postAssembleSelf(target);
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); CrashReports.report(e, "Post-assembly failed for Component %s", this);
} }
} }
protected void assembleSelf(RenderTarget target) { protected void assembleSelf(RenderTarget target) {
// To be overridden // To be overridden
} }
protected void postAssembleSelf(RenderTarget target) { protected void postAssembleSelf(RenderTarget target) {
// To be overridden // To be overridden
} }
protected void assembleChildren(RenderTarget target) { protected void assembleChildren(RenderTarget target) {
getChildren().forEach(child -> child.assemble(target)); getChildren().forEach(child -> child.assemble(target));
} }
// /** // /**
// * Returns a component that displays this component in its center. // * Returns a component that displays this component in its center.
// * @return a {@link Aligner} initialized to center this component // * @return a {@link Aligner} initialized to center this component
// */ // */
// public Component center() { // public Component center() {
// return new Aligner(this); // return new Aligner(this);
// } // }
// //
// /** // /**
// * Returns a component that aligns this component. // * Returns a component that aligns this component.
// * @return a {@link Aligner} initialized with this component // * @return a {@link Aligner} initialized with this component
// */ // */
// public Component align(double x, double y) { // public Component align(double x, double y) {
// return new Aligner(this, x, y); // return new Aligner(this, x, y);
// } // }
// //
// /** // /**
// * Returns a component that allows scrolling this component // * Returns a component that allows scrolling this component
// * @return a {@link Scroller} initialized with this component // * @return a {@link Scroller} initialized with this component
// */ // */
// public Component scroller() { // public Component scroller() {
// return new Scroller(this); // 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; package ru.windcorp.progressia.client.graphics.input;
import gnu.trove.set.TIntSet; import java.util.function.Predicate;
import ru.windcorp.progressia.client.graphics.backend.InputTracker;
import org.lwjgl.glfw.GLFW;
public class KeyMatcher { public class KeyMatcher {
private final int key; private final int key;
private final int[] additionalKeys;
private final int mods; private final int mods;
public KeyMatcher(int key, int[] additionalKeys, int mods) { protected KeyMatcher(int key, int mods) {
this.key = key; this.key = key;
this.additionalKeys = additionalKeys;
this.mods = mods; 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) { public boolean matches(KeyEvent event) {
if (!event.isPress()) return false; if (!event.isPress()) return false;
if (event.getKey() != getKey()) return false; if (event.getKey() != getKey()) return false;
if (event.getMods() != getMods()) return false; if ((event.getMods() & 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;
}
}
return true; return true;
} }
@ -65,12 +43,52 @@ public class KeyMatcher {
return key; return key;
} }
public int[] getAdditionalKeys() {
return additionalKeys;
}
public int getMods() { public int getMods() {
return mods; 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,108 +31,101 @@ import gnu.trove.map.hash.TIntObjectHashMap;
import gnu.trove.map.hash.TObjectIntHashMap; import gnu.trove.map.hash.TObjectIntHashMap;
import gnu.trove.set.TIntSet; import gnu.trove.set.TIntSet;
import gnu.trove.set.hash.TIntHashSet; import gnu.trove.set.hash.TIntHashSet;
import ru.windcorp.progressia.common.util.crash.CrashReports;
public class Keys { public class Keys {
private static final TIntObjectMap<String> CODES_TO_NAMES = private static final TIntObjectMap<String> CODES_TO_NAMES = new TIntObjectHashMap<>();
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 TIntSet MOUSE_BUTTONS = new TIntHashSet();
private static final String KEY_PREFIX = "GLFW_KEY_"; private static final String KEY_PREFIX = "GLFW_KEY_";
private static final String MOUSE_BUTTON_PREFIX = "GLFW_MOUSE_BUTTON_"; private static final String MOUSE_BUTTON_PREFIX = "GLFW_MOUSE_BUTTON_";
private static final Set<String> IGNORE_FIELDS = private static final Set<String> IGNORE_FIELDS = new HashSet<>(
new HashSet<>(Arrays.asList( Arrays.asList("GLFW_KEY_UNKNOWN", "GLFW_KEY_LAST", "GLFW_MOUSE_BUTTON_LAST", "GLFW_MOUSE_BUTTON_1", // Alias
"GLFW_KEY_UNKNOWN", // for
"GLFW_KEY_LAST", // LEFT
"GLFW_MOUSE_BUTTON_LAST",
"GLFW_MOUSE_BUTTON_1", // Alias for LEFT
"GLFW_MOUSE_BUTTON_2", // Alias for RIGHT "GLFW_MOUSE_BUTTON_2", // Alias for RIGHT
"GLFW_MOUSE_BUTTON_3" // Alias for MIDDLE "GLFW_MOUSE_BUTTON_3" // Alias for MIDDLE
)); ));
static { static {
initializeDictionary(); initializeDictionary();
} }
private static void initializeDictionary() { private static void initializeDictionary() {
try { try {
for (Field field : GLFW.class.getFields()) { for (Field field : GLFW.class.getFields()) {
if (!Modifier.isStatic(field.getModifiers())) continue; if (!Modifier.isStatic(field.getModifiers()))
if (!Modifier.isFinal(field.getModifiers())) continue; continue;
if (!Modifier.isFinal(field.getModifiers()))
continue;
String name = field.getName(); String name = field.getName();
if ( if (!name.startsWith(KEY_PREFIX) && !name.startsWith(MOUSE_BUTTON_PREFIX))
!name.startsWith(KEY_PREFIX) && continue;
!name.startsWith(MOUSE_BUTTON_PREFIX)
) continue; if (IGNORE_FIELDS.contains(name))
continue;
if (IGNORE_FIELDS.contains(name)) continue;
addToDictionary(field); addToDictionary(field);
} }
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
throw new RuntimeException(e); CrashReports.report(e, "Cannot access GLFW constants");
} }
} }
private static void addToDictionary(Field field) private static void addToDictionary(Field field) throws IllegalAccessException {
throws IllegalAccessException {
int value = field.getInt(null); int value = field.getInt(null);
String name = field.getName(); String name = field.getName();
if (name.startsWith(KEY_PREFIX)) { if (name.startsWith(KEY_PREFIX)) {
name = name.substring(KEY_PREFIX.length()); name = name.substring(KEY_PREFIX.length());
} else if (name.startsWith(MOUSE_BUTTON_PREFIX)) { } else if (name.startsWith(MOUSE_BUTTON_PREFIX)) {
name = "MOUSE_" + name.substring(MOUSE_BUTTON_PREFIX.length()); name = "MOUSE_" + name.substring(MOUSE_BUTTON_PREFIX.length());
MOUSE_BUTTONS.add(value); MOUSE_BUTTONS.add(value);
} }
if (CODES_TO_NAMES.containsKey(value)) { if (CODES_TO_NAMES.containsKey(value)) {
throw new RuntimeException( CrashReports.report(null, "Duplicate keys: %s and %s both map to %d(0x%s)",
"Duplicate keys: " + CODES_TO_NAMES.get(value) + CODES_TO_NAMES.get(value), name, value, Integer.toHexString(value));
" and " + name + " both map to " + value +
"(0x" + Integer.toHexString(value) + ")"
);
} }
CODES_TO_NAMES.put(value, name); CODES_TO_NAMES.put(value, name);
NAMES_TO_CODES.put(name, value); NAMES_TO_CODES.put(name, value);
} }
public static String getInternalName(int code) { public static String getInternalName(int code) {
String result = CODES_TO_NAMES.get(code); String result = CODES_TO_NAMES.get(code);
if (result == null) { if (result == null) {
return "UNKNOWN"; return "UNKNOWN";
} }
return result; return result;
} }
public static String getDisplayName(int code) { public static String getDisplayName(int code) {
String name = getInternalName(code); String name = getInternalName(code);
if (name.startsWith("KP_")) { if (name.startsWith("KP_")) {
name = "KEYPAD_" + name.substring("KP_".length()); name = "KEYPAD_" + name.substring("KP_".length());
} }
name = Character.toTitleCase(name.charAt(0)) + name = Character.toTitleCase(name.charAt(0)) + name.substring(1).toLowerCase();
name.substring(1).toLowerCase();
name = name.replace('_', ' '); name = name.replace('_', ' ');
return name; return name;
} }
public static int getCode(String internalName) { public static int getCode(String internalName) {
if (NAMES_TO_CODES.containsKey(internalName)) { if (NAMES_TO_CODES.containsKey(internalName)) {
return -1; return -1;
@ -140,7 +133,7 @@ public class Keys {
return NAMES_TO_CODES.get(internalName); return NAMES_TO_CODES.get(internalName);
} }
} }
public static boolean isMouse(int code) { public static boolean isMouse(int code) {
return MOUSE_BUTTONS.contains(code); return MOUSE_BUTTONS.contains(code);
} }

View File

@ -121,5 +121,14 @@ public class LambdaModel extends DynamicModel {
} }
} }
public static LambdaModel animate(Renderable model, TransformGetter transform) {
return new LambdaModel(
new Renderable[] { model },
new Mat4[] { new Mat4() },
new boolean[] { true },
new TransformGetter[] { transform }
);
}
} }

View File

@ -13,72 +13,67 @@ import ru.windcorp.progressia.client.graphics.backend.RenderTaskQueue;
import ru.windcorp.progressia.common.resource.Resource; import ru.windcorp.progressia.common.resource.Resource;
import ru.windcorp.progressia.common.util.BinUtil; import ru.windcorp.progressia.common.util.BinUtil;
import ru.windcorp.progressia.common.util.Named; import ru.windcorp.progressia.common.util.Named;
import ru.windcorp.progressia.common.util.crash.CrashReports;
public class Atlases { public class Atlases {
public static class AtlasGroup extends Named { public static class AtlasGroup extends Named {
private final int atlasSize; private final int atlasSize;
public AtlasGroup(String name, int atlasSize) { public AtlasGroup(String name, int atlasSize) {
super(name); super(name);
this.atlasSize = atlasSize; this.atlasSize = atlasSize;
if (!BinUtil.isPowerOf2(atlasSize)) { if (!BinUtil.isPowerOf2(atlasSize)) {
throw new IllegalArgumentException( throw new IllegalArgumentException("Atlas size " + atlasSize + " is not a power of 2");
"Atlas size " + atlasSize
+ " is not a power of 2"
);
} }
} }
public int getAtlasSize() { public int getAtlasSize() {
return atlasSize; return atlasSize;
} }
@Override @Override
public int hashCode() { public int hashCode() {
return super.hashCode(); return super.hashCode();
} }
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
return super.equals(obj); return super.equals(obj);
} }
} }
public static class Atlas { public static class Atlas {
private final AtlasGroup group; private final AtlasGroup group;
private final TextureDataEditor editor; private final TextureDataEditor editor;
private int nextX, nextY; private int nextX, nextY;
private int rowHeight; private int rowHeight;
private final TexturePrimitive primitive; private final TexturePrimitive primitive;
public Atlas(AtlasGroup group) { public Atlas(AtlasGroup group) {
this.group = group; this.group = group;
int size = group.getAtlasSize(); int size = group.getAtlasSize();
this.editor = new TextureDataEditor(size, size, size, size, SETTINGS); this.editor = new TextureDataEditor(size, size, size, size, SETTINGS);
this.primitive = new TexturePrimitive(editor.getData()); this.primitive = new TexturePrimitive(editor.getData());
} }
public Sprite addSprite(TextureData data) { public Sprite addSprite(TextureData data) {
int width = data.getContentWidth(); int width = data.getContentWidth();
int height = data.getContentHeight(); int height = data.getContentHeight();
selectPosition(width, height); selectPosition(width, height);
editor.draw(data, nextX, nextY); editor.draw(data, nextX, nextY);
Sprite result = new Sprite( Sprite result = new Sprite(getPrimitive(), toPrimitiveCoords(nextX, nextY),
getPrimitive(), toPrimitiveCoords(width, height));
toPrimitiveCoords(nextX, nextY),
toPrimitiveCoords(width, height)
);
nextX += width; nextX += width;
return result; return result;
} }
@ -87,10 +82,10 @@ public class Atlases {
// Wrapping // Wrapping
nextY += rowHeight; // Move to next row nextY += rowHeight; // Move to next row
rowHeight = height; // Next row is at least 'height' high rowHeight = height; // Next row is at least 'height' high
nextX = 0; // Start the row over nextX = 0; // Start the row over
} else { } else {
// Not wrapping // Not wrapping
// Update rowHeight if necessary // Update rowHeight if necessary
if (rowHeight < height) { if (rowHeight < height) {
rowHeight = height; rowHeight = height;
@ -99,10 +94,7 @@ public class Atlases {
} }
private Vec2 toPrimitiveCoords(int x, int y) { private Vec2 toPrimitiveCoords(int x, int y) {
return new Vec2( return new Vec2(toPrimitiveCoord(x), toPrimitiveCoord(y));
toPrimitiveCoord(x),
toPrimitiveCoord(y)
);
} }
private float toPrimitiveCoord(int c) { private float toPrimitiveCoord(int c) {
@ -112,87 +104,82 @@ public class Atlases {
public boolean canAddSprite(TextureData data) { public boolean canAddSprite(TextureData data) {
int width = data.getContentWidth(); int width = data.getContentWidth();
int height = data.getContentHeight(); int height = data.getContentHeight();
// Try to fit without wrapping // Try to fit without wrapping
if (nextY + height > getSize()) if (nextY + height > getSize())
// Does not fit vertically // Does not fit vertically
return false; return false;
if (nextX + width <= getSize()) if (nextX + width <= getSize())
// Can place at (nextX; nextY) // Can place at (nextX; nextY)
return true; return true;
// Try wrapping // Try wrapping
if (width > getSize()) if (width > getSize())
// GTFO. We couldn't fit if if we tried // GTFO. We couldn't fit if if we tried
return false; return false;
if (nextY + rowHeight + height > getSize()) if (nextY + rowHeight + height > getSize())
// Does not fit vertically when wrapped // Does not fit vertically when wrapped
return false; return false;
// Can place at (0; nextY + rowHeight) // Can place at (0; nextY + rowHeight)
return true; return true;
} }
public AtlasGroup getGroup() { public AtlasGroup getGroup() {
return group; return group;
} }
public TexturePrimitive getPrimitive() { public TexturePrimitive getPrimitive() {
return primitive; return primitive;
} }
public int getSize() { public int getSize() {
return editor.getBufferWidth(); return editor.getBufferWidth();
} }
} }
private static final TextureSettings SETTINGS = new TextureSettings(false); private static final TextureSettings SETTINGS = new TextureSettings(false);
private static final Map<Resource, Sprite> LOADED = private static final Map<Resource, Sprite> LOADED = new HashMap<>();
new HashMap<>(); private static final Multimap<AtlasGroup, Atlas> ATLASES = MultimapBuilder.hashKeys().arrayListValues().build();
private static final Multimap<AtlasGroup, Atlas> ATLASES =
MultimapBuilder.hashKeys().arrayListValues().build();
public static Sprite getSprite(Resource resource, AtlasGroup group) { public static Sprite getSprite(Resource resource, AtlasGroup group) {
return LOADED.computeIfAbsent(resource, k -> loadSprite(k, group)); return LOADED.computeIfAbsent(resource, k -> loadSprite(k, group));
} }
private static Sprite loadSprite(Resource resource, AtlasGroup group) { private static Sprite loadSprite(Resource resource, AtlasGroup group) {
try { try {
TextureDataEditor data = TextureDataEditor data = TextureLoader.loadPixels(resource, SETTINGS);
TextureLoader.loadPixels(resource, SETTINGS);
return loadSprite(data.getData(), group); return loadSprite(data.getData(), group);
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); CrashReports.report(e, "Could not load sprite %s into atlas group %s", resource, group);
return null;
} }
} }
public static Sprite loadSprite(TextureData data, AtlasGroup group) { public static Sprite loadSprite(TextureData data, AtlasGroup group) {
Atlas atlas = getReadyAtlas(group, data); Atlas atlas = getReadyAtlas(group, data);
return atlas.addSprite(data); return atlas.addSprite(data);
} }
private static Atlas getReadyAtlas(AtlasGroup group, TextureData data) { private static Atlas getReadyAtlas(AtlasGroup group, TextureData data) {
List<Atlas> atlases = (List<Atlas>) ATLASES.get(group); List<Atlas> atlases = (List<Atlas>) ATLASES.get(group);
if ( if (atlases.isEmpty() || !(atlases.get(atlases.size() - 1).canAddSprite(data))) {
atlases.isEmpty() ||
!(atlases.get(atlases.size() - 1).canAddSprite(data))
) {
Atlas newAtlas = new Atlas(group); Atlas newAtlas = new Atlas(group);
if (!newAtlas.canAddSprite(data)) { 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); atlases.add(newAtlas);
} }
return atlases.get(atlases.size() - 1); return atlases.get(atlases.size() - 1);
} }
@ -201,7 +188,8 @@ public class Atlases {
RenderTaskQueue.invokeLater(atlas.getPrimitive()::load); RenderTaskQueue.invokeLater(atlas.getPrimitive()::load);
}); });
} }
private Atlases() {} private Atlases() {
}
} }

View File

@ -19,10 +19,10 @@ public class ComplexTexture {
this.primitive = primitive; this.primitive = primitive;
this.assumedWidth = abstractWidth this.assumedWidth = abstractWidth
* primitive.getWidth() / (float) primitive.getBufferWidth(); / (float) primitive.getWidth() * primitive.getBufferWidth();
this.assumedHeight = abstractHeight this.assumedHeight = abstractHeight
* primitive.getHeight() / (float) primitive.getBufferHeight(); / (float) primitive.getHeight() * primitive.getBufferHeight();
} }
public Texture get(int x, int y, int width, int height) { public Texture get(int x, int y, int width, int height) {

View File

@ -6,21 +6,22 @@ import java.util.Map;
import ru.windcorp.progressia.common.resource.Resource; import ru.windcorp.progressia.common.resource.Resource;
import ru.windcorp.progressia.common.resource.ResourceManager; import ru.windcorp.progressia.common.resource.ResourceManager;
import ru.windcorp.progressia.common.util.crash.CrashReports;
public class SimpleTextures { public class SimpleTextures {
private static final TextureSettings SETTINGS = new TextureSettings(false); private static final TextureSettings SETTINGS = new TextureSettings(false);
private static final Map<Resource, Texture> TEXTURES = new HashMap<>(); private static final Map<Resource, Texture> TEXTURES = new HashMap<>();
public static Texture get(Resource resource) { public static Texture get(Resource resource) {
return TEXTURES.computeIfAbsent(resource, SimpleTextures::load); return TEXTURES.computeIfAbsent(resource, SimpleTextures::load);
} }
public static Texture get(String textureName) { public static Texture get(String textureName) {
return get(ResourceManager.getTextureResource(textureName)); return get(ResourceManager.getTextureResource(textureName));
} }
private static Texture load(Resource resource) { private static Texture load(Resource resource) {
try { try {
TextureDataEditor data = TextureDataEditor data =
@ -30,9 +31,10 @@ public class SimpleTextures {
new Sprite(new TexturePrimitive(data.getData())) new Sprite(new TexturePrimitive(data.getData()))
); );
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); CrashReports.report(e, "Could not load texture %s", resource);
return null;
} }
} }
private SimpleTextures() {} private SimpleTextures() {}

View File

@ -24,27 +24,28 @@ import java.util.Arrays;
import ru.windcorp.progressia.client.graphics.backend.OpenGLObjectTracker; import ru.windcorp.progressia.client.graphics.backend.OpenGLObjectTracker;
import ru.windcorp.progressia.client.graphics.backend.OpenGLObjectTracker.OpenGLDeletable; import ru.windcorp.progressia.client.graphics.backend.OpenGLObjectTracker.OpenGLDeletable;
import ru.windcorp.progressia.common.util.crash.CrashReports;
public class TexturePrimitive implements OpenGLDeletable { public class TexturePrimitive implements OpenGLDeletable {
private static final int NOT_LOADED = -1; private static final int NOT_LOADED = -1;
private static int[] currentlyBound = new int[32]; private static int[] currentlyBound = new int[32];
static { static {
Arrays.fill(currentlyBound, NOT_LOADED); Arrays.fill(currentlyBound, NOT_LOADED);
} }
private int handle = NOT_LOADED; private int handle = NOT_LOADED;
private TextureData pixels; private TextureData pixels;
public TexturePrimitive(TextureData pixels) { public TexturePrimitive(TextureData pixels) {
this.pixels = pixels; this.pixels = pixels;
} }
public TextureData getData() { public TextureData getData() {
return pixels; return pixels;
} }
public int getBufferWidth() { public int getBufferWidth() {
return pixels.getBufferWidth(); return pixels.getBufferWidth();
} }
@ -64,21 +65,21 @@ public class TexturePrimitive implements OpenGLDeletable {
public boolean isLoaded() { public boolean isLoaded() {
return handle != NOT_LOADED; return handle != NOT_LOADED;
} }
public void bind(int slot) { public void bind(int slot) {
if (!isLoaded()) { if (!isLoaded()) {
load(); load();
} }
if (currentlyBound[slot] == handle) { if (currentlyBound[slot] == handle) {
return; return;
} }
int code = GL_TEXTURE0 + slot; int code = GL_TEXTURE0 + slot;
glActiveTexture(code); glActiveTexture(code);
glBindTexture(GL_TEXTURE_2D, handle); glBindTexture(GL_TEXTURE_2D, handle);
currentlyBound[slot] = handle; currentlyBound[slot] = handle;
} }
@ -87,9 +88,9 @@ public class TexturePrimitive implements OpenGLDeletable {
handle = pixels.load(); handle = pixels.load();
OpenGLObjectTracker.register(this); OpenGLObjectTracker.register(this);
if (handle < 0) { if (handle < 0) {
throw new RuntimeException("oops"); CrashReports.report(null, "Failed to allocate texture");
} }
} }

View File

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

View File

@ -20,45 +20,36 @@ package ru.windcorp.progressia.client.graphics.world;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.lwjgl.glfw.GLFW; import glm.mat._4.Mat4;
import glm.Glm;
import glm.mat._3.Mat3;
import glm.vec._2.Vec2;
import glm.vec._3.Vec3; import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.Client; 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.comms.controls.InputBasedControls;
import ru.windcorp.progressia.client.graphics.Layer; import ru.windcorp.progressia.client.graphics.Layer;
import ru.windcorp.progressia.client.graphics.backend.FaceCulling; 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.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.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.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.collision.colliders.Collider;
import ru.windcorp.progressia.common.util.FloatMathUtils; 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.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 { 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 WorldRenderHelper helper = new WorldRenderHelper();
private final Client client; private final Client client;
private final InputBasedControls inputBasedControls; private final InputBasedControls inputBasedControls;
private final TestPlayerControls tmp_testControls = TestPlayerControls.getInstance();
public LayerWorld(Client client) { public LayerWorld(Client client) {
super("World"); super("World");
@ -79,38 +70,14 @@ public class LayerWorld extends Layer {
@Override @Override
protected void doRender() { protected void doRender() {
if (client.getLocalPlayer() != null) {
tmp_handleControls();
}
Camera camera = client.getCamera(); Camera camera = client.getCamera();
if (camera.hasAnchor()) { if (camera.hasAnchor()) {
renderWorld(); renderWorld();
} }
}
private void tmp_handleControls() {
EntityData player = client.getLocalPlayer();
angMat.identity().rotateZ(player.getYaw()); if (client.getLocalPlayer() != null) {
client.getLocalPlayer().update(client.getWorld());
Vec3 movement = Vectors.grab3(); }
// Horizontal and vertical max control speed
final float movementSpeed = 0.1f * 60.0f;
// (0; 1], 1 is instant change, 0 is no control authority
final float controlAuthority = 0.1f;
movement.set(movementForward, -movementRight, 0);
if (movementForward != 0 && movementRight != 0) movement.normalize();
angMat.mul_(movement); // bug in jglm, .mul() and mul_() are swapped
movement.z = movementUp;
movement.mul(movementSpeed);
movement.sub(player.getVelocity());
movement.mul(controlAuthority);
player.getVelocity().add(movement);
Vectors.release(movement);
} }
private void renderWorld() { private void renderWorld() {
@ -128,150 +95,117 @@ public class LayerWorld extends Layer {
private final Collider.ColliderWorkspace tmp_colliderWorkspace = new Collider.ColliderWorkspace(); private final Collider.ColliderWorkspace tmp_colliderWorkspace = new Collider.ColliderWorkspace();
private final List<Collideable> tmp_collideableList = new ArrayList<>(); 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() { private void tmp_doEveryFrame() {
float tickLength = (float) GraphicsInterface.getFrameLength();
try { try {
if (RENDER_AABBS) { tmp_performCollisions(tickLength);
for (EntityData data : this.client.getWorld().getData().getEntities()) { tmp_drawSelectionBox();
CollisionModel model = data.getCollisionModel();
if (model instanceof AABB) {
AABBRenderer.renderAABB((AABB) model, helper);
} else if (model instanceof CompoundCollisionModel) {
AABBRenderer.renderAABBsInCompound((CompoundCollisionModel) model, helper);
}
}
}
tmp_collideableList.clear(); tmp_testControls.applyPlayerControls();
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;
for (EntityData data : this.client.getWorld().getData().getEntities()) { 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); 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 @Override
protected void handleInput(Input input) { protected void handleInput(Input input) {
if (input.isConsumed()) return; if (input.isConsumed()) return;
InputEvent event = input.getEvent(); tmp_testControls.handleInput(input);
if (event instanceof KeyEvent) {
if (onKeyEvent((KeyEvent) event)) {
input.consume();
}
} else if (event instanceof CursorMoveEvent) {
onMouseMoved((CursorMoveEvent) event);
input.consume();
}
if (!input.isConsumed()) { if (!input.isConsumed()) {
inputBasedControls.handleInput(input); 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.lang.ref.WeakReference;
import java.util.*; import java.util.*;
import ru.windcorp.progressia.common.util.crash.CrashReports;
public class Localizer { public class Localizer {
private static final Localizer INSTANCE = new Localizer("assets/languages/", "en-US"); private static final Localizer INSTANCE = new Localizer("assets/languages/", "en-US");
@ -17,7 +19,7 @@ public class Localizer {
private final Collection<WeakReference<LocaleListener>> listeners = private final Collection<WeakReference<LocaleListener>> listeners =
Collections.synchronizedCollection(new LinkedList<>()); 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) { public Localizer(String langFolder) {
this.langFolder = langFolder; this.langFolder = langFolder;
this.langList = new Parser(langFolder + "lang_list.txt").parse(); this.langList = new Parser(langFolder + "lang_list.txt").parse();
@ -41,7 +43,7 @@ public class Localizer {
data = new Parser(langFolder + this.language + ".lang").parse(); data = new Parser(langFolder + this.language + ".lang").parse();
pokeListeners(language); pokeListeners(language);
} else { } 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) { private void pokeListeners(String newLanguage) {
//TODO extract as weak bus listener class // TODO extract as weak bus listener class
synchronized (listeners) { synchronized (listeners) {
Iterator<WeakReference<LocaleListener>> iterator = listeners.iterator(); Iterator<WeakReference<LocaleListener>> iterator = listeners.iterator();
while (iterator.hasNext()) { 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.EscapeException;
import ru.windcorp.jputil.chars.Escaper; import ru.windcorp.jputil.chars.Escaper;
import ru.windcorp.progressia.common.resource.ResourceManager; import ru.windcorp.progressia.common.resource.ResourceManager;
import ru.windcorp.progressia.common.util.crash.CrashReports;
import java.io.IOException; import java.io.IOException;
import java.io.Reader; import java.io.Reader;
@ -54,7 +55,7 @@ public class Parser {
if (c == '=') { if (c == '=') {
String key = ESCAPER.escape(stringBuilder.toString()); String key = ESCAPER.escape(stringBuilder.toString());
stringBuilder.setLength(0); stringBuilder.setLength(0);
rawData.read(); //skip a char rawData.read(); // skip a char
while (true) { while (true) {
code = rawData.read(); code = rawData.read();
if (code == -1) { if (code == -1) {
@ -80,7 +81,8 @@ public class Parser {
} }
} catch (IOException | EscapeException e) { } catch (IOException | EscapeException e) {
throw new RuntimeException(e); CrashReports.report(e, "Could not parse language file %s", filePath);
return null;
} }
return parsedData; 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.EntityRenderRegistry;
import ru.windcorp.progressia.client.world.entity.EntityRenderable; import ru.windcorp.progressia.client.world.entity.EntityRenderable;
import ru.windcorp.progressia.common.world.ChunkData; 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.WorldData;
import ru.windcorp.progressia.common.world.WorldDataListener;
import ru.windcorp.progressia.common.world.entity.EntityData; import ru.windcorp.progressia.common.world.entity.EntityData;
public class WorldRender { public class WorldRender {
@ -43,9 +45,18 @@ public class WorldRender {
public WorldRender(WorldData data) { public WorldRender(WorldData data) {
this.data = data; this.data = data;
for (ChunkData chunkData : data.getChunks()) { data.addListener(ChunkDataListeners.createAdder(new ChunkUpdateListener(this)));
chunks.put(chunkData, new ChunkRender(this, chunkData)); 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() { public WorldData getData() {

View File

@ -18,13 +18,13 @@
package ru.windcorp.progressia.client.world.block; package ru.windcorp.progressia.client.world.block;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper; 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.client.graphics.model.Renderable;
import ru.windcorp.progressia.common.util.Namespaced;
public abstract class BlockRender extends Namespaced { public abstract class BlockRender extends Namespaced {
public BlockRender(String namespace, String name) { public BlockRender(String id) {
super(namespace, name); super(id);
} }
public void render(ShapeRenderHelper renderer) { 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 class BlockRenderNone extends BlockRender {
public BlockRenderNone(String namespace, String name) { public BlockRenderNone(String id) {
super(namespace, name); super(id);
} }
@Override @Override

View File

@ -23,25 +23,22 @@ import ru.windcorp.progressia.common.world.block.BlockFace;
public class BlockRenderOpaqueCube extends BlockRenderTexturedCube { public class BlockRenderOpaqueCube extends BlockRenderTexturedCube {
public BlockRenderOpaqueCube( public BlockRenderOpaqueCube(
String namespace, String name, String id,
Texture topTexture, Texture bottomTexture, Texture topTexture, Texture bottomTexture,
Texture northTexture, Texture southTexture, Texture northTexture, Texture southTexture,
Texture eastTexture, Texture westTexture Texture eastTexture, Texture westTexture
) { ) {
super( super(
namespace, name, id,
topTexture, bottomTexture, topTexture, bottomTexture,
northTexture, southTexture, northTexture, southTexture,
eastTexture, westTexture eastTexture, westTexture
); );
} }
public BlockRenderOpaqueCube( public BlockRenderOpaqueCube(String id, Texture texture) {
String namespace, String name,
Texture texture
) {
this( this(
namespace, name, id,
texture, texture, texture, texture,
texture, texture, 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.SimpleTexture;
import ru.windcorp.progressia.client.graphics.texture.Texture; import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.common.resource.ResourceManager; 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 = private static final BlockRenderRegistry INSTANCE =
new BlockRenderRegistry(); new BlockRenderRegistry();

View File

@ -36,12 +36,12 @@ implements OpaqueCube {
private final Map<BlockFace, Texture> textures = new HashMap<>(); private final Map<BlockFace, Texture> textures = new HashMap<>();
public BlockRenderTexturedCube( public BlockRenderTexturedCube(
String namespace, String name, String id,
Texture topTexture, Texture bottomTexture, Texture topTexture, Texture bottomTexture,
Texture northTexture, Texture southTexture, Texture northTexture, Texture southTexture,
Texture eastTexture, Texture westTexture Texture eastTexture, Texture westTexture
) { ) {
super(namespace, name); super(id);
textures.put(TOP, topTexture); textures.put(TOP, topTexture);
textures.put(BOTTOM, bottomTexture); textures.put(BOTTOM, bottomTexture);

View File

@ -23,25 +23,22 @@ import ru.windcorp.progressia.common.world.block.BlockFace;
public class BlockRenderTransparentCube extends BlockRenderTexturedCube { public class BlockRenderTransparentCube extends BlockRenderTexturedCube {
public BlockRenderTransparentCube( public BlockRenderTransparentCube(
String namespace, String name, String id,
Texture topTexture, Texture bottomTexture, Texture topTexture, Texture bottomTexture,
Texture northTexture, Texture southTexture, Texture northTexture, Texture southTexture,
Texture eastTexture, Texture westTexture Texture eastTexture, Texture westTexture
) { ) {
super( super(
namespace, name, id,
topTexture, bottomTexture, topTexture, bottomTexture,
northTexture, southTexture, northTexture, southTexture,
eastTexture, westTexture eastTexture, westTexture
); );
} }
public BlockRenderTransparentCube( public BlockRenderTransparentCube(String id, Texture texture) {
String namespace, String name,
Texture texture
) {
this( this(
namespace, name, id,
texture, texture, texture, texture,
texture, texture, 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 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 abstract class ChunkRenderOptimizerSupplier extends Namespaced {
public ChunkRenderOptimizerSupplier(String namespace, String name) { public ChunkRenderOptimizerSupplier(String id) {
super(namespace, name); super(id);
} }
public abstract ChunkRenderOptimizer createOptimizer(); public abstract ChunkRenderOptimizer createOptimizer();
public static ChunkRenderOptimizerSupplier of( public static ChunkRenderOptimizerSupplier of(
String namespace, String name, String id,
Supplier<ChunkRenderOptimizer> supplier Supplier<ChunkRenderOptimizer> supplier
) { ) {
return new ChunkRenderOptimizerSupplier(namespace, name) { return new ChunkRenderOptimizerSupplier(id) {
@Override @Override
public ChunkRenderOptimizer createOptimizer() { public ChunkRenderOptimizer createOptimizer() {
return supplier.get(); return supplier.get();

View File

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

View File

@ -1,12 +1,12 @@
package ru.windcorp.progressia.client.world.entity; 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; import ru.windcorp.progressia.common.world.entity.EntityData;
public abstract class EntityRender extends Namespaced { public abstract class EntityRender extends Namespaced {
public EntityRender(String namespace, String name) { public EntityRender(String id) {
super(namespace, name); super(id);
} }
public abstract EntityRenderable createRenderable(EntityData entity); 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.TexturePrimitive;
import ru.windcorp.progressia.client.graphics.texture.TextureSettings; import ru.windcorp.progressia.client.graphics.texture.TextureSettings;
import ru.windcorp.progressia.common.resource.ResourceManager; 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 = private static final EntityRenderRegistry INSTANCE =
new EntityRenderRegistry(); new EntityRenderRegistry();
@ -28,7 +29,8 @@ public class EntityRenderRegistry extends NamespacedRegistry<EntityRender> {
).getData() ).getData()
); );
} catch (IOException e) { } 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 java.lang.Math.*;
import static ru.windcorp.progressia.common.util.FloatMathUtils.*; 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.mat._4.Mat4;
import glm.vec._3.Vec3; 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.Renderable;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper; 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; import ru.windcorp.progressia.common.world.entity.EntityData;
public class QuadripedModel extends EntityRenderable { public class QuadripedModel extends NPedModel {
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 static class Leg extends BodyPart { public static class Leg extends BodyPart {
private final float animationOffset; private final float animationOffset;
@ -95,33 +24,19 @@ public class QuadripedModel extends EntityRenderable {
} }
@Override @Override
protected void applyTransform(Mat4 mat, QuadripedModel model) { protected void applyTransform(Mat4 mat, NPedModel model) {
mat.rotateY(sin(model.walkingFrequency * model.walkingAnimationParameter + animationOffset) * model.walkingSwing * model.velocityCoeff); 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 leftForeLeg, rightForeLeg;
private final Leg leftHindLeg, rightHindLeg; 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 walkingSwing = (float) toRadians(30);
private float bodyYaw = Float.NaN;
private float headYaw;
private float headPitch;
public QuadripedModel( public QuadripedModel(
EntityData entity, EntityData entity,
@ -132,105 +47,30 @@ public class QuadripedModel extends EntityRenderable {
float scale float scale
) { ) {
super(entity); super(entity, body, head, scale);
this.body = body;
this.head = head;
this.leftForeLeg = leftForeLeg; this.leftForeLeg = leftForeLeg;
this.rightForeLeg = rightForeLeg; this.rightForeLeg = rightForeLeg;
this.leftHindLeg = leftHindLeg; this.leftHindLeg = leftHindLeg;
this.rightHindLeg = rightHindLeg; this.rightHindLeg = rightHindLeg;
this.scale = scale;
} }
@Override @Override
public void render(ShapeRenderHelper renderer) { protected void renderBodyParts(ShapeRenderHelper renderer) {
renderer.pushTransform().scale(scale).rotateZ(bodyYaw); super.renderBodyParts(renderer);
body.render(renderer, this); this.leftForeLeg.render(renderer, this);
head.render(renderer, this); this.rightForeLeg.render(renderer, this);
leftForeLeg.render(renderer, this); this.leftHindLeg.render(renderer, this);
rightForeLeg.render(renderer, this); this.rightHindLeg.render(renderer, this);
leftHindLeg.render(renderer, this);
rightHindLeg.render(renderer, this);
renderer.popTransform();
accountForVelocity();
evaluateAngles();
}
private void evaluateAngles() {
float globalYaw = normalizeAngle(getData().getYaw());
if (Float.isNaN(bodyYaw)) {
bodyYaw = globalYaw;
headYaw = 0;
} else {
headYaw = normalizeAngle(globalYaw - bodyYaw);
if (headYaw > +head.maxYaw) {
bodyYaw += headYaw - +head.maxYaw;
headYaw = +head.maxYaw;
} else if (headYaw < -head.maxYaw) {
bodyYaw += headYaw - -head.maxYaw;
headYaw = -head.maxYaw;
}
}
bodyYaw = normalizeAngle(bodyYaw);
headPitch = Glm.clamp(
getData().getPitch(),
-head.maxPitch, head.maxPitch
);
}
private void accountForVelocity() {
Vec3 horizontal = Vectors.grab3();
horizontal.set(getData().getVelocity());
horizontal.z = 0;
velocity = horizontal.length();
evaluateVelocityCoeff();
// TODO switch to world time
walkingAnimationParameter += velocity * GraphicsInterface.getFrameLength() * 1000;
bodyYaw += velocityCoeff * normalizeAngle(
(float) (atan2(horizontal.y, horizontal.x) - bodyYaw)
) * min(1, GraphicsInterface.getFrameLength() * 10);
Vectors.release(horizontal);
} }
private void evaluateVelocityCoeff() { public float getWalkingSwing() {
if (velocity * velocityCutoff > 1) { return walkingSwing;
velocityCoeff = 1;
} else {
velocityCoeff = velocity * velocityCutoff;
velocityCoeff *= velocityCoeff;
}
} }
@Override public QuadripedModel setWalkingSwing(float walkingSwing) {
public void getViewPoint(Vec3 output) { this.walkingSwing = walkingSwing;
Mat4 m = Matrices.grab4(); return this;
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);
} }
} }

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.ShapeRenderHelper;
import ru.windcorp.progressia.client.graphics.model.Renderable; import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizer; 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; import ru.windcorp.progressia.common.world.block.BlockFace;
public class TileRender extends Namespaced { public class TileRender extends Namespaced {
public TileRender(String namespace, String name) { public TileRender(String id) {
super(namespace, name); super(id);
} }
public void render(ShapeRenderHelper renderer, BlockFace face) { public void render(ShapeRenderHelper renderer, BlockFace face) {

View File

@ -18,10 +18,10 @@ public class TileRenderGrass extends TileRender implements OpaqueTile {
private final Texture sideTexture; private final Texture sideTexture;
public TileRenderGrass( public TileRenderGrass(
String namespace, String name, String id,
Texture top, Texture side Texture top, Texture side
) { ) {
super(namespace, name); super(id);
this.topTexture = top; this.topTexture = top;
this.sideTexture = side; 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.SimpleTexture;
import ru.windcorp.progressia.client.graphics.texture.Texture; import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.common.resource.ResourceManager; 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(); private static final TileRenderRegistry INSTANCE = new TileRenderRegistry();

View File

@ -16,8 +16,8 @@ public class TileRenderSimple extends TileRender implements OpaqueTile {
private final Texture texture; private final Texture texture;
public TileRenderSimple(String namespace, String name, Texture texture) { public TileRenderSimple(String id, Texture texture) {
super(namespace, name); super(id);
this.texture = texture; 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,93 +1,113 @@
package ru.windcorp.progressia.common.collision; package ru.windcorp.progressia.common.collision;
import java.util.Collection;
import java.util.Map;
import glm.vec._3.Vec3; 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 class AABBWallImpl implements Wall {
private final Vec3 originOffset = new Vec3();
private final Vec3 widthSelector = new Vec3();
private final Vec3 heightSelector = new Vec3();
private final Map<BlockFace, CollisionWall> faces = BlockFace.mapToFaces( public AABBWallImpl(
new CollisionWall(-0.5f, -0.5f, -0.5f, +1, 0, 0, 0, 0, +1), float ox, float oy, float oz,
new CollisionWall(+0.5f, -0.5f, -0.5f, 0, +1, 0, 0, 0, +1), float wx, float wy, float wz,
new CollisionWall(+0.5f, +0.5f, -0.5f, -1, 0, 0, 0, 0, +1), float hx, float hy, float hz
new CollisionWall(-0.5f, +0.5f, -0.5f, 0, -1, 0, 0, 0, +1), ) {
this.originOffset.set(ox, oy, oz);
new CollisionWall(-0.5f, -0.5f, +0.5f, +1, 0, 0, 0, +1, 0), this.widthSelector.set(wx, wy, wz);
new CollisionWall(-0.5f, -0.5f, -0.5f, 0, +1, 0, +1, 0, 0) 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 origin = new Vec3();
private final Vec3 size = new Vec3(); private final Vec3 size = new Vec3();
public AABB(Vec3 origin, Vec3 size) { public AABB(Vec3 origin, Vec3 size) {
this.origin.set(origin); this(origin.x, origin.y, origin.z, size.x, size.y, size.z);
this.size.set(size);
for (CollisionWall wall : getFaces()) {
wall.moveOrigin(origin);
wall.getWidth().mul(size);
wall.getHeight().mul(size);
}
} }
public AABB( public AABB(
float ox, float oy, float oz, float ox, float oy, float oz,
float xSize, float ySize, float zSize float xSize, float ySize, float zSize
) { ) {
this.origin.set(ox, oy, oz); this.origin.set(ox, oy, oz);
this.size.set(xSize, ySize, zSize); 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() { public Vec3 getOrigin() {
return origin; return origin;
} }
@Override
public void getOrigin(Vec3 output) {
output.set(origin);
}
@Override @Override
public void setOrigin(Vec3 origin) { public void setOrigin(Vec3 origin) {
for (CollisionWall wall : getFaces()) {
wall.getOrigin().sub(this.origin).add(origin);
}
this.origin.set(origin); this.origin.set(origin);
} }
@Override @Override
public void moveOrigin(Vec3 displacement) { public void moveOrigin(Vec3 displacement) {
for (CollisionWall wall : getFaces()) {
wall.getOrigin().add(displacement);
}
this.origin.add(displacement); this.origin.add(displacement);
} }
public Vec3 getSize() { public Vec3 getSize() {
return size; return size;
} }
@Override
public void getSize(Vec3 output) {
output.set(size);
}
public void setSize(Vec3 size) { public void setSize(Vec3 size) {
setSize(size.x, size.y, size.z); setSize(size.x, size.y, size.z);
} }
public void setSize(float xSize, float ySize, float zSize) { 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); 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 { 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; this.models = models;
} }
@ -18,7 +18,7 @@ public class CompoundCollisionModel implements CollisionModel {
this(ImmutableList.copyOf(models)); this(ImmutableList.copyOf(models));
} }
public Collection<CollisionModel> getModels() { public Collection<? extends CollisionModel> getModels() {
return models; 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.mat._3.Mat3;
import glm.vec._3.Vec3; import glm.vec._3.Vec3;
import ru.windcorp.progressia.common.collision.AABB; import ru.windcorp.progressia.common.collision.*;
import ru.windcorp.progressia.common.collision.Collideable;
import ru.windcorp.progressia.common.collision.CollisionWall;
import ru.windcorp.progressia.common.collision.colliders.Collider.ColliderWorkspace; import ru.windcorp.progressia.common.collision.colliders.Collider.ColliderWorkspace;
import ru.windcorp.progressia.common.collision.colliders.Collider.Collision; import ru.windcorp.progressia.common.collision.colliders.Collider.Collision;
import ru.windcorp.progressia.common.util.Matrices; import ru.windcorp.progressia.common.util.Matrices;
import ru.windcorp.progressia.common.util.Vectors; import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.block.BlockFace;
class AABBWithAABBCollider { class AABBoidCollider {
static Collider.Collision computeModelCollision( static Collider.Collision computeModelCollision(
Collideable aBody, Collideable bBody, Collideable aBody, Collideable bBody,
AABB aModel, AABB bModel, AABBoid aModel, AABBoid bModel,
float tickLength, float tickLength,
ColliderWorkspace workspace ColliderWorkspace workspace
) { ) {
Collideable obstacleBody = bBody; Collideable obstacleBody = bBody;
Collideable colliderBody = aBody; Collideable colliderBody = aBody;
AABB obstacleModel = bModel; AABBoid obstacleModel = bModel;
AABB colliderModel = aModel; AABBoid colliderModel = aModel;
Collision result = null; Collision result = null;
@ -32,7 +31,8 @@ class AABBWithAABBCollider {
computeCollisionVelocity(collisionVelocity, obstacleBody, colliderBody); computeCollisionVelocity(collisionVelocity, obstacleBody, colliderBody);
// For every wall of collision space // 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( Collision collision = computeWallCollision(
wall, colliderModel, wall, colliderModel,
@ -80,12 +80,21 @@ class AABBWithAABBCollider {
Vectors.release(colliderVelocity); Vectors.release(colliderVelocity);
} }
private static AABB createOriginCollisionSpace(AABB obstacle, AABB collider, AABB output) { private static AABB createOriginCollisionSpace(AABBoid obstacle, AABBoid collider, AABB output) {
output.setOrigin(obstacle.getOrigin()); Vec3 obstacleOrigin = Vectors.grab3();
Vec3 obstacleSize = Vectors.grab3();
Vec3 colliderSize = Vectors.grab3();
Vec3 size = Vectors.grab3().set(obstacle.getSize()).add(collider.getSize()); obstacle.getOrigin(obstacleOrigin);
output.setSize(size); output.setOrigin(obstacleOrigin);
Vectors.release(size);
obstacle.getSize(obstacleSize);
collider.getSize(colliderSize);
output.setSize(obstacleSize.add(colliderSize));
Vectors.release(obstacleOrigin);
Vectors.release(obstacleSize);
Vectors.release(colliderSize);
return output; return output;
} }
@ -134,27 +143,34 @@ class AABBWithAABBCollider {
* If all conditions are satisfied, then the moment of impact is t0 + t. * If all conditions are satisfied, then the moment of impact is t0 + t.
*/ */
private static Collision computeWallCollision( private static Collision computeWallCollision(
CollisionWall obstacleWall, Wall obstacleWall,
AABB colliderModel, AABBoid colliderModel,
Vec3 collisionVelocity, Vec3 collisionVelocity,
float tickLength, ColliderWorkspace workspace, float tickLength, ColliderWorkspace workspace,
Collideable aBody, Collideable bBody Collideable aBody, Collideable bBody
) { ) {
Vec3 w = obstacleWall.getWidth(); Vec3 w = Vectors.grab3();
Vec3 h = obstacleWall.getHeight(); Vec3 h = Vectors.grab3();
Vec3 v = Vectors.grab3(); Vec3 v = Vectors.grab3();
Mat3 m = Matrices.grab3(); // The matrix [w h -v] Mat3 m = Matrices.grab3(); // The matrix [w h -v]
Vec3 r = Vectors.grab3(); Vec3 r = Vectors.grab3();
Vec3 r_line = Vectors.grab3();
Vec3 r_wall = Vectors.grab3();
Vec3 xyt = Vectors.grab3(); Vec3 xyt = Vectors.grab3();
try { try {
obstacleWall.getWidth(w);
obstacleWall.getHeight(h);
v.set(collisionVelocity); v.set(collisionVelocity);
if (isExiting(v, w, h)) { if (isExiting(v, w, h)) {
return null; 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()); m.c0(w).c1(h).c2(v.negate());
if (Math.abs(m.det()) < 1e-6) { if (Math.abs(m.det()) < 1e-6) {
@ -179,9 +195,13 @@ class AABBWithAABBCollider {
return workspace.grab().set(aBody, bBody, obstacleWall, t); return workspace.grab().set(aBody, bBody, obstacleWall, t);
} finally { } finally {
Vectors.release(w);
Vectors.release(h);
Vectors.release(v); Vectors.release(v);
Vectors.release(r);
Matrices.release(m); Matrices.release(m);
Vectors.release(r);
Vectors.release(r_line);
Vectors.release(r_wall);
Vectors.release(xyt); Vectors.release(xyt);
} }
} }
@ -193,6 +213,6 @@ class AABBWithAABBCollider {
return result; return result;
} }
private AABBWithAABBCollider() {} private AABBoidCollider() {}
} }

View File

@ -6,22 +6,35 @@ import java.util.List;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import glm.vec._3.Vec3; import glm.vec._3.Vec3;
import ru.windcorp.progressia.common.collision.AABB; import ru.windcorp.progressia.common.collision.*;
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.util.LowOverheadCache; import ru.windcorp.progressia.common.util.LowOverheadCache;
import ru.windcorp.progressia.common.util.Vectors; import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.WorldData;
public class Collider { public class Collider {
private static final int MAX_COLLISIONS_PER_ENTITY = 64; 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( public static void performCollisions(
List<? extends Collideable> colls, List<? extends Collideable> colls,
CollisionClock clock, WorldData world,
float tickLength, float tickLength,
ColliderWorkspace workspace ColliderWorkspace workspace
) { ) {
@ -37,12 +50,12 @@ public class Collider {
return; return;
} }
Collision firstCollision = getFirstCollision(colls, tickLength, workspace); Collision firstCollision = getFirstCollision(colls, tickLength, world, workspace);
if (firstCollision == null) { if (firstCollision == null) {
break; break;
} else { } else {
collide(firstCollision, colls, clock, tickLength, workspace); collide(firstCollision, colls, world, tickLength, workspace);
workspace.release(firstCollision); workspace.release(firstCollision);
collisionCount++; collisionCount++;
@ -50,45 +63,49 @@ public class Collider {
} }
} }
advanceTime(colls, clock, tickLength); advanceTime(colls, null, world, tickLength);
} }
private static Collision getFirstCollision( private static Collision getFirstCollision(
List<? extends Collideable> colls, List<? extends Collideable> colls,
float tickLength, float tickLength,
WorldData world,
ColliderWorkspace workspace ColliderWorkspace workspace
) { ) {
Collision result = null; Collision result = null;
Collideable worldColl = workspace.worldCollisionHelper.getCollideable();
// For every pair of colls // For every pair of colls
for (int i = 0; i < colls.size(); ++i) { for (int i = 0; i < colls.size(); ++i) {
Collideable a = colls.get(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) { for (int j = i + 1; j < colls.size(); ++j) {
Collideable b = colls.get(j); Collideable b = colls.get(j);
Collision collision = getCollision(a, b, tickLength, workspace); Collision collision = getCollision(a, b, tickLength, workspace);
result = workspace.updateLatestCollision(result, collision);
// 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);
}
} }
} }
return result; 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( static Collision getCollision(
Collideable a, Collideable a,
Collideable b, Collideable b,
@ -108,10 +125,10 @@ public class Collider {
float tickLength, float tickLength,
ColliderWorkspace workspace ColliderWorkspace workspace
) { ) {
if (aModel instanceof AABB && bModel instanceof AABB) { if (aModel instanceof AABBoid && bModel instanceof AABBoid) {
return AABBWithAABBCollider.computeModelCollision( return AABBoidCollider.computeModelCollision(
aBody, bBody, aBody, bBody,
(AABB) aModel, (AABB) bModel, (AABBoid) aModel, (AABBoid) bModel,
tickLength, tickLength,
workspace workspace
); );
@ -144,11 +161,11 @@ public class Collider {
Collision collision, Collision collision,
Collection<? extends Collideable> colls, Collection<? extends Collideable> colls,
CollisionClock clock, WorldData world,
float tickLength, float tickLength,
ColliderWorkspace workspace ColliderWorkspace workspace
) { ) {
advanceTime(colls, clock, collision.time); advanceTime(colls, collision, world, collision.time);
boolean doNotHandle = false; boolean doNotHandle = false;
@ -237,7 +254,7 @@ public class Collider {
Vec3 du_a = Vectors.grab3(); Vec3 du_a = Vectors.grab3();
Vec3 du_b = 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.a.getCollideableVelocity(v_a);
collision.b.getCollideableVelocity(v_b); collision.b.getCollideableVelocity(v_b);
@ -274,8 +291,6 @@ public class Collider {
collision.a.changeVelocityOnCollision(du_a); collision.a.changeVelocityOnCollision(du_a);
collision.b.changeVelocityOnCollision(du_b); collision.b.changeVelocityOnCollision(du_b);
separate(collision, n, m_a, m_b);
// JGML is still to fuck // JGML is still to fuck
Vectors.release(n); Vectors.release(n);
Vectors.release(v_a); Vectors.release(v_a);
@ -287,35 +302,26 @@ public class Collider {
Vectors.release(du_b); 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( private static void advanceTime(
Collection<? extends Collideable> colls, Collection<? extends Collideable> colls,
CollisionClock clock, Collision exceptions,
WorldData world,
float step float step
) { ) {
clock.advanceTime(step); world.advanceTime(step);
Vec3 tmp = Vectors.grab3(); Vec3 tmp = Vectors.grab3();
for (Collideable coll : colls) { for (Collideable coll : colls) {
coll.getCollideableVelocity(tmp); 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); coll.moveAsCollideable(tmp);
} }
@ -328,6 +334,8 @@ public class Collider {
new LowOverheadCache<>(Collision::new); new LowOverheadCache<>(Collision::new);
AABB dummyAABB = new AABB(0, 0, 0, 1, 1, 1); AABB dummyAABB = new AABB(0, 0, 0, 1, 1, 1);
WorldCollisionHelper worldCollisionHelper = new WorldCollisionHelper();
Collision grab() { Collision grab() {
return collisionCache.grab(); return collisionCache.grab();
@ -337,12 +345,35 @@ public class Collider {
collisionCache.release(object); 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 { static class Collision {
public Collideable a; public Collideable a;
public Collideable b; 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. * Time offset from the start of the tick.
@ -350,12 +381,15 @@ public class Collider {
*/ */
public float time; 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.a = a;
this.b = b; this.b = b;
this.wall.getOrigin().set(wall.getOrigin()); wall.getWidth(wallWidth);
this.wall.getWidth().set(wall.getWidth()); wall.getHeight(wallHeight);
this.wall.getHeight().set(wall.getHeight());
this.time = time; this.time = time;
return this; return this;

View File

@ -1,11 +1,11 @@
package ru.windcorp.progressia.common.comms.controls; 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 class ControlData extends Namespaced {
public ControlData(String namespace, String name) { public ControlData(String id) {
super(namespace, name); super(id);
} }
} }

View File

@ -1,8 +1,8 @@
package ru.windcorp.progressia.common.comms.controls; 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(); private static final ControlDataRegistry INSTANCE = new ControlDataRegistry();

View File

@ -6,8 +6,8 @@ public class PacketControl extends Packet {
private final ControlData control; private final ControlData control;
public PacketControl(String namespace, String name, ControlData control) { public PacketControl(String id, ControlData control) {
super(namespace, name); super(id);
this.control = control; this.control = control;
} }

View File

@ -1,11 +1,11 @@
package ru.windcorp.progressia.common.comms.packets; 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 class Packet extends Namespaced {
public Packet(String namespace, String name) { public Packet(String id) {
super(namespace, name); super(id);
} }
} }

View File

@ -5,7 +5,11 @@ public class PacketSetLocalPlayer extends Packet {
private long localPlayerEntityId; private long localPlayerEntityId;
public PacketSetLocalPlayer(long entityId) { public PacketSetLocalPlayer(long entityId) {
super("Core", "SetLocalPlayer"); this("Core:SetLocalPlayer", entityId);
}
protected PacketSetLocalPlayer(String id, long entityId) {
super(id);
this.localPlayerEntityId = entityId; this.localPlayerEntityId = entityId;
} }

View File

@ -4,8 +4,8 @@ import ru.windcorp.progressia.common.world.WorldData;
public abstract class PacketWorldChange extends Packet { public abstract class PacketWorldChange extends Packet {
public PacketWorldChange(String namespace, String name) { public PacketWorldChange(String id) {
super(namespace, name); super(id);
} }
public abstract void apply(WorldData world); public abstract void apply(WorldData world);

View File

@ -30,50 +30,53 @@ import com.google.common.io.CharStreams;
import ru.windcorp.progressia.Progressia; import ru.windcorp.progressia.Progressia;
import ru.windcorp.progressia.common.util.Named; import ru.windcorp.progressia.common.util.Named;
import ru.windcorp.progressia.common.util.crash.CrashReports;
public class Resource extends Named { public class Resource extends Named {
public Resource(String name) { public Resource(String name) {
super(name); super(name);
} }
public InputStream getInputStream() { public InputStream getInputStream() {
// TODO Do proper resource lookup // TODO Do proper resource lookup
return Progressia.class.getClassLoader().getResourceAsStream(getName()); return Progressia.class.getClassLoader().getResourceAsStream(getName());
} }
public Reader getReader() { public Reader getReader() {
return new InputStreamReader(getInputStream()); return new InputStreamReader(getInputStream());
} }
public String readAsString() { public String readAsString() {
try (Reader reader = getReader()) { try (Reader reader = getReader()) {
return CharStreams.toString(reader); return CharStreams.toString(reader);
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); // TODO handle gracefully CrashReports.report(e, "Could not read resource %s as text", this);
return null;
} }
} }
public ByteBuffer readAsBytes(ByteBuffer output) { public ByteBuffer readAsBytes(ByteBuffer output) {
byte[] byteArray; byte[] byteArray;
try (InputStream stream = getInputStream()) { try (InputStream stream = getInputStream()) {
byteArray = ByteStreams.toByteArray(stream); byteArray = ByteStreams.toByteArray(stream);
} catch (IOException e) { } 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) { if (output == null || output.remaining() < byteArray.length) {
output = BufferUtils.createByteBuffer(byteArray.length); output = BufferUtils.createByteBuffer(byteArray.length);
} }
int position = output.position(); int position = output.position();
output.put(byteArray); output.put(byteArray);
output.limit(output.position()); output.limit(output.position());
output.position(position); output.position(position);
return output; return output;
} }
public ByteBuffer readAsBytes() { public ByteBuffer readAsBytes() {
return readAsBytes(null); return readAsBytes(null);
} }

View File

@ -43,10 +43,8 @@ extends AbstractStatefulObjectLayout {
} }
@Override @Override
public StateFieldBuilder getBuilder(String namespace, String name) { public StateFieldBuilder getBuilder(String id) {
return new InspectingStateFieldBuilder( return new InspectingStateFieldBuilder(id);
namespace, name
);
} }
private class InspectingStateFieldBuilder implements StateFieldBuilder { private class InspectingStateFieldBuilder implements StateFieldBuilder {
@ -56,7 +54,7 @@ extends AbstractStatefulObjectLayout {
@Override @Override
public IntStateField build() { public IntStateField build() {
return registerField(new IntStateField( return registerField(new IntStateField(
namespace, name, id,
isLocal, isLocal,
fieldIndexCounters.getIntsThenIncrement() fieldIndexCounters.getIntsThenIncrement()
)); ));
@ -64,16 +62,12 @@ extends AbstractStatefulObjectLayout {
} }
private final String namespace; private final String id;
private final String name;
private boolean isLocal = true; private boolean isLocal = true;
public InspectingStateFieldBuilder( public InspectingStateFieldBuilder(String id) {
String namespace, String name this.id = id;
) {
this.namespace = namespace;
this.name = name;
} }
@Override @Override

View File

@ -7,11 +7,11 @@ import java.io.IOException;
public class IntStateField extends StateField { public class IntStateField extends StateField {
public IntStateField( public IntStateField(
String namespace, String name, String id,
boolean isLocal, boolean isLocal,
int index int index
) { ) {
super(namespace, name, isLocal, index); super(id, isLocal, index);
} }
public int get(StatefulObject object) { public int get(StatefulObject object) {

View File

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

View File

@ -4,7 +4,7 @@ import java.io.DataInput;
import java.io.DataOutput; import java.io.DataOutput;
import java.io.IOException; 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 { public abstract class StateField extends Namespaced {
@ -12,11 +12,11 @@ public abstract class StateField extends Namespaced {
private final int index; private final int index;
public StateField( public StateField(
String namespace, String name, String id,
boolean isLocal, boolean isLocal,
int index int index
) { ) {
super(namespace, name); super(id);
this.isLocal = isLocal; this.isLocal = isLocal;
this.index = index; this.index = index;
} }

View File

@ -5,7 +5,7 @@ import java.io.DataOutput;
import java.io.IOException; import java.io.IOException;
import java.util.Objects; 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, * An abstract class describing objects that have trackable state,
@ -44,10 +44,9 @@ public abstract class StatefulObject extends Namespaced {
public StatefulObject( public StatefulObject(
StatefulObjectRegistry<?> type, StatefulObjectRegistry<?> type,
String namespace, String id
String name
) { ) {
super(namespace, name); super(id);
this.layout = type.getLayout(getId()); this.layout = type.getLayout(getId());
this.storage = getLayout().createStorage(); this.storage = getLayout().createStorage();
} }
@ -96,8 +95,8 @@ public abstract class StatefulObject extends Namespaced {
* *
* @return a configured builder * @return a configured builder
*/ */
protected StateFieldBuilder field(String namespace, String name) { protected StateFieldBuilder field(String id) {
StateFieldBuilder builder = getLayout().getBuilder(namespace, name); StateFieldBuilder builder = getLayout().getBuilder(id);
builder.setOrdinal(fieldOrdinal); builder.setOrdinal(fieldOrdinal);
fieldOrdinal++; fieldOrdinal++;

View File

@ -43,6 +43,6 @@ public abstract class StatefulObjectLayout {
public abstract int computeHashCode(StatefulObject object); public abstract int computeHashCode(StatefulObject object);
public abstract boolean areEqual(StatefulObject a, StatefulObject b); 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.WeakHashMap;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import ru.windcorp.progressia.common.util.Namespaced; import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import ru.windcorp.progressia.common.util.NamespacedRegistry; import ru.windcorp.progressia.common.util.namespaces.NamespacedInstanceRegistry;
/** /**
* Registry-like object for identification of various {@link StatefulObject} * 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); private final AtomicBoolean isRegistered = new AtomicBoolean(false);
public Type(String namespace, String name, Factory<T> factory) { public Type(String id, Factory<T> factory) {
super(namespace, name); super(id);
this.factory = factory; this.factory = factory;
} }
@ -45,8 +45,8 @@ public class StatefulObjectRegistry<T extends StatefulObject> {
} }
private final NamespacedRegistry<Type<T>> registry = private final NamespacedInstanceRegistry<Type<T>> registry =
new NamespacedRegistry<Type<T>>() { new NamespacedInstanceRegistry<Type<T>>() {
@Override @Override
public void register(Type<T> element) { public void register(Type<T> element) {
super.register(element); super.register(element);
@ -91,8 +91,8 @@ public class StatefulObjectRegistry<T extends StatefulObject> {
return registry.get(id).build(); return registry.get(id).build();
} }
public void register(String namespace, String name, Factory<T> factory) { public void register(String id, Factory<T> factory) {
registry.register(new Type<>(namespace, name, 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 java.util.function.Consumer;
import glm.mat._4.Mat4; 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.Vec3;
import glm.vec._3.d.Vec3d;
import glm.vec._3.i.Vec3i; import glm.vec._3.i.Vec3i;
import glm.vec._4.Vec4; import glm.vec._4.Vec4;
import glm.vec._4.d.Vec4d;
import glm.vec._4.i.Vec4i;
public class VectorUtil { public class VectorUtil {
public static enum Axis {
X, Y, Z, W;
}
public static void forEachVectorInCuboid( public static void forEachVectorInCuboid(
int x0, int y0, int z0, int x0, int y0, int z0,
int x1, int y1, int z1, int x1, int y1, int z1,
@ -47,6 +57,204 @@ public class VectorUtil {
inOut.set(vec4.x, vec4.y, vec4.z); 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() {} private VectorUtil() {}
} }

View File

@ -30,6 +30,19 @@ import glm.vec._4.i.Vec4i;
*/ */
public class Vectors { 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 = private static final LowOverheadCache<Vec3i> VEC3IS =
new LowOverheadCache<>(Vec3i::new); 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 * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * 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; public abstract class Namespaced {
import java.util.function.Predicate;
import java.util.regex.Pattern;
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; private final String id;
public Namespaced(String namespace, String name) { public Namespaced(String id) {
super(name); NamespacedUtil.checkId(id);
this.namespace = Objects.requireNonNull(namespace, "namespace"); this.id = id;
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 String getId() { public final String getId() {
return id; return id;
} }
public String getNamespace() { public String getNamespace() {
return namespace; return NamespacedUtil.getNamespace(getId());
}
public String getName() {
return NamespacedUtil.getName(getId());
} }
@Override @Override
@ -75,15 +52,10 @@ public abstract class Namespaced extends Named {
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (this == obj) if (this == obj)
return true; return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass()) if (getClass() != obj.getClass())
return false; return false;
Namespaced other = (Namespaced) obj; Namespaced other = (Namespaced) obj;
if (id == null) { if (!id.equals(other.id))
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false; return false;
return true; 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.Collection;
import java.util.Collections; import java.util.Collections;
@ -7,17 +7,20 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.google.errorprone.annotations.DoNotCall; import com.google.errorprone.annotations.DoNotCall;
public class NamespacedRegistry<E extends Namespaced> public class NamespacedInstanceRegistry<E extends Namespaced>
implements Map<String, E> { implements Map<String, E> {
private final Map<String, E> backingMap = private final Map<String, E> backingMap =
Collections.synchronizedMap(new HashMap<>()); Collections.synchronizedMap(new HashMap<>());
private final Logger logger = LogManager.getLogger(getClass());
public void register(E element) { 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); backingMap.put(element.getId(), element);
} }
@ -67,7 +70,7 @@ implements Map<String, E> {
@DoNotCall @Deprecated @DoNotCall @Deprecated
public E put(String key, E value) { public E put(String key, E value) {
throw new UnsupportedOperationException( throw new UnsupportedOperationException(
"Use NamespacedRegistry.register(E)" "Use NamespacedInstanceRegistry.register(E)"
); );
} }
@ -83,7 +86,7 @@ implements Map<String, E> {
@DoNotCall @Deprecated @DoNotCall @Deprecated
public void putAll(Map<? extends String, ? extends E> m) { public void putAll(Map<? extends String, ? extends E> m) {
throw new UnsupportedOperationException( 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 static ru.windcorp.progressia.common.world.block.BlockFace.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
@ -63,8 +64,11 @@ public class ChunkData {
private final List<EntityData> entities = private final List<EntityData> entities =
Collections.synchronizedList(new ArrayList<>()); Collections.synchronizedList(new ArrayList<>());
public ChunkData(int x, int y, int z, WorldData world) { private final Collection<ChunkDataListener> listeners =
this.position.set(x, y, z); Collections.synchronizedCollection(new ArrayList<>());
public ChunkData(Vec3i position, WorldData world) {
this.position.set(position.x, position.y, position.z);
this.world = world; this.world = world;
tmp_generate(); tmp_generate();
@ -80,7 +84,7 @@ public class ChunkData {
TileData flowers = TileDataRegistry.getInstance().get("Test:YellowFlowers"); TileData flowers = TileDataRegistry.getInstance().get("Test:YellowFlowers");
TileData sand = TileDataRegistry.getInstance().get("Test:Sand"); 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(); Vec3i pos = new Vec3i();
for (int x = 0; x < BLOCKS_PER_CHUNK; ++x) { for (int x = 0; x < BLOCKS_PER_CHUNK; ++x) {
@ -92,11 +96,11 @@ public class ChunkData {
pos.set(x, y, z); pos.set(x, y, z);
if (f > 17) { if (f > 17) {
setBlock(pos, stone); setBlock(pos, stone, false);
} else if (f > 14) { } else if (f > 14) {
setBlock(pos, dirt); setBlock(pos, dirt, false);
} else { } else {
setBlock(pos, air); setBlock(pos, air, false);
} }
} }
@ -132,26 +136,36 @@ public class ChunkData {
} }
} }
EntityData javapony = EntityDataRegistry.getInstance().create("Test:Javapony"); if (!getPosition().any()) {
javapony.setEntityId(0x42); EntityData player = EntityDataRegistry.getInstance().create("Test:Player");
javapony.setPosition(new Vec3(-6, -6, 20)); player.setEntityId(0x42);
javapony.setDirection(new Vec2( player.setPosition(new Vec3(-6, -6, 20));
(float) Math.toRadians(40), (float) Math.toRadians(45) player.setDirection(new Vec2(
)); (float) Math.toRadians(40), (float) Math.toRadians(45)
getEntities().add(javapony); ));
getEntities().add(player);
EntityData statie = EntityDataRegistry.getInstance().create("Test:Statie");
statie.setEntityId(0xDEADBEEF); EntityData statie = EntityDataRegistry.getInstance().create("Test:Statie");
statie.setPosition(new Vec3(0, 15, 16)); statie.setEntityId(0xDEADBEEF);
getEntities().add(statie); statie.setPosition(new Vec3(0, 15, 16));
getEntities().add(statie);
}
} }
public BlockData getBlock(Vec3i posInChunk) { public BlockData getBlock(Vec3i posInChunk) {
return blocks[getBlockIndex(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; 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) { public List<TileData> getTilesOrNull(Vec3i blockInChunk, BlockFace face) {
@ -243,7 +257,7 @@ public class ChunkData {
private static void checkLocalCoordinates(Vec3i posInChunk) { private static void checkLocalCoordinates(Vec3i posInChunk) {
if (!isInBounds(posInChunk)) { if (!isInBounds(posInChunk)) {
throw new IllegalArgumentException( throw new IllegalCoordinatesException(
"Coordinates " + str(posInChunk) + " " "Coordinates " + str(posInChunk) + " "
+ "are not legal chunk coordinates" + "are not legal chunk coordinates"
); );
@ -329,8 +343,28 @@ public class ChunkData {
return world; 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) { private static String str(Vec3i v) {
return "(" + v.x + "; " + v.y + "; " + v.z + ")"; 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: * Three types of coordinates are used in Progressia:
* <ul> * <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 * coordinates relative to world origin. Every block in the world has unique
* world coordinates.</li> * 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 * coordinates relative some chunk's origin. Every block in the chunk has unique
* chunk coordinates, but blocks in different chunks may have identical chunk * chunk coordinates, but blocks in different chunks may have identical chunk
* coordinates. These coordinates are only useful in combination with a chunk * coordinates. These coordinates are only useful in combination with a chunk
* reference. Chunk coordinates are always <tt>[0; {@link #BLOCKS_PER_CHUNK}) * reference. Chunk coordinates are always <tt>[0; {@link #BLOCKS_PER_CHUNK})</tt>.</li>
* </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 * chunk coordinates relative to world origin. Every chunk in the world has
* unique coordinates of chunk.</li> * unique coordinates of chunk.</li>
* *

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