Compare commits
144 Commits
moduleSyst
...
add-items
Author | SHA1 | Date | |
---|---|---|---|
38021852d0
|
|||
9c85164ed1
|
|||
ce573b51ce
|
|||
92f9639c4b
|
|||
f350467942
|
|||
a028f6f3c7
|
|||
23b0af1731 | |||
0389f97e59 | |||
706800218c
|
|||
ca2c7b58d8
|
|||
359879b0fe
|
|||
a06d8ee056
|
|||
c8138faabe
|
|||
49d283e7a3
|
|||
c93f0df30d
|
|||
75ea7baf9c
|
|||
c4a9a17c7c | |||
b4ff5114bd
|
|||
9b67897896
|
|||
efc6a8ecd0
|
|||
3641c4130b
|
|||
4b10dc82ed
|
|||
be6203719a
|
|||
c04894a0c9 | |||
9885a1ca42
|
|||
c6e6dc6851
|
|||
28b19c8f35
|
|||
eb5aa59941
|
|||
409bbdb680
|
|||
0c41350ae7 | |||
a633c8324e
|
|||
6b33f231b4 | |||
e2308b825d
|
|||
30464febf6 | |||
e0f6a08740 | |||
2820e01974 | |||
782b3ef553
|
|||
46bcb85044 | |||
05b1c73fbc
|
|||
c5dfe3d0b7 | |||
bd6442318d
|
|||
6cd812f7c3 | |||
73ee339dcc
|
|||
4749be6c60
|
|||
a85fc27f8b
|
|||
a4b731e8a5
|
|||
e7d0e8fe40
|
|||
711e4a2bb4
|
|||
0100c8791d
|
|||
040a4f7fd7
|
|||
e967a64401 | |||
d2ffe1fe0e
|
|||
f4300558d5
|
|||
cd16334db8
|
|||
41a2909f7c
|
|||
a222ea8f67
|
|||
b3ac7b6afe
|
|||
2fe84dc59e
|
|||
00ea4a6281
|
|||
98250cd524 | |||
f74c731a3d
|
|||
f186fc602d
|
|||
98c383bf7d
|
|||
dd80df2cf2
|
|||
9dcb3a7748 | |||
1727a2a4a1
|
|||
20fb8f0597
|
|||
1d28f32865
|
|||
0ccc108ddd
|
|||
c7e7d3bdac
|
|||
62729f5873
|
|||
84864f8947
|
|||
d01ef3654f
|
|||
9fc1a21191
|
|||
4f620b7261
|
|||
6f90bf345b
|
|||
2328f2ae3a
|
|||
fae09edb16 | |||
e4ced6507e | |||
b7dcbb0f30 | |||
9c26418354 | |||
15b5d367b4
|
|||
ca2014802a
|
|||
539a61e854
|
|||
a3760d7425
|
|||
d33b48578d
|
|||
a6fd81ba1e
|
|||
82872c7cf3
|
|||
54c66d28d6
|
|||
78a1c25554
|
|||
a03c783fc9
|
|||
0a45613e45
|
|||
5fb4c601ff
|
|||
020802a89c
|
|||
6891d3a095 | |||
0f909039fe
|
|||
15f741bc04
|
|||
8167c40f64 | |||
8bc23acb61 | |||
80541eafc3
|
|||
254faca0a5 | |||
0f60d45ffa
|
|||
0c66f1751e | |||
c88dea6030 | |||
6521cb5749 | |||
fbc803d6e2
|
|||
94db44e443 | |||
53f72b068a | |||
a9ca5f6b17 | |||
4ab7cb738e | |||
1ee9a55d19 | |||
a338a00f1d | |||
9a326603cd | |||
d7afe39f00
|
|||
0264e512ab
|
|||
e47fb3c4bd
|
|||
eace6733ce
|
|||
085f602427
|
|||
737b495fc4
|
|||
6fb7e7fc04
|
|||
20dccf3d12
|
|||
a95bdf1efe
|
|||
e0a03cad1d
|
|||
2532e80f6a
|
|||
3c3f3816df
|
|||
2d3d250f92
|
|||
7ecdfdfb4d
|
|||
4332a78221
|
|||
ef572c43c7
|
|||
f28c765e3f
|
|||
f4311fb27c
|
|||
abd8d9eebb
|
|||
a9a21ce664
|
|||
bd5a1fa04e
|
|||
2d55d4db51
|
|||
d438d2aa14
|
|||
d3c5011063
|
|||
10d271059c
|
|||
acef9d32df
|
|||
848178b343
|
|||
b1666fa4b9
|
|||
f9717be412
|
|||
553837f207
|
|||
8c5493f78e
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -37,4 +37,3 @@ build_packages/NSIS/*
|
||||
!build_packages/DEB
|
||||
build_packages/DEB/*
|
||||
!build_packages/DEB/template
|
||||
*.log
|
||||
|
20
build.gradle
20
build.gradle
@ -40,7 +40,6 @@ compileJava {
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
|
||||
/*
|
||||
* Specify Windcorp Maven repository
|
||||
@ -66,12 +65,12 @@ dependencies {
|
||||
|
||||
// Log4j
|
||||
// A logging library
|
||||
implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.13.3'
|
||||
implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.13.3'
|
||||
implementation 'org.apache.logging.log4j:log4j-api:2.17.0'
|
||||
implementation 'org.apache.logging.log4j:log4j-core:2.17.0'
|
||||
|
||||
// JUnit
|
||||
// A unit-testing library
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
|
||||
// See LWJGL dependencies below
|
||||
}
|
||||
@ -111,8 +110,6 @@ switch (OperatingSystem.current()) {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation 'org.jetbrains:annotations:20.1.0'
|
||||
implementation platform("org.lwjgl:lwjgl-bom:$lwjglVersion")
|
||||
|
||||
implementation "org.lwjgl:lwjgl"
|
||||
@ -175,13 +172,20 @@ compileJava.mustRunAfter addNativeDependencies // Make sure runtimeOnly has not
|
||||
task requestLinuxDependencies {
|
||||
description 'Adds linux, linux-arm64 and linux-arm32 native libraries to built artifacts.'
|
||||
doFirst {
|
||||
project.ext.platforms.addAll(['natives-linux', 'natives-linux-arm64', 'natives-linux-arm32'])
|
||||
project.ext.platforms.addAll([
|
||||
'natives-linux',
|
||||
'natives-linux-arm64',
|
||||
'natives-linux-arm32'
|
||||
])
|
||||
}
|
||||
}
|
||||
task requestWindowsDependencies {
|
||||
description 'Adds windows and windows-x86 native libraries to built artifacts.'
|
||||
doFirst {
|
||||
project.ext.platforms.addAll(['natives-windows', 'natives-windows-x86'])
|
||||
project.ext.platforms.addAll([
|
||||
'natives-windows',
|
||||
'natives-windows-x86'
|
||||
])
|
||||
}
|
||||
}
|
||||
task requestMacOSDependencies {
|
||||
|
12
docs/ProgressiaRegion.md
Normal file
12
docs/ProgressiaRegion.md
Normal file
@ -0,0 +1,12 @@
|
||||
# Progressia Region File
|
||||
## Description
|
||||
The `.progressia_region` file type is used for all region files in the game Progressia. Each region file contains a cube of 16x16x16 chunks.
|
||||
## Header
|
||||
The header of the file is 16 400 bytes. Every file starts with the string byte sequence `\x50\x52\x4F\x47` (UTF-8 for `PROG`), followed with the three integer values of the region position, in region coordinates. After this, there is exactly 16KiB of space in the header, which stores the offsets to the chunks' data. This space holds an integer, 4 bytes, for each chunk in the region. The integer value starts at 0 for every chunk, and is changed to the location of the chunk data once created. These are indexed in order by flattening the 3D in-chunk coordinates into a number between 0 and 4095 according to the formula `offset = 256*x+ 16*y + z` for chunk at (x, y, z). To convert from this offset value to the offset in bytes, use `byte_offset = 16400 + 64*n`.
|
||||
## Sectors
|
||||
Sectors are what is used to store chunk data, and are not linear, but are followed until they reach an ending block. Each is 64 bytes, which is used in the header section to find the byte offset. Each sector starts with a identification byte, followed by the sector data.
|
||||
|
||||
0. Ending - This sector is empty, and marks the end of the chunk data (This may change in the future.
|
||||
1. Data - This sector contains chunk data for a single chunk. The second byte of this sector contains a counter byte, which is a form of "checksum" to make sure that the program is reading the proper sectors in order. This starts at 0 for the first data sector and increments by one for each new data sector.
|
||||
2. Partition Link - This sector only contains another offset value, which is where the next sector is. This allows for infinite chunk size, avoiding "chunk dupes" as were present in Minecraft without reverting any chunks.
|
||||
3. Bulk Data - These would be used for many chunks in the same region that contain exactly the same data, e.g. all solid chunks underground. Exists so the program knows not to overwrite them, and just make new chunk data if modified. Not yet implemented.
|
@ -42,3 +42,16 @@ Run configurations are used by Intellij IDEA to specify how a project must be ru
|
||||
9. Click 'Apply' to save changes.
|
||||
|
||||
Step 8 is required to specify that the game must run in some directory other than the project root, which is the default in Intellij IDEA.
|
||||
|
||||
### Applying formatting templates
|
||||
|
||||
Windcorp's Progressia repository is formatted with a style defined for Eclipse IDE (sic) in
|
||||
`templates_and_presets/eclipse_ide`.
|
||||
Please apply these templates to the project to automatically format the source in a similar fashion.
|
||||
|
||||
1. In project context menu, click 'File->Properties'. (`Ctrl+Alt+S`)
|
||||
2. In 'Editor' > 'Code Style' > 'Java', press gear icon, then click 'Import Scheme' > 'Eclipse code style'
|
||||
3. In Scheme select 'Project'
|
||||
4. Open the file `templates_and_presets/eclipse_ide/FormatterProfile.xml` in 'Select Path'.
|
||||
5. Inside 'Import Scheme' widow click 'Current Scheme' check box after press OK
|
||||
|
||||
|
@ -18,18 +18,28 @@
|
||||
|
||||
package ru.windcorp.progressia;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.GUI;
|
||||
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
||||
import ru.windcorp.progressia.common.util.crash.analyzers.OutOfMemoryAnalyzer;
|
||||
import ru.windcorp.progressia.common.util.crash.providers.*;
|
||||
import ru.windcorp.progressia.test.LayerTitle;
|
||||
|
||||
public class ProgressiaLauncher {
|
||||
|
||||
public static String[] arguments;
|
||||
private static Proxy proxy;
|
||||
|
||||
public static void launch(String[] args, Proxy proxy) {
|
||||
arguments = args.clone();
|
||||
setupCrashReports();
|
||||
|
||||
proxy.initialize();
|
||||
ProgressiaLauncher.proxy = proxy;
|
||||
GUI.addTopLayer(new LayerTitle("Title"));
|
||||
}
|
||||
|
||||
public static Proxy getProxy() {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
private static void setupCrashReports() {
|
||||
|
@ -18,29 +18,66 @@
|
||||
|
||||
package ru.windcorp.progressia.client;
|
||||
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
|
||||
import ru.windcorp.progressia.client.comms.DefaultClientCommsListener;
|
||||
import ru.windcorp.progressia.client.comms.ServerCommsChannel;
|
||||
import ru.windcorp.progressia.client.events.ClientEvent;
|
||||
import ru.windcorp.progressia.client.events.NewLocalEntityEvent;
|
||||
import ru.windcorp.progressia.client.graphics.GUI;
|
||||
import ru.windcorp.progressia.client.graphics.world.Camera;
|
||||
import ru.windcorp.progressia.client.graphics.world.EntityAnchor;
|
||||
import ru.windcorp.progressia.client.graphics.world.LayerWorld;
|
||||
import ru.windcorp.progressia.client.graphics.world.LocalPlayer;
|
||||
import ru.windcorp.progressia.client.graphics.world.hud.HUDManager;
|
||||
import ru.windcorp.progressia.client.world.WorldRender;
|
||||
import ru.windcorp.progressia.common.world.WorldData;
|
||||
import ru.windcorp.progressia.common.world.entity.EntityData;
|
||||
import ru.windcorp.progressia.common.util.crash.ReportingEventBus;
|
||||
import ru.windcorp.progressia.common.world.DefaultWorldData;
|
||||
import ru.windcorp.progressia.test.LayerAbout;
|
||||
import ru.windcorp.progressia.test.LayerDebug;
|
||||
import ru.windcorp.progressia.test.LayerTestUI;
|
||||
|
||||
public class Client {
|
||||
|
||||
private final WorldRender world;
|
||||
|
||||
private final LayerWorld layerWorld = new LayerWorld(this);
|
||||
private final LayerTestUI layerTestUI = new LayerTestUI();
|
||||
private final LayerAbout layerAbout = new LayerAbout();
|
||||
private final LayerDebug layerDebug = new LayerDebug();
|
||||
|
||||
private final LocalPlayer localPlayer = new LocalPlayer(this);
|
||||
|
||||
private final Camera camera = new Camera((float) Math.toRadians(70));
|
||||
|
||||
private final EventBus eventBus = ReportingEventBus.create("ClientEvents");
|
||||
|
||||
private final HUDManager hudManager = new HUDManager(this);
|
||||
|
||||
private final ServerCommsChannel comms;
|
||||
|
||||
public Client(WorldData world, ServerCommsChannel comms) {
|
||||
public Client(DefaultWorldData world, ServerCommsChannel comms) {
|
||||
this.world = new WorldRender(world, this);
|
||||
this.comms = comms;
|
||||
|
||||
comms.addListener(new DefaultClientCommsListener(this));
|
||||
subscribe(this);
|
||||
}
|
||||
|
||||
public void install() {
|
||||
GUI.addBottomLayer(layerWorld);
|
||||
GUI.addTopLayer(layerTestUI);
|
||||
hudManager.install();
|
||||
GUI.addTopLayer(layerAbout);
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
GUI.removeLayer(layerWorld);
|
||||
GUI.removeLayer(layerTestUI);
|
||||
hudManager.remove();
|
||||
GUI.removeLayer(layerAbout);
|
||||
GUI.removeLayer(layerDebug);
|
||||
}
|
||||
|
||||
public WorldRender getWorld() {
|
||||
@ -63,17 +100,44 @@ public class Client {
|
||||
return comms;
|
||||
}
|
||||
|
||||
public void onLocalPlayerEntityChanged(EntityData entity, EntityData lastKnownEntity) {
|
||||
if (entity == null) {
|
||||
public HUDManager getHUD() {
|
||||
return hudManager;
|
||||
}
|
||||
|
||||
public void toggleDebugLayer() {
|
||||
if (GUI.getLayers().contains(layerDebug)) {
|
||||
GUI.removeLayer(layerDebug);
|
||||
} else {
|
||||
GUI.addTopLayer(layerDebug);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
private void onLocalPlayerEntityChanged(NewLocalEntityEvent e) {
|
||||
if (e.getNewEntity() == null) {
|
||||
getCamera().setAnchor(null);
|
||||
return;
|
||||
}
|
||||
|
||||
getCamera().setAnchor(
|
||||
new EntityAnchor(
|
||||
getWorld().getEntityRenderable(entity)
|
||||
getWorld().getEntityRenderable(e.getNewEntity())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public void subscribe(Object object) {
|
||||
eventBus.register(object);
|
||||
}
|
||||
|
||||
public void unsubscribe(Object object) {
|
||||
eventBus.unregister(object);
|
||||
}
|
||||
|
||||
public void postEvent(ClientEvent event) {
|
||||
event.setClient(this);
|
||||
eventBus.post(event);
|
||||
event.setClient(null);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -27,11 +27,10 @@ import ru.windcorp.progressia.client.graphics.font.GNUUnifontLoader;
|
||||
import ru.windcorp.progressia.client.graphics.font.Typefaces;
|
||||
import ru.windcorp.progressia.client.graphics.texture.Atlases;
|
||||
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
|
||||
import ru.windcorp.progressia.client.graphics.world.hud.HUDTextures;
|
||||
import ru.windcorp.progressia.client.localization.Localizer;
|
||||
import ru.windcorp.progressia.common.modules.TaskManager;
|
||||
import ru.windcorp.progressia.common.resource.ResourceManager;
|
||||
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
||||
import ru.windcorp.progressia.server.ServerState;
|
||||
import ru.windcorp.progressia.test.TestContent;
|
||||
import ru.windcorp.progressia.test.TestMusicPlayer;
|
||||
|
||||
@ -39,7 +38,9 @@ public class ClientProxy implements Proxy {
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
|
||||
GraphicsBackend.initialize();
|
||||
|
||||
try {
|
||||
RenderTaskQueue.waitAndInvoke(FlatRenderProgram::init);
|
||||
RenderTaskQueue.waitAndInvoke(WorldRenderProgram::init);
|
||||
@ -47,6 +48,7 @@ public class ClientProxy implements Proxy {
|
||||
() -> Typefaces
|
||||
.setDefault(GNUUnifontLoader.load(ResourceManager.getResource("assets/unifont-13.0.03.hex.gz")))
|
||||
);
|
||||
RenderTaskQueue.waitAndInvoke(HUDTextures::loadItemAmountTypeface);
|
||||
} catch (InterruptedException e) {
|
||||
throw CrashReports.report(e, "ClientProxy failed");
|
||||
}
|
||||
@ -58,13 +60,7 @@ public class ClientProxy implements Proxy {
|
||||
Atlases.loadAllAtlases();
|
||||
|
||||
AudioSystem.initialize();
|
||||
TaskManager.getInstance().startLoading();
|
||||
|
||||
|
||||
ServerState.startServer();
|
||||
ClientState.connectToLocalServer();
|
||||
|
||||
TestMusicPlayer.start();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -20,11 +20,10 @@ package ru.windcorp.progressia.client;
|
||||
|
||||
import ru.windcorp.progressia.client.comms.localhost.LocalServerCommsChannel;
|
||||
import ru.windcorp.progressia.client.graphics.GUI;
|
||||
import ru.windcorp.progressia.client.graphics.world.LayerWorld;
|
||||
import ru.windcorp.progressia.common.world.WorldData;
|
||||
import ru.windcorp.progressia.common.world.DefaultWorldData;
|
||||
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
|
||||
import ru.windcorp.progressia.server.ServerState;
|
||||
import ru.windcorp.progressia.test.LayerAbout;
|
||||
import ru.windcorp.progressia.test.LayerTestUI;
|
||||
import ru.windcorp.progressia.test.LayerTestText;
|
||||
import ru.windcorp.progressia.test.TestContent;
|
||||
|
||||
public class ClientState {
|
||||
@ -41,7 +40,7 @@ public class ClientState {
|
||||
|
||||
public static void connectToLocalServer() {
|
||||
|
||||
WorldData world = new WorldData();
|
||||
DefaultWorldData world = new DefaultWorldData();
|
||||
|
||||
LocalServerCommsChannel channel = new LocalServerCommsChannel(
|
||||
ServerState.getInstance()
|
||||
@ -52,11 +51,29 @@ public class ClientState {
|
||||
channel.connect(TestContent.PLAYER_LOGIN);
|
||||
|
||||
setInstance(client);
|
||||
displayLoadingScreen();
|
||||
|
||||
GUI.addBottomLayer(new LayerWorld(client));
|
||||
GUI.addTopLayer(new LayerTestUI());
|
||||
GUI.addTopLayer(new LayerAbout());
|
||||
}
|
||||
|
||||
private static void displayLoadingScreen() {
|
||||
GUI.addTopLayer(new LayerTestText("Text", new MutableStringLocalized("LayerText.Load"), layer -> {
|
||||
Client client = ClientState.getInstance();
|
||||
|
||||
// TODO refacetor and remove
|
||||
if (client != null) {
|
||||
client.getComms().processPackets();
|
||||
}
|
||||
|
||||
if (client != null && client.getLocalPlayer().hasEntity()) {
|
||||
GUI.removeLayer(layer);
|
||||
client.install();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public static void disconnectFromLocalServer() {
|
||||
getInstance().getComms().disconnect();
|
||||
getInstance().remove();
|
||||
}
|
||||
|
||||
private ClientState() {
|
||||
|
@ -18,28 +18,14 @@
|
||||
|
||||
package ru.windcorp.progressia.client.audio;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import ru.windcorp.progressia.common.modules.Module;
|
||||
import ru.windcorp.progressia.common.modules.Task;
|
||||
import ru.windcorp.progressia.common.modules.TaskManager;
|
||||
import ru.windcorp.progressia.common.resource.ResourceManager;
|
||||
|
||||
public class AudioSystem {
|
||||
static public void initialize() {
|
||||
Module audioModule = new Module("AudioModule:System");
|
||||
AudioManager.initAL();
|
||||
Thread shutdownHook = new Thread(AudioManager::closeAL, "AL Shutdown Hook");
|
||||
Runtime.getRuntime().addShutdownHook(shutdownHook);
|
||||
|
||||
Task t = new Task("AudioSystem:Initialize") {
|
||||
@Override
|
||||
protected void perform() {
|
||||
loadAudioData();
|
||||
LogManager.getLogger().info("Audio data is loaded");
|
||||
}
|
||||
};
|
||||
audioModule.addTask(t);
|
||||
TaskManager.getInstance().registerModule(audioModule);
|
||||
}
|
||||
|
||||
static void loadAudioData() {
|
||||
|
@ -143,20 +143,6 @@ public class ControlTriggers {
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
///
|
||||
///
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
public static ControlTriggerInputBased localOf(
|
||||
String id,
|
||||
Consumer<InputEvent> action,
|
||||
|
@ -19,7 +19,7 @@
|
||||
package ru.windcorp.progressia.client.comms.controls;
|
||||
|
||||
import ru.windcorp.progressia.client.Client;
|
||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||
import ru.windcorp.progressia.common.comms.packets.Packet;
|
||||
|
||||
public class InputBasedControls {
|
||||
@ -36,12 +36,12 @@ public class InputBasedControls {
|
||||
.toArray(ControlTriggerInputBased[]::new);
|
||||
}
|
||||
|
||||
public void handleInput(Input input) {
|
||||
public void handleInput(InputEvent event) {
|
||||
for (ControlTriggerInputBased c : controls) {
|
||||
Packet packet = c.onInputEvent(input.getEvent());
|
||||
Packet packet = c.onInputEvent(event);
|
||||
|
||||
if (packet != null) {
|
||||
input.consume();
|
||||
event.consume();
|
||||
client.getComms().sendPacket(packet);
|
||||
break;
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ package ru.windcorp.progressia.client.comms.localhost;
|
||||
import ru.windcorp.progressia.client.comms.ServerCommsChannel;
|
||||
import ru.windcorp.progressia.common.comms.packets.Packet;
|
||||
import ru.windcorp.progressia.server.Server;
|
||||
import ru.windcorp.progressia.server.ServerState;
|
||||
|
||||
public class LocalServerCommsChannel extends ServerCommsChannel {
|
||||
|
||||
@ -54,7 +55,7 @@ public class LocalServerCommsChannel extends ServerCommsChannel {
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
// Do nothing
|
||||
ServerState.getInstance().getClientManager().disconnectClient(localClient);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.events;
|
||||
|
||||
import ru.windcorp.progressia.client.Client;
|
||||
|
||||
/**
|
||||
* An interface for all events issued by a {@link Client}.
|
||||
*/
|
||||
public interface ClientEvent {
|
||||
|
||||
/**
|
||||
* Returns the client instance that this event happened on.
|
||||
*
|
||||
* @return the client
|
||||
*/
|
||||
Client getClient();
|
||||
|
||||
/**
|
||||
* Sets the client instance that the event is posted on. The value provided
|
||||
* to this method must be returned by subsequent calls to
|
||||
* {@link #getClient()}. Do not call this method when handling the event.
|
||||
*
|
||||
* @param client the client dispatching the event or {@code null} to unbind
|
||||
* any previously bound client
|
||||
*/
|
||||
void setClient(Client client);
|
||||
|
||||
/**
|
||||
* A default implementation of {@link ClientEvent}. This is not necessarily
|
||||
* extended by client events.
|
||||
*/
|
||||
public static abstract class Default implements ClientEvent {
|
||||
|
||||
private Client client;
|
||||
|
||||
public Default(Client client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Client getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClient(Client client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.events;
|
||||
|
||||
import ru.windcorp.progressia.client.Client;
|
||||
import ru.windcorp.progressia.common.world.entity.EntityDataPlayer;
|
||||
|
||||
public interface NewLocalEntityEvent extends ClientEvent {
|
||||
|
||||
EntityDataPlayer getNewEntity();
|
||||
EntityDataPlayer getPreviousEntity();
|
||||
|
||||
public class Immutable extends ClientEvent.Default implements NewLocalEntityEvent {
|
||||
|
||||
private final EntityDataPlayer newEntity;
|
||||
private final EntityDataPlayer previousEntity;
|
||||
|
||||
public Immutable(Client client, EntityDataPlayer newEntity, EntityDataPlayer previousEntity) {
|
||||
super(client);
|
||||
this.newEntity = newEntity;
|
||||
this.previousEntity = previousEntity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityDataPlayer getNewEntity() {
|
||||
return newEntity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityDataPlayer getPreviousEntity() {
|
||||
return previousEntity;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -34,7 +34,13 @@ public class Colors {
|
||||
DEBUG_BLUE = toVector(0xFF0000FF),
|
||||
DEBUG_CYAN = toVector(0xFF00FFFF),
|
||||
DEBUG_MAGENTA = toVector(0xFFFF00FF),
|
||||
DEBUG_YELLOW = toVector(0xFFFFFF00);
|
||||
DEBUG_YELLOW = toVector(0xFFFFFF00),
|
||||
|
||||
LIGHT_GRAY = toVector(0xFFCBCBD0),
|
||||
BLUE = toVector(0xFF37A2E6),
|
||||
HOVER_BLUE = toVector(0xFFC3E4F7),
|
||||
DISABLED_GRAY = toVector(0xFFE5E5E5),
|
||||
DISABLED_BLUE = toVector(0xFFB2D8ED);
|
||||
|
||||
public static Vec4 toVector(int argb) {
|
||||
return toVector(argb, new Vec4());
|
||||
@ -50,6 +56,25 @@ public class Colors {
|
||||
return color.mul(multiplier, multiplier, multiplier, 1, output);
|
||||
}
|
||||
|
||||
public static Vec4 mix(Vec4 zero, Vec4 one, float t, Vec4 output) {
|
||||
if (output == null) {
|
||||
output = new Vec4();
|
||||
}
|
||||
|
||||
if (t <= 0) {
|
||||
return output.set(zero);
|
||||
} else if (t >= 1) {
|
||||
return output.set(one);
|
||||
}
|
||||
|
||||
return output.set(
|
||||
zero.x * (1 - t) + one.x * t,
|
||||
zero.y * (1 - t) + one.y * t,
|
||||
zero.z * (1 - t) + one.z * t,
|
||||
zero.w * (1 - t) + one.w * t
|
||||
);
|
||||
}
|
||||
|
||||
public static Vec4 toVector(int argb, Vec4 output) {
|
||||
output.w = ((argb & 0xFF000000) >>> 24) / (float) 0xFF; // Alpha
|
||||
output.x = ((argb & 0x00FF0000) >>> 16) / (float) 0xFF; // Red
|
||||
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.graphics;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||
|
||||
public class ExponentAnimation {
|
||||
|
||||
private final float speed;
|
||||
private float value;
|
||||
|
||||
public ExponentAnimation(float speed, float value) {
|
||||
this.speed = speed;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public float getSpeed() {
|
||||
return speed;
|
||||
}
|
||||
|
||||
public float getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(float value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public float update(float target, double timeStep) {
|
||||
float difference = value - target;
|
||||
value += difference * (1 - Math.exp(speed * timeStep));
|
||||
|
||||
float newDifference = value - target;
|
||||
if (difference * newDifference < 0) {
|
||||
// Whoops, we've overshot
|
||||
value = target;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public float updateForFrame(float target) {
|
||||
return update(target, GraphicsInterface.getFrameLength());
|
||||
}
|
||||
|
||||
}
|
@ -21,15 +21,10 @@ package ru.windcorp.progressia.client.graphics;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.input.CursorEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.FrameResizeEvent;
|
||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.WheelEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
||||
|
||||
public class GUI {
|
||||
|
||||
@ -44,28 +39,37 @@ public class GUI {
|
||||
private static final List<LayerStackModification> MODIFICATION_QUEUE = Collections
|
||||
.synchronizedList(new ArrayList<>());
|
||||
|
||||
private static class ModifiableInput extends Input {
|
||||
@Override
|
||||
public void initialize(InputEvent event, Target target) {
|
||||
super.initialize(event, target);
|
||||
}
|
||||
}
|
||||
|
||||
private static final ModifiableInput THE_INPUT = new ModifiableInput();
|
||||
|
||||
private GUI() {
|
||||
}
|
||||
|
||||
public static void addBottomLayer(Layer layer) {
|
||||
modify(layers -> layers.add(layer));
|
||||
Objects.requireNonNull(layer, "layer");
|
||||
modify(layers -> {
|
||||
layers.add(layer);
|
||||
layer.onAdded();
|
||||
});
|
||||
}
|
||||
|
||||
public static void addTopLayer(Layer layer) {
|
||||
modify(layers -> layers.add(0, layer));
|
||||
Objects.requireNonNull(layer, "layer");
|
||||
modify(layers -> {
|
||||
layers.add(0, layer);
|
||||
layer.onAdded();
|
||||
});
|
||||
}
|
||||
|
||||
public static void removeLayer(Layer layer) {
|
||||
modify(layers -> layers.remove(layer));
|
||||
Objects.requireNonNull(layer, "layer");
|
||||
modify(layers -> {
|
||||
layers.remove(layer);
|
||||
layer.onRemoved();
|
||||
});
|
||||
}
|
||||
|
||||
public static void updateLayer(Layer layer) {
|
||||
modify(layers -> {
|
||||
// Do nothing
|
||||
});
|
||||
}
|
||||
|
||||
private static void modify(LayerStackModification mod) {
|
||||
@ -78,12 +82,33 @@ public class GUI {
|
||||
|
||||
public static void render() {
|
||||
synchronized (LAYERS) {
|
||||
|
||||
if (!MODIFICATION_QUEUE.isEmpty()) {
|
||||
MODIFICATION_QUEUE.forEach(action -> action.affect(LAYERS));
|
||||
MODIFICATION_QUEUE.clear();
|
||||
|
||||
boolean isMouseCurrentlyCaptured = GraphicsInterface.isMouseCaptured();
|
||||
Layer.CursorPolicy policy = Layer.CursorPolicy.REQUIRE;
|
||||
|
||||
for (Layer layer : LAYERS) {
|
||||
Layer.CursorPolicy currentPolicy = layer.getCursorPolicy();
|
||||
|
||||
if (currentPolicy != Layer.CursorPolicy.INDIFFERENT) {
|
||||
policy = currentPolicy;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
boolean shouldCaptureMouse = (policy == Layer.CursorPolicy.FORBID);
|
||||
if (shouldCaptureMouse != isMouseCurrentlyCaptured) {
|
||||
GraphicsInterface.setMouseCaptured(shouldCaptureMouse);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = LAYERS.size() - 1; i >= 0; --i) {
|
||||
LAYERS.get(i).render();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,43 +116,12 @@ public class GUI {
|
||||
LAYERS.forEach(Layer::invalidate);
|
||||
}
|
||||
|
||||
private static void dispatchInputEvent(InputEvent event) {
|
||||
Input.Target target;
|
||||
|
||||
if (event instanceof KeyEvent) {
|
||||
if (((KeyEvent) event).isMouse()) {
|
||||
target = Input.Target.HOVERED;
|
||||
} else {
|
||||
target = Input.Target.FOCUSED;
|
||||
public static void dispatchInput(InputEvent event) {
|
||||
synchronized (LAYERS) {
|
||||
for (int i = 0; i < LAYERS.size(); ++i) {
|
||||
LAYERS.get(i).handleInput(event);
|
||||
}
|
||||
} else if (event instanceof CursorEvent) {
|
||||
target = Input.Target.HOVERED;
|
||||
} else if (event instanceof WheelEvent) {
|
||||
target = Input.Target.HOVERED;
|
||||
} else if (event instanceof FrameResizeEvent) {
|
||||
return;
|
||||
} else {
|
||||
target = Input.Target.ALL;
|
||||
}
|
||||
|
||||
THE_INPUT.initialize(event, target);
|
||||
LAYERS.forEach(l -> l.handleInput(THE_INPUT));
|
||||
}
|
||||
|
||||
public static Object getEventSubscriber() {
|
||||
return new Object() {
|
||||
|
||||
@Subscribe
|
||||
public void onFrameResized(FrameResizeEvent event) {
|
||||
GUI.invalidateEverything();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onInput(InputEvent event) {
|
||||
dispatchInputEvent(event);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ package ru.windcorp.progressia.client.graphics;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||
|
||||
public abstract class Layer {
|
||||
|
||||
@ -31,15 +31,52 @@ public abstract class Layer {
|
||||
|
||||
private final AtomicBoolean isValid = new AtomicBoolean(false);
|
||||
|
||||
/**
|
||||
* Represents various requests that a {@link Layer} can make regarding the
|
||||
* presence of a visible cursor. The value of the highest layer that is not
|
||||
* {@link #INDIFFERENT} is used.
|
||||
*/
|
||||
public static enum CursorPolicy {
|
||||
/**
|
||||
* Require that a cursor is visible.
|
||||
*/
|
||||
REQUIRE,
|
||||
|
||||
/**
|
||||
* The {@link Layer} should not affect the presence or absence of a
|
||||
* visible cursor; lower layers should be consulted.
|
||||
*/
|
||||
INDIFFERENT,
|
||||
|
||||
/**
|
||||
* Forbid a visible cursor.
|
||||
*/
|
||||
FORBID
|
||||
}
|
||||
|
||||
private CursorPolicy cursorPolicy = CursorPolicy.INDIFFERENT;
|
||||
|
||||
public Layer(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Layer " + name;
|
||||
}
|
||||
|
||||
public CursorPolicy getCursorPolicy() {
|
||||
return cursorPolicy;
|
||||
}
|
||||
|
||||
public void setCursorPolicy(CursorPolicy cursorPolicy) {
|
||||
this.cursorPolicy = cursorPolicy;
|
||||
}
|
||||
|
||||
void render() {
|
||||
GraphicsInterface.startNextLayer();
|
||||
|
||||
@ -69,7 +106,7 @@ public abstract class Layer {
|
||||
|
||||
protected abstract void doRender();
|
||||
|
||||
protected abstract void handleInput(Input input);
|
||||
public abstract void handleInput(InputEvent input);
|
||||
|
||||
protected int getWidth() {
|
||||
return GraphicsInterface.getFrameWidth();
|
||||
@ -79,4 +116,12 @@ public abstract class Layer {
|
||||
return GraphicsInterface.getFrameHeight();
|
||||
}
|
||||
|
||||
protected void onAdded() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
protected void onRemoved() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,6 +19,7 @@
|
||||
package ru.windcorp.progressia.client.graphics.backend;
|
||||
|
||||
import glm.vec._2.i.Vec2i;
|
||||
|
||||
import org.lwjgl.glfw.GLFWVidMode;
|
||||
|
||||
import static org.lwjgl.glfw.GLFW.*;
|
||||
@ -43,6 +44,13 @@ public class GraphicsBackend {
|
||||
private static boolean isGLFWInitialized = false;
|
||||
private static boolean isOpenGLInitialized = false;
|
||||
|
||||
private static boolean allowDisablingCursor;
|
||||
static {
|
||||
String key = GraphicsBackend.class.getName() + ".allowDisablingCursor";
|
||||
allowDisablingCursor = Boolean.parseBoolean(System.getProperty(key, "true"));
|
||||
}
|
||||
private static boolean forceCursorToCenter = false;
|
||||
|
||||
private GraphicsBackend() {
|
||||
}
|
||||
|
||||
@ -114,6 +122,10 @@ public class GraphicsBackend {
|
||||
frameLength = now - frameStart;
|
||||
frameStart = now;
|
||||
}
|
||||
|
||||
if (forceCursorToCenter) {
|
||||
glfwSetCursorPos(windowHandle, FRAME_SIZE.x / 2.0, FRAME_SIZE.y / 2.0);
|
||||
}
|
||||
}
|
||||
|
||||
static void endFrame() {
|
||||
@ -192,4 +204,26 @@ public class GraphicsBackend {
|
||||
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
|
||||
return vidmode.refreshRate();
|
||||
}
|
||||
|
||||
public static boolean isMouseCaptured() {
|
||||
if (!allowDisablingCursor) {
|
||||
return forceCursorToCenter;
|
||||
}
|
||||
return glfwGetInputMode(windowHandle, GLFW_CURSOR) == GLFW_CURSOR_DISABLED;
|
||||
}
|
||||
|
||||
public static void setMouseCaptured(boolean capture) {
|
||||
if (!allowDisablingCursor) {
|
||||
forceCursorToCenter = capture;
|
||||
return;
|
||||
}
|
||||
|
||||
int mode = capture ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL;
|
||||
glfwSetInputMode(windowHandle, GLFW_CURSOR, mode);
|
||||
|
||||
if (!capture) {
|
||||
glfwSetCursorPos(windowHandle, FRAME_SIZE.x / 2.0, FRAME_SIZE.y / 2.0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -69,6 +69,10 @@ public class GraphicsInterface {
|
||||
InputHandler.register(listener);
|
||||
}
|
||||
|
||||
public static void unsubscribeFromInputEvents(Object listener) {
|
||||
InputHandler.unregister(listener);
|
||||
}
|
||||
|
||||
public static void startNextLayer() {
|
||||
GraphicsBackend.startNextLayer();
|
||||
}
|
||||
@ -82,4 +86,12 @@ public class GraphicsInterface {
|
||||
GraphicsBackend.setVSyncEnabled(GraphicsBackend.isVSyncEnabled());
|
||||
}
|
||||
|
||||
public static boolean isMouseCaptured() {
|
||||
return GraphicsBackend.isMouseCaptured();
|
||||
}
|
||||
|
||||
public static void setMouseCaptured(boolean capture) {
|
||||
GraphicsBackend.setMouseCaptured(capture);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ public class InputHandler {
|
||||
|
||||
public void initialize(int key, int scancode, int action, int mods) {
|
||||
this.setTime(GraphicsInterface.getTime());
|
||||
this.setConsumed(false);
|
||||
this.key = key;
|
||||
this.scancode = scancode;
|
||||
this.action = action;
|
||||
@ -59,7 +60,7 @@ public class InputHandler {
|
||||
if (GraphicsBackend.getWindowHandle() != window)
|
||||
return;
|
||||
THE_KEY_EVENT.initialize(key, scancode, action, mods);
|
||||
dispatch(THE_KEY_EVENT);
|
||||
INPUT_EVENT_BUS.post(THE_KEY_EVENT);
|
||||
|
||||
switch (action) {
|
||||
case GLFW.GLFW_PRESS:
|
||||
@ -90,6 +91,7 @@ public class InputHandler {
|
||||
|
||||
public void initialize(double x, double y) {
|
||||
this.setTime(GraphicsInterface.getTime());
|
||||
this.setConsumed(false);
|
||||
getNewPosition().set(x, y);
|
||||
}
|
||||
|
||||
@ -109,7 +111,7 @@ public class InputHandler {
|
||||
InputTracker.initializeCursorPosition(x, y);
|
||||
|
||||
THE_CURSOR_MOVE_EVENT.initialize(x, y);
|
||||
dispatch(THE_CURSOR_MOVE_EVENT);
|
||||
INPUT_EVENT_BUS.post(THE_CURSOR_MOVE_EVENT);
|
||||
|
||||
InputTracker.getCursorPosition().set(x, y);
|
||||
}
|
||||
@ -124,6 +126,7 @@ public class InputHandler {
|
||||
|
||||
public void initialize(double xOffset, double yOffset) {
|
||||
this.setTime(GraphicsInterface.getTime());
|
||||
this.setConsumed(false);
|
||||
this.getOffset().set(xOffset, yOffset);
|
||||
}
|
||||
|
||||
@ -139,7 +142,7 @@ public class InputHandler {
|
||||
if (GraphicsBackend.getWindowHandle() != window)
|
||||
return;
|
||||
THE_WHEEL_SCROLL_EVENT.initialize(xoffset, yoffset);
|
||||
dispatch(THE_WHEEL_SCROLL_EVENT);
|
||||
INPUT_EVENT_BUS.post(THE_WHEEL_SCROLL_EVENT);
|
||||
}
|
||||
|
||||
// FrameResizeEvent
|
||||
@ -152,6 +155,7 @@ public class InputHandler {
|
||||
|
||||
public void initialize(int width, int height) {
|
||||
this.setTime(GraphicsInterface.getTime());
|
||||
this.setConsumed(false);
|
||||
this.getNewSize().set(width, height);
|
||||
}
|
||||
|
||||
@ -167,17 +171,17 @@ public class InputHandler {
|
||||
int height
|
||||
) {
|
||||
THE_FRAME_RESIZE_EVENT.initialize(width, height);
|
||||
dispatch(THE_FRAME_RESIZE_EVENT);
|
||||
INPUT_EVENT_BUS.post(THE_FRAME_RESIZE_EVENT);
|
||||
}
|
||||
|
||||
// Misc
|
||||
|
||||
private static void dispatch(InputEvent event) {
|
||||
INPUT_EVENT_BUS.post(event);
|
||||
}
|
||||
|
||||
public static void register(Object listener) {
|
||||
INPUT_EVENT_BUS.register(listener);
|
||||
}
|
||||
|
||||
public static void unregister(Object listener) {
|
||||
INPUT_EVENT_BUS.unregister(listener);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,7 +24,11 @@ import static org.lwjgl.system.MemoryUtil.*;
|
||||
|
||||
import org.lwjgl.opengl.GL;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.GUI;
|
||||
import ru.windcorp.progressia.client.graphics.input.FrameResizeEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||
|
||||
class LWJGLInitializer {
|
||||
|
||||
@ -65,8 +69,6 @@ class LWJGLInitializer {
|
||||
|
||||
GraphicsBackend.setWindowHandle(handle);
|
||||
|
||||
glfwSetInputMode(handle, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
|
||||
|
||||
glfwMakeContextCurrent(handle);
|
||||
glfwSwapInterval(0); // TODO: remove after config system is added
|
||||
}
|
||||
@ -109,7 +111,20 @@ class LWJGLInitializer {
|
||||
|
||||
glfwSetScrollCallback(handle, InputHandler::handleWheelScroll);
|
||||
|
||||
GraphicsInterface.subscribeToInputEvents(GUI.getEventSubscriber());
|
||||
GraphicsInterface.subscribeToInputEvents(new Object() {
|
||||
|
||||
@Subscribe
|
||||
public void onFrameResized(FrameResizeEvent event) {
|
||||
GUI.invalidateEverything();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onInputEvent(InputEvent event) {
|
||||
GUI.dispatchInput(event);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -29,8 +29,8 @@ import glm.vec._3.Vec3;
|
||||
import glm.vec._4.Vec4;
|
||||
import ru.windcorp.progressia.client.graphics.Colors;
|
||||
import ru.windcorp.progressia.client.graphics.backend.Usage;
|
||||
import ru.windcorp.progressia.client.graphics.model.Face;
|
||||
import ru.windcorp.progressia.client.graphics.model.Faces;
|
||||
import ru.windcorp.progressia.client.graphics.model.ShapePart;
|
||||
import ru.windcorp.progressia.client.graphics.model.ShapeParts;
|
||||
import ru.windcorp.progressia.client.graphics.model.Shape;
|
||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||
@ -84,7 +84,7 @@ public class RenderTarget {
|
||||
|
||||
private final Deque<TransformedMask> maskStack = new LinkedList<>();
|
||||
private final Deque<Mat4> transformStack = new LinkedList<>();
|
||||
private final List<Face> currentClipFaces = new ArrayList<>();
|
||||
private final List<ShapePart> currentClipFaces = new ArrayList<>();
|
||||
|
||||
private int depth = 0;
|
||||
|
||||
@ -94,8 +94,8 @@ public class RenderTarget {
|
||||
|
||||
protected void assembleCurrentClipFromFaces() {
|
||||
if (!currentClipFaces.isEmpty()) {
|
||||
Face[] faces = currentClipFaces.toArray(
|
||||
new Face[currentClipFaces.size()]
|
||||
ShapePart[] faces = currentClipFaces.toArray(
|
||||
new ShapePart[currentClipFaces.size()]
|
||||
);
|
||||
currentClipFaces.clear();
|
||||
|
||||
@ -189,16 +189,14 @@ public class RenderTarget {
|
||||
|
||||
public void addCustomRenderer(Renderable renderable) {
|
||||
assembleCurrentClipFromFaces();
|
||||
assembled.add(
|
||||
new Clip(
|
||||
maskStack,
|
||||
getTransform(),
|
||||
renderable
|
||||
)
|
||||
);
|
||||
|
||||
float depth = this.depth--;
|
||||
final float kostyl = 1e-2f;
|
||||
Mat4 transform = new Mat4().translate(0, 0, depth).scale(1, 1, kostyl).mul(getTransform());
|
||||
assembled.add(new Clip(maskStack, transform, renderable));
|
||||
}
|
||||
|
||||
protected void addFaceToCurrentClip(Face face) {
|
||||
protected void addFaceToCurrentClip(ShapePart face) {
|
||||
currentClipFaces.add(face);
|
||||
}
|
||||
|
||||
@ -270,7 +268,7 @@ public class RenderTarget {
|
||||
fill(Colors.toVector(color));
|
||||
}
|
||||
|
||||
public Face createRectagleFace(
|
||||
public ShapePart createRectagleFace(
|
||||
int x,
|
||||
int y,
|
||||
int width,
|
||||
@ -280,7 +278,7 @@ public class RenderTarget {
|
||||
) {
|
||||
float depth = this.depth--;
|
||||
|
||||
return Faces.createRectangle(
|
||||
return ShapeParts.createRectangle(
|
||||
FlatRenderProgram.getDefault(),
|
||||
texture,
|
||||
color,
|
||||
@ -291,7 +289,7 @@ public class RenderTarget {
|
||||
);
|
||||
}
|
||||
|
||||
public Face createRectagleFace(
|
||||
public ShapePart createRectagleFace(
|
||||
int x,
|
||||
int y,
|
||||
int width,
|
||||
|
@ -33,19 +33,22 @@ public class Font {
|
||||
private final float align;
|
||||
private final Vec4 color;
|
||||
|
||||
public Font(Typeface typeface, int style, float align, Vec4 color) {
|
||||
private final int scale;
|
||||
|
||||
public Font(Typeface typeface, int style, float align, Vec4 color, int scale) {
|
||||
this.typeface = typeface;
|
||||
this.style = style;
|
||||
this.align = align;
|
||||
this.color = color;
|
||||
this.scale = scale;
|
||||
}
|
||||
|
||||
public Font(Typeface typeface, int style, float align, int color) {
|
||||
this(typeface, style, align, Colors.toVector(color));
|
||||
public Font(Typeface typeface, int style, float align, int color, int scale) {
|
||||
this(typeface, style, align, Colors.toVector(color), scale);
|
||||
}
|
||||
|
||||
public Font(Typeface typeface) {
|
||||
this(typeface, Typeface.Style.PLAIN, Typeface.ALIGN_LEFT, Colors.WHITE);
|
||||
this(typeface, Typeface.Style.PLAIN, Typeface.ALIGN_LEFT, Colors.WHITE, 2);
|
||||
}
|
||||
|
||||
public Font() {
|
||||
@ -68,30 +71,62 @@ public class Font {
|
||||
return color;
|
||||
}
|
||||
|
||||
public Renderable assemble(
|
||||
CharSequence chars,
|
||||
float maxWidth
|
||||
) {
|
||||
return typeface.assembleStatic(chars, style, align, maxWidth, color);
|
||||
public int getScale() {
|
||||
return scale;
|
||||
}
|
||||
|
||||
public Renderable assembleDynamic(
|
||||
Supplier<CharSequence> supplier,
|
||||
float maxWidth
|
||||
) {
|
||||
return typeface.assembleDynamic(supplier, style, align, maxWidth, color);
|
||||
private Renderable applyScale(Renderable unscaled) {
|
||||
if (scale == 1) {
|
||||
return unscaled;
|
||||
}
|
||||
|
||||
return renderer -> {
|
||||
renderer.pushTransform().scale(scale);
|
||||
unscaled.render(renderer);
|
||||
renderer.popTransform();
|
||||
};
|
||||
}
|
||||
|
||||
public Renderable assemble(CharSequence chars, float maxWidth) {
|
||||
return applyScale(typeface.assembleStatic(chars, style, align, maxWidth, color));
|
||||
}
|
||||
|
||||
public Renderable assembleDynamic(Supplier<CharSequence> supplier, float maxWidth) {
|
||||
return applyScale(typeface.assembleDynamic(supplier, style, align, maxWidth, color));
|
||||
}
|
||||
|
||||
public Renderable assemble(CharSequence chars) {
|
||||
return assemble(chars, Float.POSITIVE_INFINITY);
|
||||
}
|
||||
|
||||
public Renderable assembleDynamic(Supplier<CharSequence> supplier) {
|
||||
return assembleDynamic(supplier, Float.POSITIVE_INFINITY);
|
||||
}
|
||||
|
||||
public int getWidth(CharSequence chars, float maxWidth) {
|
||||
return typeface.getWidth(chars, style, align, maxWidth);
|
||||
return scale * typeface.getWidth(chars, style, align, maxWidth);
|
||||
}
|
||||
|
||||
public int getHeight(CharSequence chars, float maxWidth) {
|
||||
return typeface.getHeight(chars, style, align, maxWidth);
|
||||
return scale * typeface.getHeight(chars, style, align, maxWidth);
|
||||
}
|
||||
|
||||
public Vec2i getSize(CharSequence chars, float maxWidth, Vec2i result) {
|
||||
return typeface.getSize(chars, style, align, maxWidth, result);
|
||||
result = typeface.getSize(chars, style, align, maxWidth, result);
|
||||
result.mul(scale);
|
||||
return result;
|
||||
}
|
||||
|
||||
public int getWidth(CharSequence chars) {
|
||||
return getWidth(chars, Float.POSITIVE_INFINITY);
|
||||
}
|
||||
|
||||
public int getHeight(CharSequence chars) {
|
||||
return getHeight(chars, Float.POSITIVE_INFINITY);
|
||||
}
|
||||
|
||||
public Vec2i getSize(CharSequence chars, Vec2i result) {
|
||||
return getSize(chars, Float.POSITIVE_INFINITY, result);
|
||||
}
|
||||
|
||||
public boolean supports(char c) {
|
||||
@ -106,7 +141,7 @@ public class Font {
|
||||
* @return the new font
|
||||
*/
|
||||
public Font withStyle(int style) {
|
||||
return new Font(getTypeface(), style, getAlign(), getColor());
|
||||
return new Font(getTypeface(), style, getAlign(), getColor(), getScale());
|
||||
}
|
||||
|
||||
public Font deriveBold() {
|
||||
@ -158,15 +193,19 @@ public class Font {
|
||||
}
|
||||
|
||||
public Font withAlign(float align) {
|
||||
return new Font(getTypeface(), getStyle(), align, getColor());
|
||||
return new Font(getTypeface(), getStyle(), align, getColor(), getScale());
|
||||
}
|
||||
|
||||
public Font withColor(Vec4 color) {
|
||||
return new Font(getTypeface(), getStyle(), getAlign(), color);
|
||||
return new Font(getTypeface(), getStyle(), getAlign(), color, getScale());
|
||||
}
|
||||
|
||||
public Font withColor(int color) {
|
||||
return new Font(getTypeface(), getStyle(), getAlign(), color);
|
||||
return new Font(getTypeface(), getStyle(), getAlign(), color, getScale());
|
||||
}
|
||||
|
||||
public Font withScale(int scale) {
|
||||
return new Font(getTypeface(), getStyle(), getAlign(), getColor(), scale);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -33,8 +33,8 @@ import gnu.trove.stack.TIntStack;
|
||||
import gnu.trove.stack.array.TIntArrayStack;
|
||||
import ru.windcorp.progressia.client.graphics.Colors;
|
||||
import ru.windcorp.progressia.client.graphics.backend.Usage;
|
||||
import ru.windcorp.progressia.client.graphics.model.Face;
|
||||
import ru.windcorp.progressia.client.graphics.model.Faces;
|
||||
import ru.windcorp.progressia.client.graphics.model.ShapePart;
|
||||
import ru.windcorp.progressia.client.graphics.model.ShapeParts;
|
||||
import ru.windcorp.progressia.client.graphics.model.Shape;
|
||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
|
||||
@ -144,7 +144,7 @@ public abstract class SpriteTypeface extends Typeface {
|
||||
return new Shape(
|
||||
Usage.STATIC,
|
||||
getProgram(),
|
||||
Faces.createRectangle(
|
||||
ShapeParts.createRectangle(
|
||||
getProgram(),
|
||||
getTexture(c),
|
||||
Colors.WHITE,
|
||||
@ -167,7 +167,7 @@ public abstract class SpriteTypeface extends Typeface {
|
||||
private final Renderable unitLine = new Shape(
|
||||
Usage.STATIC,
|
||||
getProgram(),
|
||||
Faces.createRectangle(
|
||||
ShapeParts.createRectangle(
|
||||
getProgram(),
|
||||
null,
|
||||
Vectors.UNIT_4,
|
||||
@ -257,7 +257,7 @@ public abstract class SpriteTypeface extends Typeface {
|
||||
|
||||
private class SDWorkspace extends SpriteTypeface.Workspace {
|
||||
|
||||
private final Collection<Face> faces = new ArrayList<>();
|
||||
private final Collection<ShapePart> faces = new ArrayList<>();
|
||||
|
||||
private final Vec3 origin = new Vec3();
|
||||
private final Vec3 width = new Vec3();
|
||||
@ -298,7 +298,7 @@ public abstract class SpriteTypeface extends Typeface {
|
||||
workspace.height.sub(workspace.origin);
|
||||
|
||||
workspace.faces.add(
|
||||
Faces.createRectangle(
|
||||
ShapeParts.createRectangle(
|
||||
getProgram(),
|
||||
texture,
|
||||
color,
|
||||
@ -314,7 +314,7 @@ public abstract class SpriteTypeface extends Typeface {
|
||||
return new Shape(
|
||||
Usage.STATIC,
|
||||
getProgram(),
|
||||
workspace.faces.toArray(new Face[workspace.faces.size()])
|
||||
workspace.faces.toArray(new ShapePart[workspace.faces.size()])
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package ru.windcorp.progressia.client.graphics.gui;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.font.Font;
|
||||
import ru.windcorp.progressia.client.graphics.gui.event.ButtonEvent;
|
||||
import ru.windcorp.progressia.client.graphics.gui.event.EnableEvent;
|
||||
import ru.windcorp.progressia.client.graphics.gui.event.FocusEvent;
|
||||
import ru.windcorp.progressia.client.graphics.gui.event.HoverEvent;
|
||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||
|
||||
public abstract class BasicButton extends Component {
|
||||
|
||||
private final Label label;
|
||||
|
||||
private boolean isPressed = false;
|
||||
private final Collection<Consumer<BasicButton>> actions = Collections.synchronizedCollection(new ArrayList<>());
|
||||
|
||||
public BasicButton(String name, Label label) {
|
||||
super(name);
|
||||
this.label = label;
|
||||
|
||||
setLayout(new LayoutAlign(10));
|
||||
if (label != null) {
|
||||
addChild(this.label);
|
||||
}
|
||||
|
||||
setFocusable(true);
|
||||
reassembleAt(ARTrigger.HOVER, ARTrigger.FOCUS, ARTrigger.ENABLE);
|
||||
|
||||
// Click triggers
|
||||
addInputListener(KeyEvent.class, e -> {
|
||||
if (e.isRepeat())
|
||||
return;
|
||||
|
||||
if (
|
||||
e.isLeftMouseButton() ||
|
||||
e.getKey() == GLFW.GLFW_KEY_SPACE ||
|
||||
e.getKey() == GLFW.GLFW_KEY_ENTER
|
||||
) {
|
||||
setPressed(e.isPress());
|
||||
e.consume();
|
||||
}
|
||||
});
|
||||
|
||||
addListener(new Object() {
|
||||
|
||||
// Release when losing focus
|
||||
@Subscribe
|
||||
public void onFocusChange(FocusEvent e) {
|
||||
if (!e.getNewState()) {
|
||||
setPressed(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Release when hover ends
|
||||
@Subscribe
|
||||
public void onHoverEnded(HoverEvent e) {
|
||||
if (!e.isNowHovered()) {
|
||||
setPressed(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Release when disabled
|
||||
@Subscribe
|
||||
public void onDisabled(EnableEvent e) {
|
||||
if (!e.getComponent().isEnabled()) {
|
||||
setPressed(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger virtualClick when button is released
|
||||
@Subscribe
|
||||
public void onRelease(ButtonEvent.Release e) {
|
||||
virtualClick();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public BasicButton(String name, String label, Font labelFont) {
|
||||
this(name, new Label(name + ".Label", labelFont, label));
|
||||
}
|
||||
|
||||
public BasicButton(String name, String label) {
|
||||
this(name, label, new Font());
|
||||
}
|
||||
|
||||
public boolean isPressed() {
|
||||
return isPressed;
|
||||
}
|
||||
|
||||
public void click() {
|
||||
setPressed(true);
|
||||
setPressed(false);
|
||||
}
|
||||
|
||||
public void setPressed(boolean isPressed) {
|
||||
if (this.isPressed != isPressed) {
|
||||
this.isPressed = isPressed;
|
||||
requestReassembly();
|
||||
|
||||
if (isPressed) {
|
||||
takeFocus();
|
||||
}
|
||||
|
||||
dispatchEvent(ButtonEvent.create(this, this.isPressed));
|
||||
}
|
||||
}
|
||||
|
||||
public BasicButton addAction(Consumer<BasicButton> action) {
|
||||
this.actions.add(Objects.requireNonNull(action, "action"));
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean removeAction(Consumer<BasicButton> action) {
|
||||
return this.actions.remove(action);
|
||||
}
|
||||
|
||||
public void virtualClick() {
|
||||
this.actions.forEach(action -> {
|
||||
action.accept(this);
|
||||
});
|
||||
}
|
||||
|
||||
public Label getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public boolean hasLabel() {
|
||||
return label != null;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package ru.windcorp.progressia.client.graphics.gui;
|
||||
|
||||
import glm.vec._4.Vec4;
|
||||
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
||||
import ru.windcorp.progressia.client.graphics.font.Font;
|
||||
import ru.windcorp.progressia.client.graphics.Colors;
|
||||
|
||||
public class Button extends BasicButton {
|
||||
|
||||
public static final int MARGIN = 2;
|
||||
public static final int BORDER = 2;
|
||||
|
||||
public Button(String name, String label, Font labelFont) {
|
||||
super(name, label, labelFont);
|
||||
}
|
||||
|
||||
public Button(String name, Label label) {
|
||||
super(name, label);
|
||||
}
|
||||
|
||||
public Button(String name, String label) {
|
||||
this(name, label, new Font());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assembleSelf(RenderTarget target) {
|
||||
// Border
|
||||
|
||||
Vec4 borderColor;
|
||||
if (isPressed() || isHovered() || isFocused()) {
|
||||
borderColor = Colors.BLUE;
|
||||
} else {
|
||||
borderColor = Colors.LIGHT_GRAY;
|
||||
}
|
||||
target.fill(getX(), getY(), getWidth(), getHeight(), borderColor);
|
||||
|
||||
// Inside area
|
||||
|
||||
if (isPressed()) {
|
||||
// Do nothing
|
||||
} else {
|
||||
Vec4 backgroundColor;
|
||||
if (isHovered() && isEnabled()) {
|
||||
backgroundColor = Colors.HOVER_BLUE;
|
||||
} else {
|
||||
backgroundColor = Colors.WHITE;
|
||||
}
|
||||
target.fill(
|
||||
getX() + MARGIN,
|
||||
getY() + MARGIN,
|
||||
getWidth() - 2 * MARGIN,
|
||||
getHeight() - 2 * MARGIN,
|
||||
backgroundColor
|
||||
);
|
||||
}
|
||||
|
||||
// Change label font color
|
||||
|
||||
if (hasLabel()) {
|
||||
if (isPressed()) {
|
||||
getLabel().setFont(getLabel().getFont().withColor(Colors.WHITE));
|
||||
} else {
|
||||
getLabel().setFont(getLabel().getFont().withColor(Colors.BLACK));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void postAssembleSelf(RenderTarget target) {
|
||||
// Apply disable tint
|
||||
|
||||
if (!isEnabled()) {
|
||||
target.fill(getX(), getY(), getWidth(), getHeight(), Colors.toVector(0x88FFFFFF));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.graphics.gui;
|
||||
|
||||
import glm.vec._2.i.Vec2i;
|
||||
import glm.vec._4.Vec4;
|
||||
import ru.windcorp.progressia.client.graphics.Colors;
|
||||
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
||||
import ru.windcorp.progressia.client.graphics.font.Font;
|
||||
import ru.windcorp.progressia.client.graphics.font.Typefaces;
|
||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
|
||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutHorizontal;
|
||||
|
||||
public class Checkbox extends BasicButton {
|
||||
|
||||
private class Tick extends Component {
|
||||
|
||||
public Tick() {
|
||||
super(Checkbox.this.getName() + ".Tick");
|
||||
|
||||
setPreferredSize(new Vec2i(Typefaces.getDefault().getLineHeight() * 3 / 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assembleSelf(RenderTarget target) {
|
||||
|
||||
int size = getPreferredSize().x;
|
||||
int x = getX();
|
||||
int y = getY() + (getHeight() - size) / 2;
|
||||
|
||||
// Border
|
||||
|
||||
Vec4 borderColor;
|
||||
if (Checkbox.this.isPressed() || Checkbox.this.isHovered() || Checkbox.this.isFocused()) {
|
||||
borderColor = Colors.BLUE;
|
||||
} else {
|
||||
borderColor = Colors.LIGHT_GRAY;
|
||||
}
|
||||
target.fill(x, y, size, size, borderColor);
|
||||
|
||||
// Inside area
|
||||
|
||||
if (Checkbox.this.isPressed()) {
|
||||
// Do nothing
|
||||
} else {
|
||||
Vec4 backgroundColor;
|
||||
if (Checkbox.this.isHovered() && Checkbox.this.isEnabled()) {
|
||||
backgroundColor = Colors.HOVER_BLUE;
|
||||
} else {
|
||||
backgroundColor = Colors.WHITE;
|
||||
}
|
||||
target.fill(x + 2, y + 2, size - 4, size - 4, backgroundColor);
|
||||
}
|
||||
|
||||
// "Tick"
|
||||
|
||||
if (Checkbox.this.isChecked()) {
|
||||
target.fill(x + 4, y + 4, size - 8, size - 8, Colors.BLUE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private boolean checked;
|
||||
|
||||
public Checkbox(String name, String label, Font labelFont, boolean check) {
|
||||
super(name, label, labelFont);
|
||||
this.checked = check;
|
||||
|
||||
assert getChildren().size() == 1 : "Checkbox expects that BasicButton contains exactly one child";
|
||||
Component basicChild = getChild(0);
|
||||
|
||||
Group group = new Group(getName() + ".LabelAndTick", new LayoutHorizontal(0, 10));
|
||||
removeChild(basicChild);
|
||||
setLayout(new LayoutAlign(0, 0.5f, 10));
|
||||
group.setLayoutHint(basicChild.getLayoutHint());
|
||||
group.addChild(new Tick());
|
||||
group.addChild(basicChild);
|
||||
addChild(group);
|
||||
|
||||
addAction(b -> switchState());
|
||||
}
|
||||
|
||||
public Checkbox(String name, String label, Font labelFont) {
|
||||
this(name, label, labelFont, false);
|
||||
}
|
||||
|
||||
public Checkbox(String name, String label, boolean check) {
|
||||
this(name, label, new Font(), check);
|
||||
}
|
||||
|
||||
public Checkbox(String name, String label) {
|
||||
this(name, label, false);
|
||||
}
|
||||
|
||||
public void switchState() {
|
||||
setChecked(!isChecked());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the checked
|
||||
*/
|
||||
public boolean isChecked() {
|
||||
return checked;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param checked the checked to set
|
||||
*/
|
||||
public void setChecked(boolean checked) {
|
||||
this.checked = checked;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assembleSelf(RenderTarget target) {
|
||||
// Change label font color
|
||||
|
||||
if (isPressed()) {
|
||||
getLabel().setFont(getLabel().getFont().withColor(Colors.BLUE));
|
||||
} else {
|
||||
getLabel().setFont(getLabel().getFont().withColor(Colors.BLACK));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void postAssembleSelf(RenderTarget target) {
|
||||
// Apply disable tint
|
||||
|
||||
if (!isEnabled()) {
|
||||
target.fill(getX(), getY(), getWidth(), getHeight(), Colors.toVector(0x88FFFFFF));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -19,26 +19,33 @@
|
||||
package ru.windcorp.progressia.client.graphics.gui;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
|
||||
import glm.vec._2.i.Vec2i;
|
||||
import ru.windcorp.progressia.client.graphics.backend.InputTracker;
|
||||
import ru.windcorp.progressia.client.graphics.flat.AssembledFlatRenderHelper;
|
||||
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
||||
import ru.windcorp.progressia.client.graphics.flat.RenderTarget.Clip;
|
||||
import ru.windcorp.progressia.client.graphics.gui.event.ChildAddedEvent;
|
||||
import ru.windcorp.progressia.client.graphics.gui.event.ChildRemovedEvent;
|
||||
import ru.windcorp.progressia.client.graphics.gui.event.EnableEvent;
|
||||
import ru.windcorp.progressia.client.graphics.gui.event.FocusEvent;
|
||||
import ru.windcorp.progressia.client.graphics.gui.event.HoverEvent;
|
||||
import ru.windcorp.progressia.client.graphics.gui.event.ParentChangedEvent;
|
||||
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.KeyMatcher;
|
||||
import ru.windcorp.progressia.client.graphics.input.bus.InputBus;
|
||||
import ru.windcorp.progressia.client.graphics.input.bus.InputListener;
|
||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||
import ru.windcorp.progressia.common.util.Named;
|
||||
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
||||
import ru.windcorp.progressia.common.util.crash.ReportingEventBus;
|
||||
@ -50,7 +57,7 @@ public class Component extends Named {
|
||||
private Component parent = null;
|
||||
|
||||
private EventBus eventBus = null;
|
||||
private InputBus inputBus = null;
|
||||
private final InputBus inputBus = new InputBus(this);
|
||||
|
||||
private int x, y;
|
||||
private int width, height;
|
||||
@ -62,6 +69,8 @@ public class Component extends Named {
|
||||
private Object layoutHint = null;
|
||||
private Layout layout = null;
|
||||
|
||||
private boolean isEnabled = true;
|
||||
|
||||
private boolean isFocusable = false;
|
||||
private boolean isFocused = false;
|
||||
|
||||
@ -69,6 +78,9 @@ public class Component extends Named {
|
||||
|
||||
public Component(String name) {
|
||||
super(name);
|
||||
|
||||
// Update hover flag when cursor moves
|
||||
addInputListener(CursorMoveEvent.class, this::updateHoverFlag, InputBus.Option.ALWAYS);
|
||||
}
|
||||
|
||||
public Component getParent() {
|
||||
@ -285,10 +297,31 @@ public class Component extends Named {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this component is focusable. A component needs to be
|
||||
* focusable to become focused. A component that is focusable may not
|
||||
* necessarily be ready to gain focus (see {@link #canGainFocusNow()}).
|
||||
*
|
||||
* @return {@code true} iff the component is focusable
|
||||
* @see #canGainFocusNow()
|
||||
*/
|
||||
public boolean isFocusable() {
|
||||
return isFocusable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this component can become focused at this moment.
|
||||
* <p>
|
||||
* The implementation of this method in {@link Component} considers the
|
||||
* component a focus candidate if it is both focusable and enabled.
|
||||
*
|
||||
* @return {@code true} iff the component can receive focus
|
||||
* @see #isFocusable()
|
||||
*/
|
||||
public boolean canGainFocusNow() {
|
||||
return isFocusable() && isEnabled();
|
||||
}
|
||||
|
||||
public Component setFocusable(boolean focusable) {
|
||||
this.isFocusable = focusable;
|
||||
return this;
|
||||
@ -337,7 +370,7 @@ public class Component extends Named {
|
||||
return;
|
||||
}
|
||||
|
||||
if (component.isFocusable()) {
|
||||
if (component.canGainFocusNow()) {
|
||||
setFocused(false);
|
||||
component.setFocused(true);
|
||||
return;
|
||||
@ -379,7 +412,7 @@ public class Component extends Named {
|
||||
return;
|
||||
}
|
||||
|
||||
if (component.isFocusable()) {
|
||||
if (component.canGainFocusNow()) {
|
||||
setFocused(false);
|
||||
component.setFocused(true);
|
||||
return;
|
||||
@ -433,12 +466,51 @@ public class Component extends Named {
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return isEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables this component. An {@link EnableEvent} is dispatched
|
||||
* if the state changes.
|
||||
*
|
||||
* @param enabled {@code true} to enable the component, {@code false} to
|
||||
* disable the component
|
||||
* @see #setEnabledRecursively(boolean)
|
||||
*/
|
||||
public void setEnabled(boolean enabled) {
|
||||
if (this.isEnabled != enabled) {
|
||||
if (isFocused() && isEnabled()) {
|
||||
focusNext();
|
||||
}
|
||||
|
||||
if (isEnabled()) {
|
||||
setHovered(false);
|
||||
}
|
||||
|
||||
this.isEnabled = enabled;
|
||||
dispatchEvent(new EnableEvent(this));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables this component and all of its children recursively.
|
||||
*
|
||||
* @param enabled {@code true} to enable the components, {@code false} to
|
||||
* disable the components
|
||||
* @see #setEnabled(boolean)
|
||||
*/
|
||||
public void setEnabledRecursively(boolean enabled) {
|
||||
setEnabled(enabled);
|
||||
getChildren().forEach(c -> c.setEnabledRecursively(enabled));
|
||||
}
|
||||
|
||||
public boolean isHovered() {
|
||||
return isHovered;
|
||||
}
|
||||
|
||||
protected void setHovered(boolean isHovered) {
|
||||
if (this.isHovered != isHovered) {
|
||||
if (this.isHovered != isHovered && isEnabled()) {
|
||||
this.isHovered = isHovered;
|
||||
|
||||
if (!isHovered && !getChildren().isEmpty()) {
|
||||
@ -455,6 +527,10 @@ public class Component extends Named {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateHoverFlag(CursorMoveEvent e) {
|
||||
setHovered(contains((int) InputTracker.getCursorX(), (int) InputTracker.getCursorY()));
|
||||
}
|
||||
|
||||
public void addListener(Object listener) {
|
||||
if (eventBus == null) {
|
||||
eventBus = ReportingEventBus.create(getName());
|
||||
@ -475,121 +551,30 @@ public class Component extends Named {
|
||||
eventBus.post(event);
|
||||
}
|
||||
|
||||
public <T extends InputEvent> void addListener(
|
||||
Class<? extends T> type,
|
||||
boolean handlesConsumed,
|
||||
InputListener<T> listener
|
||||
) {
|
||||
if (inputBus == null) {
|
||||
inputBus = new InputBus();
|
||||
public <T extends InputEvent> void addInputListener(Class<? extends T> type, InputListener<T> listener, InputBus.Option... options) {
|
||||
inputBus.register(type, listener, options);
|
||||
}
|
||||
|
||||
inputBus.register(type, handlesConsumed, listener);
|
||||
public void addKeyListener(KeyMatcher matcher, InputListener<? super KeyEvent> listener, InputBus.Option... options) {
|
||||
inputBus.register(matcher, listener, options);
|
||||
}
|
||||
|
||||
public <T extends InputEvent> void addListener(Class<? extends T> type, InputListener<T> listener) {
|
||||
if (inputBus == null) {
|
||||
inputBus = new InputBus();
|
||||
}
|
||||
|
||||
inputBus.register(type, listener);
|
||||
}
|
||||
|
||||
public void removeListener(InputListener<?> listener) {
|
||||
if (inputBus != null) {
|
||||
public void removeInputListener(InputListener<?> listener) {
|
||||
inputBus.unregister(listener);
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleInput(Input input) {
|
||||
if (inputBus != null) {
|
||||
inputBus.dispatch(input);
|
||||
}
|
||||
}
|
||||
|
||||
public void dispatchInput(Input input) {
|
||||
try {
|
||||
switch (input.getTarget()) {
|
||||
case FOCUSED:
|
||||
dispatchInputToFocused(input);
|
||||
break;
|
||||
case HOVERED:
|
||||
dispatchInputToHovered(input);
|
||||
break;
|
||||
case ALL:
|
||||
default:
|
||||
dispatchInputToAll(input);
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw CrashReports.report(e, "Could not dispatch input to Component %s", this);
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchInputToFocused(Input input) {
|
||||
Component c = findFocused();
|
||||
|
||||
if (c == null)
|
||||
return;
|
||||
if (attemptFocusTransfer(input, c))
|
||||
return;
|
||||
|
||||
while (c != null) {
|
||||
c.handleInput(input);
|
||||
c = c.getParent();
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchInputToHovered(Input input) {
|
||||
getChildren().forEach(child -> {
|
||||
if (child.containsCursor()) {
|
||||
child.setHovered(true);
|
||||
|
||||
if (!input.isConsumed()) {
|
||||
child.dispatchInput(input);
|
||||
}
|
||||
} else {
|
||||
child.setHovered(false);
|
||||
}
|
||||
});
|
||||
|
||||
handleInput(input);
|
||||
}
|
||||
|
||||
private void dispatchInputToAll(Input input) {
|
||||
getChildren().forEach(c -> c.dispatchInput(input));
|
||||
handleInput(input);
|
||||
}
|
||||
|
||||
private boolean attemptFocusTransfer(Input input, Component focused) {
|
||||
if (input.isConsumed())
|
||||
return false;
|
||||
if (!(input.getEvent() instanceof KeyEvent))
|
||||
return false;
|
||||
|
||||
KeyEvent keyInput = (KeyEvent) input.getEvent();
|
||||
|
||||
if (keyInput.getKey() == GLFW.GLFW_KEY_TAB && !keyInput.isRelease()) {
|
||||
input.consume();
|
||||
if (keyInput.hasShift()) {
|
||||
focused.focusPrevious();
|
||||
} else {
|
||||
focused.focusNext();
|
||||
}
|
||||
protected boolean passInputToChildren(InputEvent e) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
InputBus getInputBus() {
|
||||
return inputBus;
|
||||
}
|
||||
|
||||
public synchronized boolean contains(int x, int y) {
|
||||
return x >= getX() && x < getX() + getWidth() && y >= getY() && y < getY() + getHeight();
|
||||
}
|
||||
|
||||
public boolean containsCursor() {
|
||||
return contains((int) InputTracker.getCursorX(), (int) InputTracker.getCursorY());
|
||||
}
|
||||
|
||||
public void requestReassembly() {
|
||||
if (parent != null) {
|
||||
parent.requestReassembly();
|
||||
@ -598,6 +583,17 @@ public class Component extends Named {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules the reassembly to occur.
|
||||
* <p>
|
||||
* This method is invoked in root components whenever a
|
||||
* {@linkplain #requestReassembly() reassembly request} is made by one of
|
||||
* its children. When creating the dedicated root component, override this
|
||||
* method to perform any implementation-specific actions that will cause a
|
||||
* reassembly as soon as possible.
|
||||
* <p>
|
||||
* The default implementation of this method does nothing.
|
||||
*/
|
||||
protected void handleReassemblyRequest() {
|
||||
// To be overridden
|
||||
}
|
||||
@ -638,6 +634,149 @@ public class Component extends Named {
|
||||
getChildren().forEach(child -> child.assemble(target));
|
||||
}
|
||||
|
||||
/*
|
||||
* Automatic Reassembly
|
||||
*/
|
||||
|
||||
/**
|
||||
* The various kinds of changes that may be used with
|
||||
* {@link Component#reassembleAt(ARTrigger...)}.
|
||||
*/
|
||||
protected static enum ARTrigger {
|
||||
/**
|
||||
* Reassemble the component whenever its hover status changes, e.g.
|
||||
* whenever the pointer enters or leaves its bounds.
|
||||
*/
|
||||
HOVER,
|
||||
|
||||
/**
|
||||
* Reassemble the component whenever it gains or loses focus.
|
||||
* <p>
|
||||
* <em>Component must be focusable to be able to gain focus.</em> The
|
||||
* component will not be reassembled unless
|
||||
* {@link Component#setFocusable(boolean) setFocusable(true)} has been
|
||||
* invoked.
|
||||
*/
|
||||
FOCUS,
|
||||
|
||||
/**
|
||||
* Reassemble the component whenever it is enabled or disabled.
|
||||
*/
|
||||
ENABLE
|
||||
}
|
||||
|
||||
/**
|
||||
* All trigger objects (event listeners) that are currently registered with
|
||||
* {@link #eventBus}. The field is {@code null} until the first trigger is
|
||||
* installed.
|
||||
*/
|
||||
private Map<ARTrigger, Object> autoReassemblyTriggerObjects = null;
|
||||
|
||||
private Object createTriggerObject(ARTrigger type) {
|
||||
switch (type) {
|
||||
case HOVER:
|
||||
return new Object() {
|
||||
@Subscribe
|
||||
public void onHoverChanged(HoverEvent e) {
|
||||
requestReassembly();
|
||||
}
|
||||
};
|
||||
case FOCUS:
|
||||
return new Object() {
|
||||
@Subscribe
|
||||
public void onFocusChanged(FocusEvent e) {
|
||||
requestReassembly();
|
||||
}
|
||||
};
|
||||
case ENABLE:
|
||||
return new Object() {
|
||||
@Subscribe
|
||||
public void onEnabled(EnableEvent e) {
|
||||
requestReassembly();
|
||||
}
|
||||
};
|
||||
default:
|
||||
throw new NullPointerException("type");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests that {@link #requestReassembly()} is invoked on this component
|
||||
* whenever any of the specified changes occur. Duplicate attempts to
|
||||
* register the same trigger are silently ignored.
|
||||
* <p>
|
||||
* {@code triggers} may be empty, which results in a no-op. It must not be
|
||||
* {@code null}.
|
||||
*
|
||||
* @param triggers the {@linkplain ARTrigger triggers} to
|
||||
* request reassembly with.
|
||||
* @see #disableAutoReassemblyAt(ARTrigger...)
|
||||
*/
|
||||
protected synchronized void reassembleAt(ARTrigger... triggers) {
|
||||
|
||||
Objects.requireNonNull(triggers, "triggers");
|
||||
if (triggers.length == 0)
|
||||
return;
|
||||
|
||||
if (autoReassemblyTriggerObjects == null) {
|
||||
autoReassemblyTriggerObjects = new EnumMap<>(ARTrigger.class);
|
||||
}
|
||||
|
||||
for (ARTrigger trigger : triggers) {
|
||||
if (!autoReassemblyTriggerObjects.containsKey(trigger)) {
|
||||
Object triggerObject = createTriggerObject(trigger);
|
||||
addListener(triggerObject);
|
||||
autoReassemblyTriggerObjects.put(trigger, triggerObject);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests that {@link #requestReassembly()} is no longer invoked on this
|
||||
* component whenever any of the specified changes occur. After a trigger is
|
||||
* removed, it may be reinstalled with
|
||||
* {@link #reassembleAt(ARTrigger...)}. Attempts to remove a
|
||||
* nonexistant trigger are silently ignored.
|
||||
* <p>
|
||||
* {@code triggers} may be empty, which results in a no-op. It must not be
|
||||
* {@code null}.
|
||||
*
|
||||
* @param triggers the {@linkplain ARTrigger triggers} to remove
|
||||
* @see #reassemblyAt(ARTrigger...)
|
||||
*/
|
||||
protected synchronized void disableAutoReassemblyAt(ARTrigger... triggers) {
|
||||
|
||||
Objects.requireNonNull(triggers, "triggers");
|
||||
if (triggers.length == 0)
|
||||
return;
|
||||
|
||||
if (autoReassemblyTriggerObjects == null)
|
||||
return;
|
||||
|
||||
for (ARTrigger trigger : triggers) {
|
||||
Object triggerObject = autoReassemblyTriggerObjects.remove(trigger);
|
||||
if (triggerObject != null) {
|
||||
removeListener(trigger);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Renderable assembleToRenderable() {
|
||||
|
||||
RenderTarget target = new RenderTarget();
|
||||
assemble(target);
|
||||
Clip[] clips = target.assemble();
|
||||
|
||||
return renderer -> {
|
||||
for (Clip clip : clips) {
|
||||
clip.render((AssembledFlatRenderHelper) renderer);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Returns a component that displays this component in its center.
|
||||
// * @return a {@link Aligner} initialized to center this component
|
||||
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.graphics.gui;
|
||||
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
|
||||
|
||||
public class Components {
|
||||
|
||||
public static Component center(Component c) {
|
||||
return new Group(c.getName() + ".Centerer", new LayoutAlign(), c);
|
||||
}
|
||||
|
||||
public static Component hide(Component c, BooleanSupplier shouldHide) {
|
||||
return new Hider(c.getName() + ".Hider", c, shouldHide);
|
||||
}
|
||||
|
||||
private Components() {
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.graphics.gui;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import glm.vec._2.d.Vec2d;
|
||||
import ru.windcorp.progressia.client.graphics.gui.event.DragEvent;
|
||||
import ru.windcorp.progressia.client.graphics.gui.event.DragStartEvent;
|
||||
import ru.windcorp.progressia.client.graphics.gui.event.DragStopEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.CursorMoveEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
|
||||
import ru.windcorp.progressia.client.graphics.input.bus.InputBus;
|
||||
|
||||
public class DragManager {
|
||||
|
||||
private Component component;
|
||||
|
||||
private boolean isDragged = false;
|
||||
private final Vec2d change = new Vec2d();
|
||||
|
||||
public void install(Component c) {
|
||||
Objects.requireNonNull(c, "c");
|
||||
if (c == component) {
|
||||
return;
|
||||
}
|
||||
if (component != null) {
|
||||
throw new IllegalStateException("Already installed on " + component + "; attempted to install on " + c);
|
||||
}
|
||||
|
||||
component = c;
|
||||
|
||||
c.addInputListener(CursorMoveEvent.class, this::onCursorMove, InputBus.Option.ALWAYS);
|
||||
c.addKeyListener(KeyMatcher.LMB, this::onLMB, InputBus.Option.ALWAYS, InputBus.Option.IGNORE_ACTION);
|
||||
}
|
||||
|
||||
private void onCursorMove(CursorMoveEvent e) {
|
||||
if (isDragged) {
|
||||
Vec2d currentChange = e.getChange(null);
|
||||
change.add(currentChange);
|
||||
component.dispatchEvent(new DragEvent(component, currentChange, change));
|
||||
}
|
||||
}
|
||||
|
||||
private void onLMB(KeyEvent e) {
|
||||
if (isDragged && e.isRelease()) {
|
||||
|
||||
isDragged = false;
|
||||
component.dispatchEvent(new DragStopEvent(component, change));
|
||||
|
||||
} else if (!isDragged && !e.isConsumed() && e.isPress() && component.isHovered()) {
|
||||
|
||||
isDragged = true;
|
||||
change.set(0, 0);
|
||||
component.dispatchEvent(new DragStartEvent(component));
|
||||
e.consume();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -33,7 +33,7 @@ public class DynamicLabel extends Component {
|
||||
super(name);
|
||||
this.font = font;
|
||||
this.contents = contents;
|
||||
setPreferredSize(width, font.getHeight("", Float.POSITIVE_INFINITY) * 2);
|
||||
setPreferredSize(width, font.getHeight("", Float.POSITIVE_INFINITY));
|
||||
}
|
||||
|
||||
public Font getFont() {
|
||||
@ -46,7 +46,7 @@ public class DynamicLabel extends Component {
|
||||
|
||||
@Override
|
||||
protected void assembleSelf(RenderTarget target) {
|
||||
target.pushTransform(new Mat4().identity().translate(getX(), getY(), -1000).scale(2));
|
||||
target.pushTransform(new Mat4().identity().translate(getX(), getY(), -1000));
|
||||
target.addCustomRenderer(font.assembleDynamic(getContentSupplier(), Float.POSITIVE_INFINITY));
|
||||
target.popTransform();
|
||||
}
|
||||
|
@ -18,9 +18,17 @@
|
||||
|
||||
package ru.windcorp.progressia.client.graphics.gui;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.flat.AssembledFlatLayer;
|
||||
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.bus.InputBus;
|
||||
import ru.windcorp.progressia.common.util.LowOverheadCache;
|
||||
import ru.windcorp.progressia.common.util.StashingStack;
|
||||
|
||||
public abstract class GUILayer extends AssembledFlatLayer {
|
||||
|
||||
@ -33,7 +41,9 @@ public abstract class GUILayer extends AssembledFlatLayer {
|
||||
|
||||
public GUILayer(String name, Layout layout) {
|
||||
super(name);
|
||||
|
||||
getRoot().setLayout(layout);
|
||||
getRoot().addInputListener(KeyEvent.class, this::attemptFocusTransfer, InputBus.Option.IGNORE_FOCUS);
|
||||
}
|
||||
|
||||
public Component getRoot() {
|
||||
@ -47,9 +57,85 @@ public abstract class GUILayer extends AssembledFlatLayer {
|
||||
getRoot().assemble(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stack frame for {@link #handleInput(InputEvent)}.
|
||||
*/
|
||||
private static class EventHandlingFrame {
|
||||
Component component;
|
||||
Iterator<Component> children;
|
||||
|
||||
void init(Component c) {
|
||||
component = c;
|
||||
children = c.getChildren().iterator();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
component = null;
|
||||
children = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stacks for {@link #handleInput(InputEvent)}.
|
||||
*/
|
||||
private final LowOverheadCache<StashingStack<EventHandlingFrame>> pathCache = new LowOverheadCache<>(
|
||||
() -> new StashingStack<>(64, EventHandlingFrame::new)
|
||||
);
|
||||
|
||||
/*
|
||||
* This is essentially a depth-first iteration of the component tree. The
|
||||
* recursive procedure has been unrolled to reduce call stack length.
|
||||
*/
|
||||
@Override
|
||||
protected void handleInput(Input input) {
|
||||
getRoot().dispatchInput(input);
|
||||
public void handleInput(InputEvent event) {
|
||||
StashingStack<EventHandlingFrame> path = pathCache.grab();
|
||||
|
||||
if (!path.isEmpty()) {
|
||||
throw new IllegalStateException("path is not empty: " + path);
|
||||
}
|
||||
|
||||
path.push().init(root);
|
||||
|
||||
while (!path.isEmpty()) {
|
||||
|
||||
Iterator<Component> it = path.peek().children;
|
||||
if (it.hasNext()) {
|
||||
|
||||
Component c = it.next();
|
||||
|
||||
if (c.isEnabled()) {
|
||||
if (c.getChildren().isEmpty() || !c.passInputToChildren(event)) {
|
||||
c.getInputBus().dispatch(event);
|
||||
} else {
|
||||
path.push().init(c);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
path.peek().component.getInputBus().dispatch(event);
|
||||
path.pop().reset();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pathCache.release(path);
|
||||
}
|
||||
|
||||
private void attemptFocusTransfer(KeyEvent e) {
|
||||
Component focused = getRoot().findFocused();
|
||||
|
||||
if (focused == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.getKey() == GLFW.GLFW_KEY_TAB && !e.isRelease()) {
|
||||
e.consume();
|
||||
if (e.hasShift()) {
|
||||
focused.focusPrevious();
|
||||
} else {
|
||||
focused.focusNext();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
36
src/main/java/ru/windcorp/progressia/client/graphics/gui/Group.java
Executable file
36
src/main/java/ru/windcorp/progressia/client/graphics/gui/Group.java
Executable file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package ru.windcorp.progressia.client.graphics.gui;
|
||||
|
||||
public class Group extends Component {
|
||||
|
||||
public Group(String name, Layout layout) {
|
||||
super(name);
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
public Group(String name, Layout layout, Component... children) {
|
||||
this(name, layout);
|
||||
|
||||
for (Component child : children) {
|
||||
addChild(child);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.graphics.gui;
|
||||
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutFill;
|
||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||
|
||||
public class Hider extends Component {
|
||||
|
||||
private final BooleanSupplier shouldHide;
|
||||
private final Component contents;
|
||||
|
||||
public Hider(String name, Component contents, BooleanSupplier shouldHide) {
|
||||
super(name);
|
||||
this.contents = contents;
|
||||
this.shouldHide = shouldHide;
|
||||
|
||||
setLayout(new LayoutFill());
|
||||
addChild(contents);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean passInputToChildren(InputEvent e) {
|
||||
return !shouldHide.getAsBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Component findFocused() {
|
||||
if (shouldHide.getAsBoolean()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return super.findFocused();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assembleChildren(RenderTarget target) {
|
||||
Renderable renderable = contents.assembleToRenderable();
|
||||
target.addCustomRenderer(renderer -> {
|
||||
if (!shouldHide.getAsBoolean()) {
|
||||
renderable.render(renderer);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -70,7 +70,7 @@ public class Label extends Component {
|
||||
|
||||
public void update() {
|
||||
currentText = contents.get();
|
||||
currentSize = font.getSize(currentText, maxWidth, null).mul(2);
|
||||
currentSize = font.getSize(currentText, maxWidth, null);
|
||||
requestReassembly();
|
||||
}
|
||||
|
||||
@ -83,6 +83,10 @@ public class Label extends Component {
|
||||
return font;
|
||||
}
|
||||
|
||||
public void setFont(Font font) {
|
||||
this.font = font;
|
||||
}
|
||||
|
||||
public String getCurrentText() {
|
||||
return currentText;
|
||||
}
|
||||
@ -95,16 +99,8 @@ public class Label extends Component {
|
||||
protected void assembleSelf(RenderTarget target) {
|
||||
float startX = getX() + font.getAlign() * (getWidth() - currentSize.x);
|
||||
|
||||
target.pushTransform(
|
||||
new Mat4().identity().translate(startX, getY(), -1000) // TODO wtf
|
||||
// is this
|
||||
// magic
|
||||
// <---
|
||||
.scale(2)
|
||||
);
|
||||
|
||||
target.pushTransform(new Mat4().identity().translate(startX, getY(), 0));
|
||||
target.addCustomRenderer(font.assemble(currentText, maxWidth));
|
||||
|
||||
target.popTransform();
|
||||
}
|
||||
|
||||
|
60
src/main/java/ru/windcorp/progressia/client/graphics/gui/Panel.java
Executable file → Normal file
60
src/main/java/ru/windcorp/progressia/client/graphics/gui/Panel.java
Executable file → Normal file
@ -15,14 +15,66 @@
|
||||
* 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;
|
||||
|
||||
public class Panel extends Component {
|
||||
import java.util.Objects;
|
||||
|
||||
import glm.vec._4.Vec4;
|
||||
import ru.windcorp.progressia.client.graphics.Colors;
|
||||
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
||||
|
||||
public class Panel extends Group {
|
||||
|
||||
private Vec4 fill;
|
||||
private Vec4 border;
|
||||
|
||||
public Panel(String name, Layout layout, Vec4 fill, Vec4 border) {
|
||||
super(name, layout);
|
||||
|
||||
this.fill = Objects.requireNonNull(fill, "fill");
|
||||
this.border = border;
|
||||
}
|
||||
|
||||
public Panel(String name, Layout layout) {
|
||||
super(name);
|
||||
setLayout(layout);
|
||||
this(name, layout, Colors.WHITE, Colors.LIGHT_GRAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the fill
|
||||
*/
|
||||
public Vec4 getFill() {
|
||||
return fill;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fill the fill to set
|
||||
*/
|
||||
public void setFill(Vec4 fill) {
|
||||
this.fill = Objects.requireNonNull(fill, "fill");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the border
|
||||
*/
|
||||
public Vec4 getBorder() {
|
||||
return border;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param border the border to set
|
||||
*/
|
||||
public void setBorder(Vec4 border) {
|
||||
this.border = border;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assembleSelf(RenderTarget target) {
|
||||
if (border == null) {
|
||||
target.fill(getX(), getY(), getWidth(), getHeight(), fill);
|
||||
} else {
|
||||
target.fill(getX(), getY(), getWidth(), getHeight(), border);
|
||||
target.fill(getX() + 2, getY() + 2, getWidth() - 4, getHeight() - 4, fill);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,204 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.graphics.gui;
|
||||
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import glm.vec._2.i.Vec2i;
|
||||
import glm.vec._4.Vec4;
|
||||
import ru.windcorp.progressia.client.graphics.Colors;
|
||||
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
||||
import ru.windcorp.progressia.client.graphics.font.Font;
|
||||
import ru.windcorp.progressia.client.graphics.font.Typefaces;
|
||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
|
||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutHorizontal;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||
|
||||
public class RadioButton extends BasicButton {
|
||||
|
||||
private class Tick extends Component {
|
||||
|
||||
public Tick() {
|
||||
super(RadioButton.this.getName() + ".Tick");
|
||||
|
||||
setPreferredSize(new Vec2i(Typefaces.getDefault().getLineHeight() * 3 / 2));
|
||||
}
|
||||
|
||||
private void cross(RenderTarget target, int x, int y, int size, Vec4 color) {
|
||||
target.fill(x + 4, y, size - 8, size, color);
|
||||
target.fill(x + 2, y + 2, size - 4, size - 4, color);
|
||||
target.fill(x, y + 4, size, size - 8, color);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assembleSelf(RenderTarget target) {
|
||||
|
||||
int size = getPreferredSize().x;
|
||||
int x = getX();
|
||||
int y = getY() + (getHeight() - size) / 2;
|
||||
|
||||
// Border
|
||||
|
||||
Vec4 borderColor;
|
||||
if (RadioButton.this.isPressed() || RadioButton.this.isHovered() || RadioButton.this.isFocused()) {
|
||||
borderColor = Colors.BLUE;
|
||||
} else {
|
||||
borderColor = Colors.LIGHT_GRAY;
|
||||
}
|
||||
cross(target, x, y, size, borderColor);
|
||||
|
||||
// Inside area
|
||||
|
||||
if (RadioButton.this.isPressed()) {
|
||||
// Do nothing
|
||||
} else {
|
||||
Vec4 backgroundColor;
|
||||
if (RadioButton.this.isHovered() && RadioButton.this.isEnabled()) {
|
||||
backgroundColor = Colors.HOVER_BLUE;
|
||||
} else {
|
||||
backgroundColor = Colors.WHITE;
|
||||
}
|
||||
cross(target, x + 2, y + 2, size - 4, backgroundColor);
|
||||
}
|
||||
|
||||
// "Tick"
|
||||
|
||||
if (RadioButton.this.isChecked()) {
|
||||
cross(target, x + 4, y + 4, size - 8, Colors.BLUE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private boolean checked;
|
||||
|
||||
private RadioButtonGroup group = null;
|
||||
|
||||
public RadioButton(String name, String label, Font labelFont, boolean check) {
|
||||
super(name, label, labelFont);
|
||||
this.checked = check;
|
||||
|
||||
assert getChildren().size() == 1 : "RadioButton expects that BasicButton contains exactly one child";
|
||||
Component basicChild = getChild(0);
|
||||
|
||||
Group group = new Group(getName() + ".LabelAndTick", new LayoutHorizontal(0, 10));
|
||||
removeChild(basicChild);
|
||||
setLayout(new LayoutAlign(0, 0.5f, 10));
|
||||
group.setLayoutHint(basicChild.getLayoutHint());
|
||||
group.addChild(new Tick());
|
||||
group.addChild(basicChild);
|
||||
addChild(group);
|
||||
|
||||
addInputListener(KeyEvent.class, e -> {
|
||||
if (e.isRelease()) return;
|
||||
|
||||
if (e.getKey() == GLFW.GLFW_KEY_LEFT || e.getKey() == GLFW.GLFW_KEY_UP) {
|
||||
if (this.group != null) {
|
||||
this.group.selectPrevious();
|
||||
this.group.getSelected().takeFocus();
|
||||
}
|
||||
e.consume();
|
||||
} else if (e.getKey() == GLFW.GLFW_KEY_RIGHT || e.getKey() == GLFW.GLFW_KEY_DOWN) {
|
||||
if (this.group != null) {
|
||||
this.group.selectNext();
|
||||
this.group.getSelected().takeFocus();
|
||||
}
|
||||
e.consume();
|
||||
}
|
||||
});
|
||||
|
||||
addAction(b -> setChecked(true));
|
||||
}
|
||||
|
||||
public RadioButton(String name, String label, Font labelFont) {
|
||||
this(name, label, labelFont, false);
|
||||
}
|
||||
|
||||
public RadioButton(String name, String label, boolean check) {
|
||||
this(name, label, new Font(), check);
|
||||
}
|
||||
|
||||
public RadioButton(String name, String label) {
|
||||
this(name, label, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param group the group to set
|
||||
*/
|
||||
public RadioButton setGroup(RadioButtonGroup group) {
|
||||
|
||||
if (this.group != null) {
|
||||
group.selectNext();
|
||||
removeAction(group.listener);
|
||||
group.buttons.remove(this);
|
||||
group.getSelected(); // Clear reference if this was the only button
|
||||
// in the group
|
||||
}
|
||||
|
||||
this.group = group;
|
||||
|
||||
if (this.group != null) {
|
||||
group.buttons.add(this);
|
||||
addAction(group.listener);
|
||||
}
|
||||
|
||||
setChecked(false);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the checked
|
||||
*/
|
||||
public boolean isChecked() {
|
||||
return checked;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param checked the checked to set
|
||||
*/
|
||||
public void setChecked(boolean checked) {
|
||||
this.checked = checked;
|
||||
|
||||
if (group != null) {
|
||||
group.listener.accept(this); // Failsafe for manual invocations of
|
||||
// setChecked()
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assembleSelf(RenderTarget target) {
|
||||
// Change label font color
|
||||
|
||||
if (isPressed()) {
|
||||
getLabel().setFont(getLabel().getFont().withColor(Colors.BLUE));
|
||||
} else {
|
||||
getLabel().setFont(getLabel().getFont().withColor(Colors.BLACK));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void postAssembleSelf(RenderTarget target) {
|
||||
// Apply disable tint
|
||||
|
||||
if (!isEnabled()) {
|
||||
target.fill(getX(), getY(), getWidth(), getHeight(), Colors.toVector(0x88FFFFFF));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.graphics.gui;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class RadioButtonGroup {
|
||||
|
||||
private final Collection<Consumer<RadioButtonGroup>> actions = Collections.synchronizedCollection(new ArrayList<>());
|
||||
final List<RadioButton> buttons = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
private RadioButton selected = null;
|
||||
|
||||
Consumer<BasicButton> listener = b -> {
|
||||
if (b instanceof RadioButton && ((RadioButton) b).isChecked() && buttons.contains(b)) {
|
||||
select((RadioButton) b);
|
||||
}
|
||||
};
|
||||
|
||||
public RadioButtonGroup addAction(Consumer<RadioButtonGroup> action) {
|
||||
this.actions.add(Objects.requireNonNull(action, "action"));
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean removeAction(Consumer<BasicButton> action) {
|
||||
return this.actions.remove(action);
|
||||
}
|
||||
|
||||
public List<RadioButton> getButtons() {
|
||||
return Collections.unmodifiableList(buttons);
|
||||
}
|
||||
|
||||
public synchronized RadioButton getSelected() {
|
||||
if (!buttons.contains(selected)) {
|
||||
selected = null;
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
public synchronized void select(RadioButton button) {
|
||||
if (button != null && !buttons.contains(button)) {
|
||||
throw new IllegalArgumentException("Button " + button + " is not in the group");
|
||||
}
|
||||
|
||||
getSelected(); // Clear if invalid
|
||||
|
||||
if (selected == button) {
|
||||
return; // Terminate listener-setter recursion
|
||||
}
|
||||
|
||||
if (selected != null) {
|
||||
selected.setChecked(false);
|
||||
}
|
||||
|
||||
selected = button;
|
||||
|
||||
if (selected != null) {
|
||||
selected.setChecked(true);
|
||||
}
|
||||
|
||||
actions.forEach(action -> action.accept(this));
|
||||
}
|
||||
|
||||
public void selectNext() {
|
||||
selectNeighbour(+1);
|
||||
}
|
||||
|
||||
public void selectPrevious() {
|
||||
selectNeighbour(-1);
|
||||
}
|
||||
|
||||
private synchronized void selectNeighbour(int direction) {
|
||||
if (getSelected() == null) {
|
||||
if (buttons.isEmpty()) {
|
||||
throw new IllegalStateException("Cannot select neighbour button: group empty");
|
||||
}
|
||||
|
||||
select(buttons.get(0));
|
||||
} else {
|
||||
RadioButton button;
|
||||
int index = buttons.indexOf(selected);
|
||||
|
||||
do {
|
||||
index += direction;
|
||||
|
||||
if (index >= buttons.size()) {
|
||||
index = 0;
|
||||
} else if (index < 0) {
|
||||
index = buttons.size() - 1;
|
||||
}
|
||||
|
||||
button = buttons.get(index);
|
||||
} while (button != getSelected() && !button.isEnabled());
|
||||
|
||||
select(button);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package ru.windcorp.progressia.client.graphics.gui.event;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.gui.BasicButton;
|
||||
|
||||
public class ButtonEvent extends ComponentEvent {
|
||||
|
||||
public static class Press extends ButtonEvent {
|
||||
public Press(BasicButton button) {
|
||||
super(button, true);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Release extends ButtonEvent {
|
||||
public Release(BasicButton button) {
|
||||
super(button, false);
|
||||
}
|
||||
}
|
||||
|
||||
private final boolean isPress;
|
||||
|
||||
protected ButtonEvent(BasicButton button, boolean isPress) {
|
||||
super(button);
|
||||
this.isPress = isPress;
|
||||
}
|
||||
|
||||
public static ButtonEvent create(BasicButton button, boolean isPress) {
|
||||
if (isPress) {
|
||||
return new Press(button);
|
||||
} else {
|
||||
return new Release(button);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isPress() {
|
||||
return isPress;
|
||||
}
|
||||
|
||||
public boolean isRelease() {
|
||||
return !isPress;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.graphics.gui.event;
|
||||
|
||||
import glm.vec._2.d.Vec2d;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||
|
||||
public class DragEvent extends ComponentEvent {
|
||||
|
||||
private final Vec2d currentChange = new Vec2d();
|
||||
private final Vec2d totalChange = new Vec2d();
|
||||
|
||||
public DragEvent(Component component, Vec2d currentChange, Vec2d totalChange) {
|
||||
super(component);
|
||||
this.currentChange.set(currentChange.x, currentChange.y);
|
||||
this.totalChange.set(totalChange.x, totalChange.y);
|
||||
}
|
||||
|
||||
public Vec2d getCurrentChange() {
|
||||
return currentChange;
|
||||
}
|
||||
|
||||
public double getCurrentChangeX() {
|
||||
return currentChange.x;
|
||||
}
|
||||
|
||||
public double getCurrentChangeY() {
|
||||
return currentChange.y;
|
||||
}
|
||||
|
||||
public Vec2d getTotalChange() {
|
||||
return totalChange;
|
||||
}
|
||||
|
||||
public double getTotalChangeX() {
|
||||
return totalChange.x;
|
||||
}
|
||||
|
||||
public double getTotalChangeY() {
|
||||
return totalChange.y;
|
||||
}
|
||||
|
||||
}
|
@ -15,17 +15,14 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.common.modules;
|
||||
package ru.windcorp.progressia.client.graphics.gui.event;
|
||||
|
||||
public class ModuleBuilder {
|
||||
private final Module module;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||
|
||||
public ModuleBuilder(String id) {
|
||||
module = new Module(id);
|
||||
public class DragStartEvent extends ComponentEvent {
|
||||
|
||||
public DragStartEvent(Component component) {
|
||||
super(component);
|
||||
}
|
||||
|
||||
public ModuleBuilder AddTask(Task task) {
|
||||
module.addTask(task);
|
||||
return this;
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.graphics.gui.event;
|
||||
|
||||
import glm.vec._2.d.Vec2d;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||
|
||||
public class DragStopEvent extends ComponentEvent {
|
||||
|
||||
private final Vec2d totalChange = new Vec2d();
|
||||
|
||||
public DragStopEvent(Component component, Vec2d totalChange) {
|
||||
super(component);
|
||||
this.totalChange.set(totalChange.x, totalChange.y);
|
||||
}
|
||||
|
||||
public Vec2d getTotalChange() {
|
||||
return totalChange;
|
||||
}
|
||||
|
||||
public double getTotalChangeX() {
|
||||
return totalChange.x;
|
||||
}
|
||||
|
||||
public double getTotalChangeY() {
|
||||
return totalChange.y;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package ru.windcorp.progressia.client.graphics.gui.event;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||
|
||||
public class EnableEvent extends ComponentEvent {
|
||||
|
||||
public EnableEvent(Component component) {
|
||||
super(component);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package ru.windcorp.progressia.client.graphics.gui.layout;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
|
||||
import glm.vec._2.i.Vec2i;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Layout;
|
||||
|
||||
public class LayoutFill implements Layout {
|
||||
|
||||
private final int margin;
|
||||
|
||||
public LayoutFill(int margin) {
|
||||
this.margin = margin;
|
||||
}
|
||||
|
||||
public LayoutFill() {
|
||||
this(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void layout(Component c) {
|
||||
c.getChildren().forEach(child -> {
|
||||
|
||||
int cWidth = c.getWidth() - 2 * margin;
|
||||
int cHeight = c.getHeight() - 2 * margin;
|
||||
|
||||
child.setBounds(
|
||||
c.getX() + margin,
|
||||
c.getY() + margin,
|
||||
cWidth,
|
||||
cHeight
|
||||
);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec2i calculatePreferredSize(Component c) {
|
||||
Vec2i result = new Vec2i(0, 0);
|
||||
|
||||
c.getChildren().stream()
|
||||
.map(child -> child.getPreferredSize())
|
||||
.forEach(size -> {
|
||||
result.x = max(size.x, result.x);
|
||||
result.y = max(size.y, result.y);
|
||||
});
|
||||
|
||||
result.x += 2 * margin;
|
||||
result.y += 2 * margin;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + "(" + margin + ")";
|
||||
}
|
||||
|
||||
}
|
@ -98,15 +98,26 @@ public class LayoutGrid implements Layout {
|
||||
if (!isSummed)
|
||||
throw new IllegalStateException("Not summed yet");
|
||||
|
||||
int width, height;
|
||||
|
||||
if (column == columns.length - 1) {
|
||||
width = parent.getWidth() - margin - columns[column];
|
||||
} else {
|
||||
width = columns[column + 1] - columns[column] - gap;
|
||||
}
|
||||
|
||||
if (row == rows.length - 1) {
|
||||
height = parent.getHeight() - margin - rows[row];
|
||||
} else {
|
||||
height = rows[row + 1] - rows[row] - gap;
|
||||
}
|
||||
|
||||
child.setBounds(
|
||||
parent.getX() + columns[column],
|
||||
parent.getY() + rows[row],
|
||||
parent.getY() + parent.getHeight() - (rows[row] + height),
|
||||
|
||||
(column != (columns.length - 1) ? (columns[column + 1] - columns[column] - gap)
|
||||
: (parent.getWidth() - margin - columns[column])),
|
||||
|
||||
(row != (rows.length - 1) ? (rows[row + 1] - rows[row] - gap)
|
||||
: (parent.getHeight() - margin - rows[row]))
|
||||
width,
|
||||
height
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -132,10 +143,9 @@ public class LayoutGrid implements Layout {
|
||||
GridDimensions grid = calculateGrid(c);
|
||||
grid.sum();
|
||||
|
||||
int[] coords;
|
||||
for (Component child : c.getChildren()) {
|
||||
coords = (int[]) child.getLayoutHint();
|
||||
grid.setBounds(coords[0], coords[1], child, c);
|
||||
Vec2i coords = (Vec2i) child.getLayoutHint();
|
||||
grid.setBounds(coords.x, coords.y, child, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -149,11 +159,10 @@ public class LayoutGrid implements Layout {
|
||||
|
||||
private GridDimensions calculateGrid(Component parent) {
|
||||
GridDimensions result = new GridDimensions();
|
||||
int[] coords;
|
||||
|
||||
for (Component child : parent.getChildren()) {
|
||||
coords = (int[]) child.getLayoutHint();
|
||||
result.add(coords[0], coords[1], child.getPreferredSize());
|
||||
Vec2i coords = (Vec2i) child.getLayoutHint();
|
||||
result.add(coords.x, coords.y, child.getPreferredSize());
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.graphics.gui.menu;
|
||||
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import glm.vec._2.i.Vec2i;
|
||||
import ru.windcorp.progressia.client.graphics.Colors;
|
||||
import ru.windcorp.progressia.client.graphics.GUI;
|
||||
import ru.windcorp.progressia.client.graphics.font.Font;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||
import ru.windcorp.progressia.client.graphics.gui.GUILayer;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Label;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Layout;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Panel;
|
||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
|
||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutFill;
|
||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
|
||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||
import ru.windcorp.progressia.client.localization.MutableString;
|
||||
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
|
||||
|
||||
public class MenuLayer extends GUILayer {
|
||||
|
||||
private final Component content;
|
||||
private final Component background;
|
||||
|
||||
private final Runnable closeAction = () -> {
|
||||
GUI.removeLayer(this);
|
||||
};
|
||||
|
||||
public MenuLayer(String name, Component content) {
|
||||
super(name, new LayoutFill(0));
|
||||
|
||||
setCursorPolicy(CursorPolicy.REQUIRE);
|
||||
|
||||
this.background = new Panel(name + ".Background", new LayoutAlign(10), Colors.toVector(0x66000000), null);
|
||||
this.content = content;
|
||||
|
||||
background.addChild(content);
|
||||
getRoot().addChild(background);
|
||||
}
|
||||
|
||||
public MenuLayer(String name, Layout contentLayout) {
|
||||
this(name, new Panel(name + ".Content", contentLayout));
|
||||
}
|
||||
|
||||
public MenuLayer(String name) {
|
||||
this(name, new LayoutVertical(20, 10));
|
||||
}
|
||||
|
||||
public Component getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public Component getBackground() {
|
||||
return background;
|
||||
}
|
||||
|
||||
protected void addTitle() {
|
||||
String translationKey = "Layer" + getName() + ".Title";
|
||||
MutableString titleText = new MutableStringLocalized(translationKey);
|
||||
Font titleFont = new Font().deriveBold().withColor(Colors.BLACK).withAlign(0.5f);
|
||||
|
||||
Label label = new Label(getName() + ".Title", titleFont, titleText);
|
||||
getContent().addChild(label);
|
||||
|
||||
Panel panel = new Panel(getName() + ".Title.Underscore", null, Colors.BLUE, null);
|
||||
panel.setLayout(new LayoutFill() {
|
||||
@Override
|
||||
public Vec2i calculatePreferredSize(Component c) {
|
||||
return new Vec2i(label.getPreferredSize().x + 40, 4);
|
||||
}
|
||||
});
|
||||
getContent().addChild(panel);
|
||||
}
|
||||
|
||||
protected Runnable getCloseAction() {
|
||||
return closeAction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleInput(InputEvent event) {
|
||||
|
||||
if (!event.isConsumed()) {
|
||||
if (event instanceof KeyEvent) {
|
||||
KeyEvent keyEvent = (KeyEvent) event;
|
||||
if (keyEvent.isPress() && keyEvent.getKey() == GLFW.GLFW_KEY_ESCAPE) {
|
||||
getCloseAction().run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.handleInput(event);
|
||||
event.consume();
|
||||
}
|
||||
|
||||
}
|
@ -18,7 +18,6 @@
|
||||
|
||||
package ru.windcorp.progressia.client.graphics.input;
|
||||
|
||||
import glm.vec._2.Vec2;
|
||||
import glm.vec._2.d.Vec2d;
|
||||
|
||||
public class CursorMoveEvent extends CursorEvent {
|
||||
@ -81,7 +80,10 @@ public class CursorMoveEvent extends CursorEvent {
|
||||
return getNewY() - getPreviousY();
|
||||
}
|
||||
|
||||
public Vec2 getChange(Vec2 result) {
|
||||
public Vec2d getChange(Vec2d result) {
|
||||
if (result == null) {
|
||||
result = new Vec2d();
|
||||
}
|
||||
return result.set(getChangeX(), getChangeY());
|
||||
}
|
||||
|
||||
|
@ -18,10 +18,32 @@
|
||||
|
||||
package ru.windcorp.progressia.client.graphics.input;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||
|
||||
/**
|
||||
* An instance of user input.
|
||||
* <p>
|
||||
* User input events are typically generated by graphics backend between frames
|
||||
* and passed to the graphics layers from top to bottom. Layers that use
|
||||
* {@link Component}s will forward this event through the Component hierarchy.
|
||||
* <p>
|
||||
* Events have a {@code consumed} flag. A freshly-generated event will have this
|
||||
* flag set to {@code false}. Event listeners that process the event will
|
||||
* usually choose to raise the flag ("consume the event") to ask future
|
||||
* listeners to ignore this event. This is done to avoid multiple UI interfaces
|
||||
* reacting to single input. By default, listeners will not receive consumed
|
||||
* events; however, some listeners may choose to receive, handle and even
|
||||
* un-consume the event.
|
||||
* <p>
|
||||
* {@code InputEvent} objects may be reused for future input events after their
|
||||
* processing is complete; to obtain a static copy, use {@link #snapshot()}.
|
||||
*/
|
||||
public abstract class InputEvent {
|
||||
|
||||
private double time;
|
||||
|
||||
private boolean isConsumed = false;
|
||||
|
||||
public InputEvent(double time) {
|
||||
this.time = time;
|
||||
}
|
||||
@ -36,4 +58,16 @@ public abstract class InputEvent {
|
||||
|
||||
public abstract InputEvent snapshot();
|
||||
|
||||
public boolean isConsumed() {
|
||||
return isConsumed;
|
||||
}
|
||||
|
||||
public void setConsumed(boolean isConsumed) {
|
||||
this.isConsumed = isConsumed;
|
||||
}
|
||||
|
||||
public void consume() {
|
||||
setConsumed(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -18,16 +18,75 @@
|
||||
|
||||
package ru.windcorp.progressia.client.graphics.input;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
public class KeyMatcher {
|
||||
|
||||
private static final Pattern DECLAR_SPLIT_REGEX = Pattern.compile("\\s*\\+\\s*");
|
||||
private static final Map<String, Integer> MOD_TOKENS = ImmutableMap.of(
|
||||
"SHIFT", GLFW.GLFW_MOD_SHIFT,
|
||||
"CONTROL", GLFW.GLFW_MOD_CONTROL,
|
||||
"ALT", GLFW.GLFW_MOD_ALT,
|
||||
"SUPER", GLFW.GLFW_MOD_SUPER
|
||||
);
|
||||
|
||||
public static final KeyMatcher LMB = new KeyMatcher(GLFW.GLFW_MOUSE_BUTTON_LEFT);
|
||||
public static final KeyMatcher RMB = new KeyMatcher(GLFW.GLFW_MOUSE_BUTTON_RIGHT);
|
||||
public static final KeyMatcher MMB = new KeyMatcher(GLFW.GLFW_MOUSE_BUTTON_MIDDLE);
|
||||
|
||||
private final int key;
|
||||
private final int mods;
|
||||
|
||||
protected KeyMatcher(int key, int mods) {
|
||||
public KeyMatcher(int key, int mods) {
|
||||
this.key = key;
|
||||
this.mods = mods;
|
||||
}
|
||||
|
||||
public KeyMatcher(int key) {
|
||||
this.key = key;
|
||||
this.mods = 0;
|
||||
}
|
||||
|
||||
public KeyMatcher(String declar) {
|
||||
String[] tokens = DECLAR_SPLIT_REGEX.split(declar);
|
||||
if (tokens.length == 0) {
|
||||
throw new IllegalArgumentException("No tokens found in \"" + declar + "\"");
|
||||
}
|
||||
|
||||
int key = -1;
|
||||
int mods = 0;
|
||||
|
||||
for (String token : tokens) {
|
||||
token = token.toUpperCase();
|
||||
|
||||
if (MOD_TOKENS.containsKey(token)) {
|
||||
int mod = MOD_TOKENS.get(token);
|
||||
if ((mods & mod) != 0) {
|
||||
throw new IllegalArgumentException("Duplicate modifier \"" + token + "\" in \"" + declar + "\"");
|
||||
}
|
||||
mods |= mod;
|
||||
} else if (key != -1) {
|
||||
throw new IllegalArgumentException("Too many non-modifier tokens in \"" + declar + "\": maximum one key, first offender: \"" + token + "\"");
|
||||
} else {
|
||||
token = token.replace(' ', '_');
|
||||
|
||||
if (token.startsWith("KEYPAD_")) {
|
||||
token = "KP_" + token.substring("KEYPAD_".length());
|
||||
}
|
||||
|
||||
key = Keys.getCode(token);
|
||||
|
||||
if (key == -1) {
|
||||
throw new IllegalArgumentException("Unknown token \"" + token + "\" in \"" + declar + "\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.key = key;
|
||||
this.mods = mods;
|
||||
}
|
||||
@ -43,6 +102,15 @@ public class KeyMatcher {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean matchesIgnoringAction(KeyEvent event) {
|
||||
if (event.getKey() != getKey())
|
||||
return false;
|
||||
if ((event.getMods() & getMods()) != getMods())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getKey() {
|
||||
return key;
|
||||
}
|
||||
@ -51,48 +119,24 @@ public class KeyMatcher {
|
||||
return mods;
|
||||
}
|
||||
|
||||
public static KeyMatcher.Builder of(int key) {
|
||||
return new KeyMatcher.Builder(key);
|
||||
public KeyMatcher with(int modifier) {
|
||||
return new KeyMatcher(key, mods | modifier);
|
||||
}
|
||||
|
||||
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() {
|
||||
public KeyMatcher withShift() {
|
||||
return with(GLFW.GLFW_MOD_SHIFT);
|
||||
}
|
||||
|
||||
public Builder withCtrl() {
|
||||
public KeyMatcher withCtrl() {
|
||||
return with(GLFW.GLFW_MOD_CONTROL);
|
||||
}
|
||||
|
||||
public Builder withAlt() {
|
||||
public KeyMatcher withAlt() {
|
||||
return with(GLFW.GLFW_MOD_ALT);
|
||||
}
|
||||
|
||||
public Builder withSuper() {
|
||||
public KeyMatcher withSuper() {
|
||||
return with(GLFW.GLFW_MOD_SUPER);
|
||||
}
|
||||
|
||||
public KeyMatcher build() {
|
||||
return new KeyMatcher(key, mods);
|
||||
}
|
||||
|
||||
public Predicate<KeyEvent> matcher() {
|
||||
return build()::matches;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ public class Keys {
|
||||
}
|
||||
|
||||
public static int getCode(String internalName) {
|
||||
if (NAMES_TO_CODES.containsKey(internalName)) {
|
||||
if (!NAMES_TO_CODES.containsKey(internalName)) {
|
||||
return -1;
|
||||
} else {
|
||||
return NAMES_TO_CODES.get(internalName);
|
||||
|
@ -20,68 +20,396 @@ package ru.windcorp.progressia.client.graphics.input.bus;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
|
||||
import ru.windcorp.jputil.ArrayUtil;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||
import ru.windcorp.progressia.client.graphics.input.CursorEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
|
||||
import ru.windcorp.progressia.client.graphics.input.WheelEvent;
|
||||
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
||||
|
||||
/**
|
||||
* An event bus optionally related to a {@link Component} that delivers input
|
||||
* events to input listeners. This bus may skip listeners based on circumstance;
|
||||
* behavior can be customized for each listener with {@link Option}s.
|
||||
* <p>
|
||||
* By default, events are filtered by four checks before being delivered to each
|
||||
* listener:
|
||||
* <ol>
|
||||
* <li><em>Consumption check</em>: unless {@link Option#RECEIVE_CONSUMED
|
||||
* RECEIVE_CONSUMED} is set, events that are consumed will not be
|
||||
* delivered.</li>
|
||||
* <li><em>Hover check</em>: for certain event types (for example,
|
||||
* {@link WheelEvent} or {@link KeyEvent} that {@link KeyEvent#isMouse()
|
||||
* isMouse()}), the event will only be delivered if the component is hovered.
|
||||
* This check may be bypassed with option {@link Option#IGNORE_HOVER
|
||||
* IGNORE_HOVER} or made mandatory for all events with
|
||||
* {@link Option#REQUIRE_HOVER REQUIRE_HOVER}. Hover check automatically
|
||||
* succeeds if no component is provided.</li>
|
||||
* <li><em>Focus check</em>: for certain event types (for example,
|
||||
* {@link KeyEvent} that {@code !isMouse()}), the event will only be delivered
|
||||
* if the component has focus. This check may be bypassed with option
|
||||
* {@link Option#IGNORE_FOCUS IGNORE_FOCUS} or made mandatory for all events
|
||||
* with {@link Option#REQUIRE_FOCUS REQUIRE_FOCUS}. Focus check automatically
|
||||
* succeeds if no component is provided.</li>
|
||||
* <li><em>Type check</em>: events of type {@code E} are only delivered to
|
||||
* listeners registered with event type {@code T} if objects of type {@code E}
|
||||
* can be cast to {@code T}.</li>
|
||||
* </ol>
|
||||
* Checks 1-3 are bypassed when option {@link Option#ALWAYS ALWAYS} is
|
||||
* specified.
|
||||
*/
|
||||
public class InputBus {
|
||||
|
||||
private static class WrappedListener {
|
||||
/**
|
||||
* Options that allow customization of checks for listeners.
|
||||
*/
|
||||
public enum Option {
|
||||
|
||||
/**
|
||||
* Ignore checks for consumed events, hover and focus; deliver event if
|
||||
* at all possible. This is shorthand for {@link #RECEIVE_CONSUMED},
|
||||
* {@link #IGNORE_HOVER} and {@link #IGNORE_FOCUS}.
|
||||
*/
|
||||
ALWAYS,
|
||||
|
||||
/**
|
||||
* Receive events that were previously consumed.
|
||||
*/
|
||||
RECEIVE_CONSUMED,
|
||||
|
||||
/**
|
||||
* Do not process events if the listener is registered with a component
|
||||
* and the component is not hovered.
|
||||
*/
|
||||
REQUIRE_HOVER,
|
||||
|
||||
/**
|
||||
* Deliver events even if the event is limited to hovered components by
|
||||
* default.
|
||||
*/
|
||||
IGNORE_HOVER,
|
||||
|
||||
/**
|
||||
* Do not process events if the listener is registered with a component
|
||||
* and the component is not focused.
|
||||
*/
|
||||
REQUIRE_FOCUS,
|
||||
|
||||
/**
|
||||
* Deliver events even if the event is limited to focused components by
|
||||
* default.
|
||||
*/
|
||||
IGNORE_FOCUS,
|
||||
|
||||
/**
|
||||
* Deliver events according to
|
||||
* {@link KeyMatcher#matchesIgnoringAction(KeyEvent)} rather than
|
||||
* {@link KeyMatcher#matches(KeyEvent)} when a {@link KeyMatcher} is
|
||||
* specified.
|
||||
*/
|
||||
IGNORE_ACTION;
|
||||
|
||||
}
|
||||
|
||||
private enum YesNoDefault {
|
||||
YES, NO, DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* A listener with check preferences resolved and type specified.
|
||||
*/
|
||||
private class WrappedListener {
|
||||
|
||||
private final Class<?> type;
|
||||
private final boolean handleConsumed;
|
||||
|
||||
private final boolean dropIfConsumed;
|
||||
private final YesNoDefault dropIfNotHovered;
|
||||
private final YesNoDefault dropIfNotFocused;
|
||||
|
||||
private final InputListener<?> listener;
|
||||
|
||||
public WrappedListener(
|
||||
Class<?> type,
|
||||
boolean handleConsumed,
|
||||
boolean dropIfConsumed,
|
||||
YesNoDefault dropIfNotHovered,
|
||||
YesNoDefault dropIfNotFocused,
|
||||
InputListener<?> listener
|
||||
) {
|
||||
this.type = type;
|
||||
this.handleConsumed = handleConsumed;
|
||||
this.dropIfConsumed = dropIfConsumed;
|
||||
this.dropIfNotHovered = dropIfNotHovered;
|
||||
this.dropIfNotFocused = dropIfNotFocused;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
private boolean handles(Input input) {
|
||||
return (!input.isConsumed() || handleConsumed) &&
|
||||
type.isInstance(input.getEvent());
|
||||
private boolean handles(InputEvent input) {
|
||||
if (dropIfConsumed && input.isConsumed())
|
||||
return false;
|
||||
|
||||
switch (dropIfNotHovered) {
|
||||
case YES:
|
||||
if (!isHovered())
|
||||
return false;
|
||||
break;
|
||||
case NO:
|
||||
break;
|
||||
default:
|
||||
|
||||
if (isHovered())
|
||||
break;
|
||||
|
||||
if (input instanceof KeyEvent && ((KeyEvent) input).isMouse())
|
||||
return false;
|
||||
|
||||
if (input instanceof CursorEvent)
|
||||
return false;
|
||||
|
||||
if (input instanceof WheelEvent)
|
||||
return false;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
switch (dropIfNotFocused) {
|
||||
case YES:
|
||||
if (!isFocused())
|
||||
return false;
|
||||
break;
|
||||
case NO:
|
||||
break;
|
||||
default:
|
||||
|
||||
if (isFocused())
|
||||
break;
|
||||
|
||||
if (input instanceof KeyEvent && !((KeyEvent) input).isMouse())
|
||||
return false;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (!type.isInstance(input))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the listener if the event is deemed appropriate by the four
|
||||
* checks.
|
||||
*
|
||||
* @param event the event to deliver
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void handle(Input input) {
|
||||
if (handles(input)) {
|
||||
boolean consumed = ((InputListener<InputEvent>) listener)
|
||||
.handle(
|
||||
(InputEvent) type.cast(input.getEvent())
|
||||
public void handle(InputEvent event) {
|
||||
if (handles(event)) {
|
||||
// A runtime check of types has been performed; this is safe.
|
||||
InputListener<InputEvent> castListener = (InputListener<InputEvent>) listener;
|
||||
|
||||
try {
|
||||
castListener.handle(event);
|
||||
} catch (Exception e) {
|
||||
throw CrashReports.report(
|
||||
e,
|
||||
"InputListener %s for component %s has failed to receive event %s",
|
||||
listener,
|
||||
owner,
|
||||
event
|
||||
);
|
||||
|
||||
input.setConsumed(consumed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The component queried for focus and hover. May be {@code null}.
|
||||
*/
|
||||
private final Component owner;
|
||||
|
||||
/**
|
||||
* Registered listeners.
|
||||
*/
|
||||
private final Collection<WrappedListener> listeners = new ArrayList<>(4);
|
||||
|
||||
public void dispatch(Input input) {
|
||||
listeners.forEach(l -> l.handle(input));
|
||||
/**
|
||||
* Creates a new input bus that consults the specified {@link Component} to
|
||||
* determine hover and focus.
|
||||
*
|
||||
* @param owner the component to use for hover and focus tests
|
||||
* @see #InputBus()
|
||||
*/
|
||||
public InputBus(Component owner) {
|
||||
this.owner = Objects.requireNonNull(owner, "owner");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new input bus that assumes all hover and focus checks are
|
||||
* successful.
|
||||
*
|
||||
* @see #InputBus(Component)
|
||||
*/
|
||||
public InputBus() {
|
||||
this.owner = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether hover should be assumed for this event bus.
|
||||
*
|
||||
* @return {@code true} iff no component is linked or the linked component
|
||||
* is hovered
|
||||
*/
|
||||
private boolean isHovered() {
|
||||
return owner == null ? true : owner.isHovered();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether focus should be assumed for this event bus.
|
||||
*
|
||||
* @return {@code true} iff no component is linked or the linked component
|
||||
* is focused
|
||||
*/
|
||||
private boolean isFocused() {
|
||||
return owner == null ? true : owner.isFocused();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches (delivers) the provided event to all appropriate listeners.
|
||||
*
|
||||
* @param event the event to process
|
||||
*/
|
||||
public void dispatch(InputEvent event) {
|
||||
Objects.requireNonNull(event, "event");
|
||||
for (WrappedListener listener : listeners) {
|
||||
listener.handle(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a listener on this bus.
|
||||
* <p>
|
||||
* {@code type} specifies the class of events that should be passed to this
|
||||
* listener. Only events of types that extend, implement or equal
|
||||
* {@code type} are processed.
|
||||
* <p>
|
||||
* Zero or more {@link Option}s may be specified to enable or disable the
|
||||
* processing of certain events in certain circumstances. See
|
||||
* {@linkplain InputBus class description} for a detailed breakdown of the
|
||||
* checks performed and the effects of various options. When providing
|
||||
* options to this method, later options override the effects of previous
|
||||
* options.
|
||||
* <p>
|
||||
* Option {@link Option#IGNORE_ACTION IGNORE_ACTION} is ignored silently.
|
||||
*
|
||||
* @param type the event class to deliver
|
||||
* @param listener the listener
|
||||
* @param options the options for this listener
|
||||
*/
|
||||
public <T extends InputEvent> void register(
|
||||
Class<? extends T> type,
|
||||
boolean handlesConsumed,
|
||||
InputListener<T> listener
|
||||
InputListener<T> listener,
|
||||
Option... options
|
||||
) {
|
||||
listeners.add(new WrappedListener(type, handlesConsumed, listener));
|
||||
Objects.requireNonNull(type, "type");
|
||||
Objects.requireNonNull(listener, "listener");
|
||||
|
||||
boolean dropIfConsumed = true;
|
||||
YesNoDefault dropIfNotHovered = YesNoDefault.DEFAULT;
|
||||
YesNoDefault dropIfNotFocused = YesNoDefault.DEFAULT;
|
||||
|
||||
if (options != null) {
|
||||
for (Option option : options) {
|
||||
switch (option) {
|
||||
case ALWAYS:
|
||||
dropIfConsumed = false;
|
||||
dropIfNotHovered = YesNoDefault.NO;
|
||||
dropIfNotFocused = YesNoDefault.NO;
|
||||
break;
|
||||
case RECEIVE_CONSUMED:
|
||||
dropIfConsumed = false;
|
||||
break;
|
||||
case REQUIRE_HOVER:
|
||||
dropIfNotHovered = YesNoDefault.YES;
|
||||
break;
|
||||
case IGNORE_HOVER:
|
||||
dropIfNotFocused = YesNoDefault.NO;
|
||||
break;
|
||||
case REQUIRE_FOCUS:
|
||||
dropIfNotHovered = YesNoDefault.YES;
|
||||
break;
|
||||
case IGNORE_FOCUS:
|
||||
dropIfNotFocused = YesNoDefault.NO;
|
||||
break;
|
||||
case IGNORE_ACTION:
|
||||
// Ignore
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unexpected option " + option);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public <T extends InputEvent> void register(
|
||||
Class<? extends T> type,
|
||||
InputListener<T> listener
|
||||
) {
|
||||
register(type, false, listener);
|
||||
listeners.add(new WrappedListener(type, dropIfConsumed, dropIfNotHovered, dropIfNotFocused, listener));
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a {@link KeyEvent} listener on this bus. An event has to match
|
||||
* the provided {@link KeyMatcher} to be delivered to the listener.
|
||||
* <p>
|
||||
* Zero or more {@link Option}s may be specified to enable or disable the
|
||||
* processing of certain events in certain circumstances. See
|
||||
* {@linkplain InputBus class description} for a detailed breakdown of the
|
||||
* checks performed and the effects of various options. When providing
|
||||
* options to this method, later options override the effects of previous
|
||||
* options.
|
||||
* <p>
|
||||
* Option {@link Option#IGNORE_ACTION IGNORE_ACTION} requests that events
|
||||
* are delivered according to
|
||||
* {@link KeyMatcher#matchesIgnoringAction(KeyEvent)} rather than
|
||||
* {@link KeyMatcher#matches(KeyEvent)}.
|
||||
* specified.
|
||||
*
|
||||
* @param matcher an event filter
|
||||
* @param listener the listener
|
||||
* @param options the options for this listener
|
||||
*/
|
||||
public void register(KeyMatcher matcher, InputListener<? super KeyEvent> listener, Option... options) {
|
||||
Objects.requireNonNull(matcher, "matcher");
|
||||
Objects.requireNonNull(listener, "listener");
|
||||
|
||||
InputListener<KeyEvent> filteringListener;
|
||||
|
||||
if (ArrayUtil.firstIndexOf(options, Option.IGNORE_ACTION) != -1) {
|
||||
filteringListener = e -> {
|
||||
if (matcher.matchesIgnoringAction(e)) {
|
||||
listener.handle(e);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
filteringListener = e -> {
|
||||
if (matcher.matches(e)) {
|
||||
listener.handle(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
register(KeyEvent.class, filteringListener, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all occurrences of the provided listener from this bus.
|
||||
*
|
||||
* @param listener the listener to unregister
|
||||
*/
|
||||
public void unregister(InputListener<?> listener) {
|
||||
if (listener == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
listeners.removeIf(l -> l.listener == listener);
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,6 @@ import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||
@FunctionalInterface
|
||||
public interface InputListener<T extends InputEvent> {
|
||||
|
||||
boolean handle(T event);
|
||||
void handle(T event);
|
||||
|
||||
}
|
||||
|
@ -18,23 +18,23 @@
|
||||
|
||||
package ru.windcorp.progressia.client.graphics.model;
|
||||
|
||||
import static ru.windcorp.progressia.common.world.block.BlockFace.*;
|
||||
import static ru.windcorp.progressia.common.world.rels.AbsFace.*;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import glm.vec._3.Vec3;
|
||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
||||
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
||||
|
||||
class BlockFaceVectors {
|
||||
|
||||
private static BlockFaceVectors createInner(BlockFaceVectors outer) {
|
||||
ImmutableMap.Builder<BlockFace, Vec3> originBuilder = ImmutableMap.builder();
|
||||
ImmutableMap.Builder<AbsFace, Vec3> originBuilder = ImmutableMap.builder();
|
||||
|
||||
ImmutableMap.Builder<BlockFace, Vec3> widthBuilder = ImmutableMap.builder();
|
||||
ImmutableMap.Builder<AbsFace, Vec3> widthBuilder = ImmutableMap.builder();
|
||||
|
||||
ImmutableMap.Builder<BlockFace, Vec3> heightBuilder = ImmutableMap.builder();
|
||||
ImmutableMap.Builder<AbsFace, Vec3> heightBuilder = ImmutableMap.builder();
|
||||
|
||||
for (BlockFace face : getFaces()) {
|
||||
for (AbsFace face : getFaces()) {
|
||||
Vec3 width = outer.getWidth(face);
|
||||
Vec3 height = outer.getHeight(face);
|
||||
|
||||
@ -59,36 +59,36 @@ class BlockFaceVectors {
|
||||
|
||||
static {
|
||||
OUTER = new BlockFaceVectors(
|
||||
ImmutableMap.<BlockFace, Vec3>builder()
|
||||
ImmutableMap.<AbsFace, Vec3>builder()
|
||||
|
||||
.put(TOP, new Vec3(-0.5f, +0.5f, +0.5f))
|
||||
.put(BOTTOM, new Vec3(-0.5f, -0.5f, -0.5f))
|
||||
.put(NORTH, new Vec3(+0.5f, -0.5f, -0.5f))
|
||||
.put(SOUTH, new Vec3(-0.5f, +0.5f, -0.5f))
|
||||
.put(WEST, new Vec3(+0.5f, +0.5f, -0.5f))
|
||||
.put(EAST, new Vec3(-0.5f, -0.5f, -0.5f))
|
||||
.put(POS_Z, new Vec3(-0.5f, +0.5f, +0.5f))
|
||||
.put(NEG_Z, new Vec3(-0.5f, -0.5f, -0.5f))
|
||||
.put(POS_X, new Vec3(+0.5f, -0.5f, -0.5f))
|
||||
.put(NEG_X, new Vec3(-0.5f, +0.5f, -0.5f))
|
||||
.put(POS_Y, new Vec3(+0.5f, +0.5f, -0.5f))
|
||||
.put(NEG_Y, new Vec3(-0.5f, -0.5f, -0.5f))
|
||||
|
||||
.build(),
|
||||
|
||||
ImmutableMap.<BlockFace, Vec3>builder()
|
||||
ImmutableMap.<AbsFace, Vec3>builder()
|
||||
|
||||
.put(TOP, new Vec3(0, -1, 0))
|
||||
.put(BOTTOM, new Vec3(0, +1, 0))
|
||||
.put(NORTH, new Vec3(0, +1, 0))
|
||||
.put(SOUTH, new Vec3(0, -1, 0))
|
||||
.put(WEST, new Vec3(-1, 0, 0))
|
||||
.put(EAST, new Vec3(+1, 0, 0))
|
||||
.put(POS_Z, new Vec3(0, -1, 0))
|
||||
.put(NEG_Z, new Vec3(0, +1, 0))
|
||||
.put(POS_X, new Vec3(0, +1, 0))
|
||||
.put(NEG_X, new Vec3(0, -1, 0))
|
||||
.put(POS_Y, new Vec3(-1, 0, 0))
|
||||
.put(NEG_Y, new Vec3(+1, 0, 0))
|
||||
|
||||
.build(),
|
||||
|
||||
ImmutableMap.<BlockFace, Vec3>builder()
|
||||
ImmutableMap.<AbsFace, Vec3>builder()
|
||||
|
||||
.put(TOP, new Vec3(+1, 0, 0))
|
||||
.put(BOTTOM, new Vec3(+1, 0, 0))
|
||||
.put(NORTH, new Vec3(0, 0, +1))
|
||||
.put(SOUTH, new Vec3(0, 0, +1))
|
||||
.put(WEST, new Vec3(0, 0, +1))
|
||||
.put(EAST, new Vec3(0, 0, +1))
|
||||
.put(POS_Z, new Vec3(+1, 0, 0))
|
||||
.put(NEG_Z, new Vec3(+1, 0, 0))
|
||||
.put(POS_X, new Vec3(0, 0, +1))
|
||||
.put(NEG_X, new Vec3(0, 0, +1))
|
||||
.put(POS_Y, new Vec3(0, 0, +1))
|
||||
.put(NEG_Y, new Vec3(0, 0, +1))
|
||||
|
||||
.build()
|
||||
);
|
||||
@ -100,29 +100,29 @@ class BlockFaceVectors {
|
||||
return inner ? INNER : OUTER;
|
||||
}
|
||||
|
||||
private final ImmutableMap<BlockFace, Vec3> origins;
|
||||
private final ImmutableMap<BlockFace, Vec3> widths;
|
||||
private final ImmutableMap<BlockFace, Vec3> heights;
|
||||
private final ImmutableMap<AbsFace, Vec3> origins;
|
||||
private final ImmutableMap<AbsFace, Vec3> widths;
|
||||
private final ImmutableMap<AbsFace, Vec3> heights;
|
||||
|
||||
public BlockFaceVectors(
|
||||
ImmutableMap<BlockFace, Vec3> origins,
|
||||
ImmutableMap<BlockFace, Vec3> widths,
|
||||
ImmutableMap<BlockFace, Vec3> heights
|
||||
ImmutableMap<AbsFace, Vec3> origins,
|
||||
ImmutableMap<AbsFace, Vec3> widths,
|
||||
ImmutableMap<AbsFace, Vec3> heights
|
||||
) {
|
||||
this.origins = origins;
|
||||
this.widths = widths;
|
||||
this.heights = heights;
|
||||
}
|
||||
|
||||
public Vec3 getOrigin(BlockFace face) {
|
||||
public Vec3 getOrigin(AbsFace face) {
|
||||
return origins.get(face);
|
||||
}
|
||||
|
||||
public Vec3 getWidth(BlockFace face) {
|
||||
public Vec3 getWidth(AbsFace face) {
|
||||
return widths.get(face);
|
||||
}
|
||||
|
||||
public Vec3 getHeight(BlockFace face) {
|
||||
public Vec3 getHeight(AbsFace face) {
|
||||
return heights.get(face);
|
||||
}
|
||||
}
|
||||
|
@ -30,10 +30,10 @@ import ru.windcorp.progressia.client.graphics.backend.VertexBufferObject;
|
||||
public class Shape implements Renderable {
|
||||
|
||||
private final ShapeRenderProgram program;
|
||||
private final Face[] faces;
|
||||
private final ShapePart[] parts;
|
||||
private final Usage usage;
|
||||
|
||||
private FaceGroup[] groups;
|
||||
private ShapePartGroup[] groups;
|
||||
|
||||
private ByteBuffer vertices;
|
||||
private ShortBuffer indices;
|
||||
@ -45,33 +45,33 @@ public class Shape implements Renderable {
|
||||
private VertexBufferObject verticesVbo;
|
||||
private VertexBufferObject indicesVbo;
|
||||
|
||||
public Shape(Usage usage, ShapeRenderProgram program, Face... faces) {
|
||||
public Shape(Usage usage, ShapeRenderProgram program, ShapePart... parts) {
|
||||
this.program = program;
|
||||
this.faces = faces;
|
||||
this.parts = parts;
|
||||
this.usage = usage;
|
||||
|
||||
configureFaces();
|
||||
configureParts();
|
||||
program.preprocess(this);
|
||||
|
||||
assembleBuffers();
|
||||
}
|
||||
|
||||
private void configureFaces() {
|
||||
for (Face face : faces) {
|
||||
face.setShape(this);
|
||||
private void configureParts() {
|
||||
for (ShapePart part : parts) {
|
||||
part.setShape(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void assembleBuffers() {
|
||||
// TODO optimize: only update faces that requested it
|
||||
|
||||
sortFaces();
|
||||
sortParts();
|
||||
resizeBuffers();
|
||||
|
||||
for (Face face : faces) {
|
||||
assembleVertices(face);
|
||||
assembleIndices(face);
|
||||
face.resetUpdateFlags();
|
||||
for (ShapePart part : parts) {
|
||||
assembleVertices(part);
|
||||
assembleIndices(part);
|
||||
part.resetUpdateFlags();
|
||||
}
|
||||
|
||||
this.vertices.flip();
|
||||
@ -85,110 +85,110 @@ public class Shape implements Renderable {
|
||||
|
||||
private void resizeBuffers() {
|
||||
int verticesRequired = 0, indicesRequired = 0;
|
||||
for (Face face : faces) {
|
||||
verticesRequired += face.getVertices().remaining();
|
||||
indicesRequired += face.getIndices().remaining();
|
||||
for (ShapePart part : parts) {
|
||||
verticesRequired += part.getVertices().remaining();
|
||||
indicesRequired += part.getIndices().remaining();
|
||||
}
|
||||
|
||||
if (this.vertices == null || vertices.capacity() < verticesRequired) {
|
||||
if (vertices == null || vertices.capacity() < verticesRequired) {
|
||||
this.vertices = BufferUtils.createByteBuffer(verticesRequired);
|
||||
} else {
|
||||
this.vertices.position(0).limit(verticesRequired);
|
||||
vertices.position(0).limit(verticesRequired);
|
||||
}
|
||||
|
||||
if (this.indices == null || this.indices.capacity() < indicesRequired) {
|
||||
if (indices == null || indices.capacity() < indicesRequired) {
|
||||
this.indices = BufferUtils.createShortBuffer(indicesRequired);
|
||||
} else {
|
||||
this.indices.position(0).limit(indicesRequired);
|
||||
indices.position(0).limit(indicesRequired);
|
||||
}
|
||||
}
|
||||
|
||||
private void assembleVertices(Face face) {
|
||||
face.locationOfVertices = this.vertices.position();
|
||||
private void assembleVertices(ShapePart part) {
|
||||
part.locationOfVertices = this.vertices.position();
|
||||
|
||||
insertVertices(face);
|
||||
linkVerticesWith(face);
|
||||
insertVertices(part);
|
||||
linkVerticesWith(part);
|
||||
}
|
||||
|
||||
private void insertVertices(Face face) {
|
||||
ByteBuffer faceVertices = face.getVertices();
|
||||
private void insertVertices(ShapePart part) {
|
||||
ByteBuffer partVertices = part.getVertices();
|
||||
|
||||
faceVertices.mark();
|
||||
this.vertices.put(faceVertices);
|
||||
faceVertices.reset();
|
||||
partVertices.mark();
|
||||
this.vertices.put(partVertices);
|
||||
partVertices.reset();
|
||||
}
|
||||
|
||||
private void linkVerticesWith(Face face) {
|
||||
private void linkVerticesWith(ShapePart part) {
|
||||
int limit = vertices.limit();
|
||||
int position = vertices.position();
|
||||
|
||||
vertices.limit(position).position(face.getLocationOfVertices());
|
||||
face.vertices = vertices.slice();
|
||||
vertices.limit(position).position(part.getLocationOfVertices());
|
||||
part.vertices = vertices.slice();
|
||||
|
||||
vertices.position(position).limit(limit);
|
||||
}
|
||||
|
||||
private void assembleIndices(Face face) {
|
||||
short vertexOffset = (short) (face.getLocationOfVertices() / program.getBytesPerVertex());
|
||||
private void assembleIndices(ShapePart part) {
|
||||
short vertexOffset = (short) (part.getLocationOfVertices() / program.getBytesPerVertex());
|
||||
|
||||
face.locationOfIndices = indices.position();
|
||||
part.locationOfIndices = indices.position();
|
||||
|
||||
ShortBuffer faceIndices = face.getIndices();
|
||||
ShortBuffer partIndices = part.getIndices();
|
||||
|
||||
if (faceIndices == null) {
|
||||
for (int i = 0; i < face.getVertexCount(); ++i) {
|
||||
if (partIndices == null) {
|
||||
for (int i = 0; i < part.getVertexCount(); ++i) {
|
||||
this.indices.put((short) (vertexOffset + i));
|
||||
}
|
||||
} else {
|
||||
for (int i = faceIndices.position(); i < faceIndices.limit(); ++i) {
|
||||
short faceIndex = faceIndices.get(i);
|
||||
faceIndex += vertexOffset;
|
||||
this.indices.put(faceIndex);
|
||||
for (int i = partIndices.position(); i < partIndices.limit(); ++i) {
|
||||
short partIndex = partIndices.get(i);
|
||||
partIndex += vertexOffset;
|
||||
this.indices.put(partIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sortFaces() {
|
||||
Arrays.sort(faces);
|
||||
private void sortParts() {
|
||||
Arrays.sort(parts);
|
||||
}
|
||||
|
||||
private void assembleGroups() {
|
||||
int unique = countUniqueFaces();
|
||||
this.groups = new FaceGroup[unique];
|
||||
int unique = countUniqueParts();
|
||||
this.groups = new ShapePartGroup[unique];
|
||||
|
||||
if (faces.length == 0)
|
||||
if (parts.length == 0)
|
||||
return;
|
||||
|
||||
int previousHandle = faces[0].getSortingIndex();
|
||||
int previousHandle = parts[0].getSortingIndex();
|
||||
int start = 0;
|
||||
int groupIndex = 0;
|
||||
|
||||
for (int i = 1; i < faces.length; ++i) {
|
||||
if (previousHandle != faces[i].getSortingIndex()) {
|
||||
for (int i = 1; i < parts.length; ++i) {
|
||||
if (previousHandle != parts[i].getSortingIndex()) {
|
||||
|
||||
groups[groupIndex] = new FaceGroup(faces, start, i);
|
||||
groups[groupIndex] = new ShapePartGroup(parts, start, i);
|
||||
start = i;
|
||||
groupIndex++;
|
||||
|
||||
previousHandle = faces[i].getSortingIndex();
|
||||
previousHandle = parts[i].getSortingIndex();
|
||||
}
|
||||
}
|
||||
|
||||
assert groupIndex == groups.length - 1;
|
||||
groups[groupIndex] = new FaceGroup(faces, start, faces.length);
|
||||
groups[groupIndex] = new ShapePartGroup(parts, start, parts.length);
|
||||
}
|
||||
|
||||
private int countUniqueFaces() {
|
||||
if (faces.length == 0)
|
||||
private int countUniqueParts() {
|
||||
if (parts.length == 0)
|
||||
return 0;
|
||||
|
||||
int result = 1;
|
||||
int previousHandle = faces[0].getSortingIndex();
|
||||
int previousHandle = parts[0].getSortingIndex();
|
||||
|
||||
for (int i = 1; i < faces.length; ++i) {
|
||||
if (previousHandle != faces[i].getSortingIndex()) {
|
||||
for (int i = 1; i < parts.length; ++i) {
|
||||
if (previousHandle != parts[i].getSortingIndex()) {
|
||||
result++;
|
||||
previousHandle = faces[i].getSortingIndex();
|
||||
previousHandle = parts[i].getSortingIndex();
|
||||
}
|
||||
}
|
||||
|
||||
@ -238,11 +238,11 @@ public class Shape implements Renderable {
|
||||
return program;
|
||||
}
|
||||
|
||||
public Face[] getFaces() {
|
||||
return faces;
|
||||
public ShapePart[] getParts() {
|
||||
return parts;
|
||||
}
|
||||
|
||||
public FaceGroup[] getGroups() {
|
||||
public ShapePartGroup[] getGroups() {
|
||||
return groups;
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ import java.util.Objects;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||
|
||||
public class Face implements Comparable<Face> {
|
||||
public class ShapePart implements Comparable<ShapePart> {
|
||||
|
||||
private static final ShortBuffer GENERATE_SUCCESSIVE_LATER = null;
|
||||
|
||||
@ -40,7 +40,7 @@ public class Face implements Comparable<Face> {
|
||||
private ShortBuffer userIndices;
|
||||
private boolean userIndicesUpdated = true;
|
||||
|
||||
public Face(
|
||||
public ShapePart(
|
||||
Texture texture,
|
||||
ByteBuffer vertices,
|
||||
ShortBuffer indices
|
||||
@ -50,7 +50,7 @@ public class Face implements Comparable<Face> {
|
||||
setIndices(indices);
|
||||
}
|
||||
|
||||
public Face(
|
||||
public ShapePart(
|
||||
Texture texture,
|
||||
ByteBuffer vertices
|
||||
) {
|
||||
@ -155,7 +155,7 @@ public class Face implements Comparable<Face> {
|
||||
return vertices;
|
||||
}
|
||||
|
||||
public Face setVertices(ByteBuffer vertices) {
|
||||
public ShapePart setVertices(ByteBuffer vertices) {
|
||||
this.vertices = Objects.requireNonNull(vertices, "vertices");
|
||||
markForVertexUpdate();
|
||||
return this;
|
||||
@ -202,7 +202,7 @@ public class Face implements Comparable<Face> {
|
||||
return userIndices.remaining();
|
||||
}
|
||||
|
||||
public Face setIndices(ShortBuffer indices) {
|
||||
public ShapePart setIndices(ShortBuffer indices) {
|
||||
if (indices == null) {
|
||||
indices = GENERATE_SUCCESSIVE_LATER;
|
||||
}
|
||||
@ -245,7 +245,7 @@ public class Face implements Comparable<Face> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Face o) {
|
||||
public int compareTo(ShapePart o) {
|
||||
return Integer.compare(getSortingIndex(), o.getSortingIndex());
|
||||
}
|
||||
|
@ -21,13 +21,13 @@ package ru.windcorp.progressia.client.graphics.model;
|
||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||
import ru.windcorp.progressia.client.graphics.texture.TexturePrimitive;
|
||||
|
||||
public class FaceGroup {
|
||||
public class ShapePartGroup {
|
||||
|
||||
private final TexturePrimitive texture;
|
||||
private final int indexCount;
|
||||
private final int byteOffsetOfIndices;
|
||||
|
||||
FaceGroup(Face[] faces, int start, int end) {
|
||||
ShapePartGroup(ShapePart[] faces, int start, int end) {
|
||||
|
||||
Texture t = faces[start].getTexture();
|
||||
this.texture = t == null ? null : t.getSprite().getPrimitive();
|
||||
@ -36,7 +36,7 @@ public class FaceGroup {
|
||||
int indexCount = 0;
|
||||
|
||||
for (int i = start; i < end; ++i) {
|
||||
Face face = faces[i];
|
||||
ShapePart face = faces[i];
|
||||
|
||||
assert this.texture == null
|
||||
? (face.getTexture() == null)
|
@ -25,14 +25,15 @@ import glm.vec._3.Vec3;
|
||||
import glm.vec._4.Vec4;
|
||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram.VertexBuilder;
|
||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
||||
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram.WRPVertexBuilder;
|
||||
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
||||
|
||||
public class Faces {
|
||||
public class ShapeParts {
|
||||
|
||||
private Faces() {
|
||||
private ShapeParts() {
|
||||
}
|
||||
|
||||
public static Face createRectangle(
|
||||
public static ShapePart createRectangle(
|
||||
ShapeRenderProgram program,
|
||||
Texture texture,
|
||||
Vec4 colorMultiplier,
|
||||
@ -40,9 +41,48 @@ public class Faces {
|
||||
Vec3 width,
|
||||
Vec3 height,
|
||||
boolean flip
|
||||
) {
|
||||
return createRectangle(program, texture, colorMultiplier, origin, width, height, flip, null);
|
||||
}
|
||||
|
||||
public static ShapePart createRectangle(
|
||||
ShapeRenderProgram program,
|
||||
Texture texture,
|
||||
Vec4 colorMultiplier,
|
||||
Vec3 origin,
|
||||
Vec3 width,
|
||||
Vec3 height,
|
||||
boolean flip,
|
||||
Vec3 forcedNormals
|
||||
) {
|
||||
VertexBuilder builder = program.getVertexBuilder();
|
||||
|
||||
if (forcedNormals != null && builder instanceof WRPVertexBuilder) {
|
||||
((WRPVertexBuilder) builder).addVertex(
|
||||
origin,
|
||||
colorMultiplier,
|
||||
new Vec2(0, 0),
|
||||
forcedNormals
|
||||
);
|
||||
((WRPVertexBuilder) builder).addVertex(
|
||||
origin.add_(height),
|
||||
colorMultiplier,
|
||||
new Vec2(0, 1),
|
||||
forcedNormals
|
||||
);
|
||||
((WRPVertexBuilder) builder).addVertex(
|
||||
origin.add_(width),
|
||||
colorMultiplier,
|
||||
new Vec2(1, 0),
|
||||
forcedNormals
|
||||
);
|
||||
((WRPVertexBuilder) builder).addVertex(
|
||||
origin.add_(width).add(height),
|
||||
colorMultiplier,
|
||||
new Vec2(1, 1),
|
||||
forcedNormals
|
||||
);
|
||||
} else {
|
||||
builder.addVertex(
|
||||
origin,
|
||||
colorMultiplier,
|
||||
@ -60,6 +100,7 @@ public class Faces {
|
||||
colorMultiplier,
|
||||
new Vec2(1, 1)
|
||||
);
|
||||
}
|
||||
|
||||
ShortBuffer buffer = flip ? ShortBuffer.wrap(
|
||||
new short[] {
|
||||
@ -82,19 +123,19 @@ public class Faces {
|
||||
}
|
||||
);
|
||||
|
||||
return new Face(
|
||||
return new ShapePart(
|
||||
texture,
|
||||
builder.assemble(),
|
||||
buffer
|
||||
);
|
||||
}
|
||||
|
||||
public static Face createBlockFace(
|
||||
public static ShapePart createBlockFace(
|
||||
ShapeRenderProgram program,
|
||||
Texture texture,
|
||||
Vec4 colorMultiplier,
|
||||
Vec3 blockCenter,
|
||||
BlockFace face,
|
||||
AbsFace face,
|
||||
boolean inner
|
||||
) {
|
||||
BlockFaceVectors vectors = BlockFaceVectors.get(inner);
|
@ -0,0 +1,248 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.graphics.model;
|
||||
|
||||
import java.nio.ShortBuffer;
|
||||
import java.util.Objects;
|
||||
|
||||
import glm.mat._3.Mat3;
|
||||
import glm.mat._4.Mat4;
|
||||
import glm.vec._2.Vec2;
|
||||
import glm.vec._3.Vec3;
|
||||
import glm.vec._4.Vec4;
|
||||
import ru.windcorp.progressia.client.graphics.Colors;
|
||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram.VertexBuilder;
|
||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
|
||||
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram.WRPVertexBuilder;
|
||||
import ru.windcorp.progressia.common.util.StashingStack;
|
||||
import ru.windcorp.progressia.common.util.VectorUtil;
|
||||
|
||||
public class ShapePrototype {
|
||||
|
||||
private final ShapeRenderProgram program;
|
||||
|
||||
private Texture texture;
|
||||
|
||||
private final Vec3[] positions;
|
||||
private final Vec4[] colorMultipliers;
|
||||
private final Vec2[] textureCoords;
|
||||
private final Vec3[] forcedNormals;
|
||||
|
||||
private ShortBuffer indices;
|
||||
private ShortBuffer flippedIndices = null;
|
||||
|
||||
protected static final int TRANSFORM_STACK_SIZE = 64;
|
||||
private final StashingStack<Mat4> transformStack = new StashingStack<>(
|
||||
TRANSFORM_STACK_SIZE,
|
||||
Mat4::new
|
||||
);
|
||||
|
||||
public ShapePrototype(
|
||||
ShapeRenderProgram program,
|
||||
Texture texture,
|
||||
Vec3[] positions,
|
||||
Vec4[] colorMultipliers,
|
||||
Vec2[] textureCoords,
|
||||
Vec3[] forcedNormals,
|
||||
ShortBuffer indices
|
||||
) {
|
||||
this.program = Objects.requireNonNull(program, "program");
|
||||
this.texture = texture;
|
||||
this.indices = Objects.requireNonNull(indices, "indices");
|
||||
|
||||
Objects.requireNonNull(positions, "positions");
|
||||
Objects.requireNonNull(colorMultipliers, "colorMultipliers");
|
||||
Objects.requireNonNull(textureCoords, "textureCoords");
|
||||
|
||||
if (forcedNormals != null && !(program instanceof WorldRenderProgram)) {
|
||||
throw new IllegalArgumentException("Cannot force normals on non-WorldRenderPrograms cuz javahorse stupiddd");
|
||||
}
|
||||
|
||||
if (forcedNormals == null) {
|
||||
forcedNormals = new Vec3[positions.length];
|
||||
}
|
||||
|
||||
this.positions = positions;
|
||||
this.colorMultipliers = colorMultipliers;
|
||||
this.textureCoords = textureCoords;
|
||||
this.forcedNormals = forcedNormals;
|
||||
|
||||
if (positions.length != colorMultipliers.length) {
|
||||
throw new IllegalArgumentException("positions.length (" + positions.length + ") != colorMultipliers.length (" + colorMultipliers + ")");
|
||||
}
|
||||
|
||||
if (positions.length != textureCoords.length) {
|
||||
throw new IllegalArgumentException("positions.length (" + positions.length + ") != textureCoords.length (" + textureCoords + ")");
|
||||
}
|
||||
|
||||
if (positions.length != forcedNormals.length) {
|
||||
throw new IllegalArgumentException("positions.length (" + positions.length + ") != forcedNormals.length (" + forcedNormals + ")");
|
||||
}
|
||||
|
||||
transformStack.push().identity();
|
||||
}
|
||||
|
||||
public ShapePart build() {
|
||||
VertexBuilder builder = program.getVertexBuilder();
|
||||
|
||||
Vec3 transformedPosition = new Vec3();
|
||||
|
||||
for (int i = 0; i < positions.length; ++i) {
|
||||
|
||||
transformedPosition.set(positions[i]);
|
||||
if (transformStack.getSize() > 1) {
|
||||
VectorUtil.applyMat4(transformedPosition, transformStack.getHead());
|
||||
}
|
||||
|
||||
if (forcedNormals[i] != null && builder instanceof WRPVertexBuilder) {
|
||||
((WRPVertexBuilder) builder).addVertex(
|
||||
transformedPosition, colorMultipliers[i], textureCoords[i], forcedNormals[i]
|
||||
);
|
||||
} else {
|
||||
builder.addVertex(transformedPosition, colorMultipliers[i], textureCoords[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return new ShapePart(texture, builder.assemble(), indices);
|
||||
}
|
||||
|
||||
public ShapePrototype apply(Mat4 transform) {
|
||||
for (Vec3 vector : positions) {
|
||||
VectorUtil.applyMat4(vector, transform);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapePrototype apply(Mat3 transform) {
|
||||
for (Vec3 vector : positions) {
|
||||
transform.mul(vector);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Mat4 push() {
|
||||
Mat4 previous = transformStack.getHead();
|
||||
return transformStack.push().set(previous);
|
||||
}
|
||||
|
||||
public ShapePrototype pop() {
|
||||
transformStack.pop();
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapePrototype setTexture(Texture texture) {
|
||||
this.texture = texture;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapePrototype deleteTexture() {
|
||||
this.texture = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapePrototype resetColorMultiplier() {
|
||||
for (Vec4 color : colorMultipliers) {
|
||||
color.set(Colors.WHITE);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapePrototype addColorMultiplier(Vec4 color) {
|
||||
for (Vec4 c : colorMultipliers) {
|
||||
c.mul(color);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapePrototype flip() {
|
||||
ShortBuffer tmp = indices;
|
||||
indices = flippedIndices;
|
||||
flippedIndices = tmp;
|
||||
|
||||
if (indices == null) {
|
||||
int length = flippedIndices.limit();
|
||||
indices = ShortBuffer.allocate(length);
|
||||
for (int i = 0; i < length; ++i) {
|
||||
indices.put(i, flippedIndices.get(length - i - 1));
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapePrototype makeDoubleSided() {
|
||||
int length = indices.limit();
|
||||
ShortBuffer newIndices = ShortBuffer.allocate(length * 2);
|
||||
|
||||
for (int i = 0; i < length; ++i) {
|
||||
newIndices.put(i, indices.get(i));
|
||||
}
|
||||
for (int i = 0; i < length; ++i) {
|
||||
newIndices.put(i + length, indices.get(length - i - 1));
|
||||
}
|
||||
|
||||
indices = flippedIndices = newIndices;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public static ShapePrototype unitSquare(Texture texture, Vec4 color, ShapeRenderProgram program) {
|
||||
return new ShapePrototype(
|
||||
program,
|
||||
texture,
|
||||
new Vec3[] {
|
||||
new Vec3(0, 0, 0),
|
||||
new Vec3(0, 1, 0),
|
||||
new Vec3(1, 0, 0),
|
||||
new Vec3(1, 1, 0)
|
||||
},
|
||||
new Vec4[] {
|
||||
new Vec4(color),
|
||||
new Vec4(color),
|
||||
new Vec4(color),
|
||||
new Vec4(color)
|
||||
},
|
||||
new Vec2[] {
|
||||
new Vec2(0, 0),
|
||||
new Vec2(0, 1),
|
||||
new Vec2(1, 0),
|
||||
new Vec2(1, 1)
|
||||
},
|
||||
null,
|
||||
ShortBuffer.wrap(new short[] {3, 1, 0, 2, 3, 0})
|
||||
);
|
||||
}
|
||||
|
||||
public static ShapePrototype unitSquare(Texture texture, Vec4 color) {
|
||||
return unitSquare(texture, color, WorldRenderProgram.getDefault());
|
||||
}
|
||||
|
||||
public static ShapePrototype unitSquare(Texture texture) {
|
||||
return unitSquare(texture, Colors.WHITE, WorldRenderProgram.getDefault());
|
||||
}
|
||||
|
||||
public static ShapePrototype unitSquare(Vec4 color) {
|
||||
return unitSquare(null, color, WorldRenderProgram.getDefault());
|
||||
}
|
||||
|
||||
public static ShapePrototype unitSquare() {
|
||||
return unitSquare(null, Colors.WHITE, WorldRenderProgram.getDefault());
|
||||
}
|
||||
|
||||
}
|
@ -116,7 +116,7 @@ public class ShapeRenderProgram extends Program {
|
||||
|
||||
try {
|
||||
enableAttributes();
|
||||
for (FaceGroup group : shape.getGroups()) {
|
||||
for (ShapePartGroup group : shape.getGroups()) {
|
||||
renderFaceGroup(group);
|
||||
}
|
||||
} finally {
|
||||
@ -182,7 +182,7 @@ public class ShapeRenderProgram extends Program {
|
||||
indices.bind(BindTarget.ELEMENT_ARRAY);
|
||||
}
|
||||
|
||||
protected void renderFaceGroup(FaceGroup group) {
|
||||
protected void renderFaceGroup(ShapePartGroup group) {
|
||||
TexturePrimitive texture = group.getTexture();
|
||||
|
||||
if (texture != null) {
|
||||
@ -206,12 +206,12 @@ public class ShapeRenderProgram extends Program {
|
||||
}
|
||||
|
||||
public void preprocess(Shape shape) {
|
||||
for (Face face : shape.getFaces()) {
|
||||
for (ShapePart face : shape.getParts()) {
|
||||
applySprites(face);
|
||||
}
|
||||
}
|
||||
|
||||
private void applySprites(Face face) {
|
||||
private void applySprites(ShapePart face) {
|
||||
if (face.getTexture() == null)
|
||||
return;
|
||||
|
||||
|
@ -20,11 +20,13 @@ package ru.windcorp.progressia.client.graphics.model;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import glm.mat._4.Mat4;
|
||||
import glm.vec._3.Vec3;
|
||||
import glm.vec._4.Vec4;
|
||||
import ru.windcorp.progressia.client.graphics.backend.Usage;
|
||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
||||
import ru.windcorp.progressia.common.util.VectorUtil;
|
||||
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
||||
|
||||
public class Shapes {
|
||||
|
||||
@ -50,7 +52,7 @@ public class Shapes {
|
||||
boolean flip
|
||||
) {
|
||||
|
||||
Face top = Faces.createRectangle(
|
||||
ShapePart top = ShapeParts.createRectangle(
|
||||
program,
|
||||
topTexture,
|
||||
colorMultiplier,
|
||||
@ -60,7 +62,7 @@ public class Shapes {
|
||||
flip
|
||||
);
|
||||
|
||||
Face bottom = Faces.createRectangle(
|
||||
ShapePart bottom = ShapeParts.createRectangle(
|
||||
program,
|
||||
bottomTexture,
|
||||
colorMultiplier,
|
||||
@ -70,7 +72,7 @@ public class Shapes {
|
||||
flip
|
||||
);
|
||||
|
||||
Face north = Faces.createRectangle(
|
||||
ShapePart north = ShapeParts.createRectangle(
|
||||
program,
|
||||
northTexture,
|
||||
colorMultiplier,
|
||||
@ -80,7 +82,7 @@ public class Shapes {
|
||||
flip
|
||||
);
|
||||
|
||||
Face south = Faces.createRectangle(
|
||||
ShapePart south = ShapeParts.createRectangle(
|
||||
program,
|
||||
southTexture,
|
||||
colorMultiplier,
|
||||
@ -90,7 +92,7 @@ public class Shapes {
|
||||
flip
|
||||
);
|
||||
|
||||
Face east = Faces.createRectangle(
|
||||
ShapePart east = ShapeParts.createRectangle(
|
||||
program,
|
||||
eastTexture,
|
||||
colorMultiplier,
|
||||
@ -100,7 +102,7 @@ public class Shapes {
|
||||
flip
|
||||
);
|
||||
|
||||
Face west = Faces.createRectangle(
|
||||
ShapePart west = ShapeParts.createRectangle(
|
||||
program,
|
||||
westTexture,
|
||||
colorMultiplier,
|
||||
@ -124,6 +126,36 @@ public class Shapes {
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Shape createParallelogram(
|
||||
// Try saying that 10 times fast
|
||||
ShapeRenderProgram program,
|
||||
|
||||
Vec3 origin,
|
||||
|
||||
Vec3 width,
|
||||
Vec3 height,
|
||||
|
||||
Vec4 colorMultiplier,
|
||||
|
||||
Texture texture
|
||||
) {
|
||||
Shape result = new Shape(
|
||||
Usage.STATIC,
|
||||
program,
|
||||
ShapeParts.createRectangle(
|
||||
program,
|
||||
texture,
|
||||
colorMultiplier,
|
||||
origin,
|
||||
width,
|
||||
height,
|
||||
false
|
||||
)
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static class PppBuilder {
|
||||
|
||||
private final ShapeRenderProgram program;
|
||||
@ -165,16 +197,16 @@ public class Shapes {
|
||||
|
||||
public PppBuilder(
|
||||
ShapeRenderProgram program,
|
||||
Map<BlockFace, Texture> textureMap
|
||||
Map<AbsFace, Texture> textureMap
|
||||
) {
|
||||
this(
|
||||
program,
|
||||
textureMap.get(BlockFace.TOP),
|
||||
textureMap.get(BlockFace.BOTTOM),
|
||||
textureMap.get(BlockFace.NORTH),
|
||||
textureMap.get(BlockFace.SOUTH),
|
||||
textureMap.get(BlockFace.EAST),
|
||||
textureMap.get(BlockFace.WEST)
|
||||
textureMap.get(AbsFace.POS_Z),
|
||||
textureMap.get(AbsFace.NEG_Z),
|
||||
textureMap.get(AbsFace.POS_X),
|
||||
textureMap.get(AbsFace.NEG_X),
|
||||
textureMap.get(AbsFace.NEG_Y),
|
||||
textureMap.get(AbsFace.POS_Y)
|
||||
);
|
||||
}
|
||||
|
||||
@ -260,6 +292,34 @@ public class Shapes {
|
||||
return this.setSize(size, size, size);
|
||||
}
|
||||
|
||||
public PppBuilder centerAt(float x, float y, float z) {
|
||||
origin.set(x, y, z);
|
||||
|
||||
origin.mul(2);
|
||||
origin.sub(width);
|
||||
origin.sub(height);
|
||||
origin.sub(depth);
|
||||
origin.div(2);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public PppBuilder apply(Mat4 transform) {
|
||||
VectorUtil.applyMat4(origin, transform);
|
||||
VectorUtil.rotateOnly(width, transform);
|
||||
VectorUtil.rotateOnly(height, transform);
|
||||
VectorUtil.rotateOnly(depth, transform);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PppBuilder scale(float factor) {
|
||||
origin.mul(factor);
|
||||
width.mul(factor);
|
||||
height.mul(factor);
|
||||
depth.mul(factor);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PppBuilder flip() {
|
||||
this.flip = true;
|
||||
return this;
|
||||
@ -285,4 +345,108 @@ public class Shapes {
|
||||
|
||||
}
|
||||
|
||||
public static class PgmBuilder {
|
||||
|
||||
private final ShapeRenderProgram program;
|
||||
|
||||
private final Vec3 origin = new Vec3(0, 0, 0);
|
||||
|
||||
private final Vec3 width = new Vec3(1, 0, 0);
|
||||
private final Vec3 height = new Vec3(0, 1, 0);
|
||||
|
||||
private final Vec4 colorMultiplier = new Vec4(1, 1, 1, 1);
|
||||
|
||||
private final Texture texture;
|
||||
|
||||
public PgmBuilder(ShapeRenderProgram program, Texture texture) {
|
||||
this.program = program;
|
||||
this.texture = texture;
|
||||
}
|
||||
|
||||
public PgmBuilder setOrigin(Vec3 origin) {
|
||||
this.origin.set(origin);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PgmBuilder setOrigin(float x, float y, float z) {
|
||||
this.origin.set(x, y, z);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PgmBuilder setColorMultiplier(Vec4 colorMultiplier) {
|
||||
this.colorMultiplier.set(colorMultiplier);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PgmBuilder setColorMultiplier(float r, float g, float b) {
|
||||
this.colorMultiplier.set(r, g, b, 1);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PgmBuilder setColorMultiplier(float r, float g, float b, float a) {
|
||||
this.colorMultiplier.set(r, g, b, a);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PgmBuilder setWidth(Vec3 vector) {
|
||||
this.width.set(vector);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PgmBuilder setWidth(float x, float y, float z) {
|
||||
this.width.set(x, y, z);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PgmBuilder setWidth(float x) {
|
||||
this.width.set(x, 0, 0);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PgmBuilder setHeight(Vec3 vector) {
|
||||
this.height.set(vector);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PgmBuilder setHeight(float x, float y, float z) {
|
||||
this.height.set(x, y, z);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PgmBuilder setHeight(float y) {
|
||||
this.height.set(0, y, 0);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PgmBuilder setSize(float x, float y) {
|
||||
return this.setWidth(x).setHeight(y);
|
||||
}
|
||||
|
||||
public PgmBuilder setSize(float size) {
|
||||
return this.setSize(size, size);
|
||||
}
|
||||
|
||||
public PgmBuilder centerAt(float x, float y, float z) {
|
||||
origin.set(x, y, z);
|
||||
|
||||
origin.mul(2);
|
||||
origin.sub(width);
|
||||
origin.div(2);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Shape create() {
|
||||
return createParallelogram(
|
||||
program,
|
||||
origin,
|
||||
width,
|
||||
height,
|
||||
colorMultiplier,
|
||||
texture
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ package ru.windcorp.progressia.client.graphics.texture;
|
||||
import java.util.Map;
|
||||
|
||||
import glm.vec._2.Vec2;
|
||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
||||
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
||||
|
||||
public class ComplexTexture {
|
||||
|
||||
@ -54,14 +54,14 @@ public class ComplexTexture {
|
||||
);
|
||||
}
|
||||
|
||||
public Map<BlockFace, Texture> getCuboidTextures(
|
||||
public Map<AbsFace, Texture> getCuboidTextures(
|
||||
int x,
|
||||
int y,
|
||||
int width,
|
||||
int height,
|
||||
int depth
|
||||
) {
|
||||
return BlockFace.mapToFaces(
|
||||
return AbsFace.mapToFaces(
|
||||
get(
|
||||
x + depth + width,
|
||||
y + height + depth,
|
||||
@ -86,7 +86,7 @@ public class ComplexTexture {
|
||||
);
|
||||
}
|
||||
|
||||
public Map<BlockFace, Texture> getCuboidTextures(
|
||||
public Map<AbsFace, Texture> getCuboidTextures(
|
||||
int x,
|
||||
int y,
|
||||
int size
|
||||
|
@ -28,6 +28,7 @@ import javax.imageio.ImageIO;
|
||||
|
||||
import ru.windcorp.progressia.common.resource.Resource;
|
||||
import ru.windcorp.progressia.common.util.BinUtil;
|
||||
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
||||
|
||||
public class TextureLoader {
|
||||
|
||||
@ -99,7 +100,12 @@ public class TextureLoader {
|
||||
TextureSettings settings
|
||||
)
|
||||
throws IOException {
|
||||
return loadPixels(resource.getInputStream(), settings);
|
||||
|
||||
InputStream stream = resource.getInputStream();
|
||||
if (stream == null) {
|
||||
throw CrashReports.report(null, "Texture \"%s\" not found", resource.getName());
|
||||
}
|
||||
return loadPixels(stream, settings);
|
||||
}
|
||||
|
||||
private TextureLoader() {
|
||||
|
@ -29,6 +29,9 @@ import glm.mat._4.Mat4;
|
||||
import glm.vec._3.Vec3;
|
||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||
import ru.windcorp.progressia.client.graphics.world.Camera.Anchor.Mode;
|
||||
import ru.windcorp.progressia.client.world.entity.NPedModel;
|
||||
import ru.windcorp.progressia.common.util.Matrices;
|
||||
import ru.windcorp.progressia.common.util.Vectors;
|
||||
|
||||
public class Camera {
|
||||
|
||||
@ -60,13 +63,13 @@ public class Camera {
|
||||
}
|
||||
}
|
||||
|
||||
void getCameraPosition(Vec3 output);
|
||||
Vec3 getCameraPosition(Vec3 output);
|
||||
|
||||
void getCameraVelocity(Vec3 output);
|
||||
Vec3 getCameraVelocity(Vec3 output);
|
||||
|
||||
float getCameraYaw();
|
||||
Vec3 getLookingAt(Vec3 output);
|
||||
|
||||
float getCameraPitch();
|
||||
Vec3 getUpVector(Vec3 output);
|
||||
|
||||
Collection<Mode> getCameraModes();
|
||||
|
||||
@ -84,14 +87,11 @@ public class Camera {
|
||||
*/
|
||||
|
||||
private final Vec3 lastAnchorPosition = new Vec3();
|
||||
private float lastAnchorYaw;
|
||||
private float lastAnchorPitch;
|
||||
private final Vec3 lastAnchorLookingAt = new Vec3();
|
||||
private final Vec3 lastAnchorUpVector = new Vec3();
|
||||
|
||||
private final Mat4 lastCameraMatrix = new Mat4();
|
||||
|
||||
private final Vec3 lastAnchorLookingAt = new Vec3();
|
||||
private final Vec3 lastAnchorUp = new Vec3();
|
||||
|
||||
{
|
||||
invalidateCache();
|
||||
}
|
||||
@ -108,6 +108,9 @@ public class Camera {
|
||||
*/
|
||||
|
||||
public void apply(WorldRenderHelper helper) {
|
||||
if (NPedModel.flag) {
|
||||
// System.out.println("Camera.apply()");
|
||||
}
|
||||
applyPerspective(helper);
|
||||
rotateCoordinateSystem(helper);
|
||||
|
||||
@ -149,26 +152,34 @@ public class Camera {
|
||||
}
|
||||
|
||||
private void applyDirection(WorldRenderHelper helper) {
|
||||
float pitch = anchor.getCameraPitch();
|
||||
float yaw = anchor.getCameraYaw();
|
||||
anchor.getLookingAt(lastAnchorLookingAt);
|
||||
anchor.getUpVector(lastAnchorUpVector);
|
||||
|
||||
helper.pushViewTransform()
|
||||
.rotateY(-pitch)
|
||||
.rotateZ(-yaw);
|
||||
lookAt(helper.pushViewTransform());
|
||||
}
|
||||
|
||||
this.lastAnchorYaw = yaw;
|
||||
this.lastAnchorPitch = pitch;
|
||||
private void lookAt(Mat4 result) {
|
||||
Vec3 f = this.lastAnchorLookingAt;
|
||||
Vec3 s = Vectors.grab3();
|
||||
Vec3 u = Vectors.grab3();
|
||||
|
||||
this.lastAnchorLookingAt.set(
|
||||
cos(pitch) * cos(yaw),
|
||||
cos(pitch) * sin(yaw),
|
||||
sin(pitch)
|
||||
);
|
||||
this.lastAnchorUp.set(
|
||||
cos(pitch + PI_F / 2) * cos(yaw),
|
||||
cos(pitch + PI_F / 2) * sin(yaw),
|
||||
sin(pitch + PI_F / 2)
|
||||
f.cross(this.lastAnchorUpVector, s);
|
||||
s.normalize();
|
||||
|
||||
s.cross(f, u);
|
||||
|
||||
Mat4 workspace = Matrices.grab4();
|
||||
workspace.set(
|
||||
+f.x, -s.x, +u.x, 0,
|
||||
+f.y, -s.y, +u.y, 0,
|
||||
+f.z, -s.z, +u.z, 0,
|
||||
0, 0, 0, 1
|
||||
);
|
||||
result.mul(workspace);
|
||||
Matrices.release(workspace);
|
||||
|
||||
Vectors.release(s);
|
||||
Vectors.release(u);
|
||||
}
|
||||
|
||||
private void applyPosition(WorldRenderHelper helper) {
|
||||
@ -247,8 +258,6 @@ public class Camera {
|
||||
|
||||
private void invalidateCache() {
|
||||
this.lastAnchorPosition.set(Float.NaN);
|
||||
this.lastAnchorYaw = Float.NaN;
|
||||
this.lastAnchorPitch = Float.NaN;
|
||||
|
||||
this.lastCameraMatrix.set(
|
||||
Float.NaN,
|
||||
@ -270,7 +279,7 @@ public class Camera {
|
||||
);
|
||||
|
||||
this.lastAnchorLookingAt.set(Float.NaN);
|
||||
this.lastAnchorUp.set(Float.NaN);
|
||||
this.lastAnchorUpVector.set(Float.NaN);
|
||||
}
|
||||
|
||||
public Anchor.Mode getMode() {
|
||||
@ -289,14 +298,6 @@ public class Camera {
|
||||
return currentModeIndex;
|
||||
}
|
||||
|
||||
public float getLastAnchorYaw() {
|
||||
return lastAnchorYaw;
|
||||
}
|
||||
|
||||
public float getLastAnchorPitch() {
|
||||
return lastAnchorPitch;
|
||||
}
|
||||
|
||||
public Vec3 getLastAnchorPosition() {
|
||||
return lastAnchorPosition;
|
||||
}
|
||||
@ -310,7 +311,7 @@ public class Camera {
|
||||
}
|
||||
|
||||
public Vec3 getLastAnchorUp() {
|
||||
return lastAnchorUp;
|
||||
return lastAnchorUpVector;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -59,24 +59,32 @@ public class EntityAnchor implements Anchor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getCameraPosition(Vec3 output) {
|
||||
public Vec3 getCameraPosition(Vec3 output) {
|
||||
if (output == null) output = new Vec3();
|
||||
model.getViewPoint(output);
|
||||
output.add(entity.getPosition());
|
||||
output.add(model.getPosition());
|
||||
return output;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getCameraVelocity(Vec3 output) {
|
||||
public Vec3 getCameraVelocity(Vec3 output) {
|
||||
if (output == null) output = new Vec3();
|
||||
output.set(entity.getVelocity());
|
||||
return output;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getCameraYaw() {
|
||||
return entity.getYaw();
|
||||
public Vec3 getLookingAt(Vec3 output) {
|
||||
if (output == null) output = new Vec3();
|
||||
model.getLookingAt(output);
|
||||
return output;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getCameraPitch() {
|
||||
return entity.getPitch();
|
||||
public Vec3 getUpVector(Vec3 output) {
|
||||
if (output == null) output = new Vec3();
|
||||
model.getUpVector(output);
|
||||
return output;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -84,4 +92,13 @@ public class EntityAnchor implements Anchor {
|
||||
return modes;
|
||||
}
|
||||
|
||||
public EntityData getEntity() {
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Anchor for entity " + entity;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ import ru.windcorp.progressia.client.comms.controls.InputBasedControls;
|
||||
import ru.windcorp.progressia.client.graphics.Layer;
|
||||
import ru.windcorp.progressia.client.graphics.backend.FaceCulling;
|
||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||
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;
|
||||
@ -41,9 +41,11 @@ import ru.windcorp.progressia.common.Units;
|
||||
import ru.windcorp.progressia.common.collision.Collideable;
|
||||
import ru.windcorp.progressia.common.collision.colliders.Collider;
|
||||
import ru.windcorp.progressia.common.util.FloatMathUtil;
|
||||
import ru.windcorp.progressia.common.util.Vectors;
|
||||
import ru.windcorp.progressia.common.world.GravityModel;
|
||||
import ru.windcorp.progressia.common.world.entity.EntityData;
|
||||
import ru.windcorp.progressia.test.CollisionModelRenderer;
|
||||
import ru.windcorp.progressia.test.TestPlayerControls;
|
||||
import ru.windcorp.progressia.test.controls.TestPlayerControls;
|
||||
|
||||
public class LayerWorld extends Layer {
|
||||
|
||||
@ -57,6 +59,8 @@ public class LayerWorld extends Layer {
|
||||
super("World");
|
||||
this.client = client;
|
||||
this.inputBasedControls = new InputBasedControls(client);
|
||||
|
||||
setCursorPolicy(CursorPolicy.FORBID);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -72,6 +76,8 @@ public class LayerWorld extends Layer {
|
||||
|
||||
@Override
|
||||
protected void doRender() {
|
||||
client.getComms().processPackets();
|
||||
|
||||
Camera camera = client.getCamera();
|
||||
if (camera.hasAnchor()) {
|
||||
renderWorld();
|
||||
@ -197,27 +203,34 @@ public class LayerWorld extends Layer {
|
||||
entity.getVelocity().mul((float) Math.exp(-FRICTION_COEFF / entity.getCollisionMass() * tickLength));
|
||||
}
|
||||
|
||||
private static final float MC_g = Units.get("32 m/s^2");
|
||||
private static final float IRL_g = Units.get("9.8 m/s^2");
|
||||
|
||||
private void tmp_applyGravity(EntityData entity, float tickLength) {
|
||||
GravityModel gm = ClientState.getInstance().getWorld().getData().getGravityModel();
|
||||
|
||||
Vec3 upVector = Vectors.grab3();
|
||||
gm.getUp(entity.getPosition(), upVector);
|
||||
entity.changeUpVector(upVector);
|
||||
Vectors.release(upVector);
|
||||
|
||||
if (ClientState.getInstance().getLocalPlayer().getEntity() == entity && tmp_testControls.isFlying()) {
|
||||
return;
|
||||
}
|
||||
if (entity.getId().equals("Test:NoclipCamera")) {
|
||||
return;
|
||||
}
|
||||
|
||||
final float gravitationalAcceleration = tmp_testControls.useMinecraftGravity() ? MC_g : IRL_g;
|
||||
entity.getVelocity().add(0, 0, -gravitationalAcceleration * tickLength);
|
||||
Vec3 gravitationalAcceleration = Vectors.grab3();
|
||||
gm.getGravity(entity.getPosition(), gravitationalAcceleration);
|
||||
|
||||
gravitationalAcceleration.mul(tickLength);
|
||||
entity.getVelocity().add(gravitationalAcceleration);
|
||||
|
||||
Vectors.release(gravitationalAcceleration);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleInput(Input input) {
|
||||
if (input.isConsumed())
|
||||
return;
|
||||
|
||||
tmp_testControls.handleInput(input);
|
||||
|
||||
if (!input.isConsumed()) {
|
||||
inputBasedControls.handleInput(input);
|
||||
public void handleInput(InputEvent event) {
|
||||
if (!event.isConsumed()) {
|
||||
inputBasedControls.handleInput(event);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,17 +18,22 @@
|
||||
|
||||
package ru.windcorp.progressia.client.graphics.world;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
|
||||
import ru.windcorp.progressia.client.Client;
|
||||
import ru.windcorp.progressia.client.events.ClientEvent;
|
||||
import ru.windcorp.progressia.client.events.NewLocalEntityEvent;
|
||||
import ru.windcorp.progressia.client.world.WorldRender;
|
||||
import ru.windcorp.progressia.client.world.entity.EntityRenderable;
|
||||
import ru.windcorp.progressia.common.world.entity.EntityData;
|
||||
import ru.windcorp.progressia.common.world.entity.EntityDataPlayer;
|
||||
|
||||
public class LocalPlayer {
|
||||
|
||||
private final Client client;
|
||||
|
||||
private long entityId = EntityData.NULL_ENTITY_ID;
|
||||
private EntityData lastKnownEntity = null;
|
||||
private EntityDataPlayer lastKnownEntity = null;
|
||||
|
||||
private final Selection selection = new Selection();
|
||||
|
||||
@ -59,19 +64,32 @@ public class LocalPlayer {
|
||||
return getEntity() != null;
|
||||
}
|
||||
|
||||
public EntityData getEntity() {
|
||||
public EntityDataPlayer getEntity() {
|
||||
if (!hasEntityId()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
EntityData entity = getClient().getWorld().getData().getEntity(getEntityId());
|
||||
EntityDataPlayer playerEntity;
|
||||
|
||||
if (entity != lastKnownEntity) {
|
||||
getClient().onLocalPlayerEntityChanged(entity, lastKnownEntity);
|
||||
this.lastKnownEntity = entity;
|
||||
if (entity == null || entity instanceof EntityDataPlayer) {
|
||||
playerEntity = (EntityDataPlayer) entity;
|
||||
} else {
|
||||
LogManager.getLogger().warn(
|
||||
"Entity ID of local player is {}, but the entity is not an EntityDataPlayer: {}",
|
||||
EntityData.formatEntityId(getEntityId()),
|
||||
entity
|
||||
);
|
||||
playerEntity = null;
|
||||
}
|
||||
|
||||
return entity;
|
||||
if (playerEntity != lastKnownEntity) {
|
||||
ClientEvent event = new NewLocalEntityEvent.Immutable(getClient(), playerEntity, lastKnownEntity);
|
||||
this.lastKnownEntity = playerEntity;
|
||||
getClient().postEvent(event);
|
||||
}
|
||||
|
||||
return playerEntity;
|
||||
}
|
||||
|
||||
public Selection getSelection() {
|
||||
|
@ -23,13 +23,13 @@ import glm.vec._3.Vec3;
|
||||
import glm.vec._3.i.Vec3i;
|
||||
import ru.windcorp.progressia.client.world.WorldRender;
|
||||
import ru.windcorp.progressia.common.world.BlockRay;
|
||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
||||
import ru.windcorp.progressia.common.world.entity.EntityData;
|
||||
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
||||
|
||||
public class Selection {
|
||||
|
||||
private final Vec3i block = new Vec3i();
|
||||
private BlockFace surface = null;
|
||||
private AbsFace surface = null;
|
||||
private final Vec2 pointOnSurface = new Vec2(0.5f, 0.5f);
|
||||
private final Vec3 point = new Vec3();
|
||||
|
||||
@ -38,10 +38,9 @@ public class Selection {
|
||||
private BlockRay ray = new BlockRay();
|
||||
|
||||
public void update(WorldRender world, EntityData player) {
|
||||
Vec3 direction = new Vec3();
|
||||
Vec3 start = new Vec3();
|
||||
Vec3 direction = player.getLookingAt();
|
||||
|
||||
player.getLookingAtVector(direction);
|
||||
world.getEntityRenderable(player).getViewPoint(start);
|
||||
start.add(player.getPosition());
|
||||
|
||||
@ -71,7 +70,7 @@ public class Selection {
|
||||
return exists ? point : null;
|
||||
}
|
||||
|
||||
public BlockFace getSurface() {
|
||||
public AbsFace getSurface() {
|
||||
return exists ? surface : null;
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ import glm.vec._4.Vec4;
|
||||
import ru.windcorp.progressia.client.graphics.backend.VertexBufferObject;
|
||||
import ru.windcorp.progressia.client.graphics.backend.shaders.attributes.*;
|
||||
import ru.windcorp.progressia.client.graphics.backend.shaders.uniforms.*;
|
||||
import ru.windcorp.progressia.client.graphics.model.Face;
|
||||
import ru.windcorp.progressia.client.graphics.model.ShapePart;
|
||||
import ru.windcorp.progressia.client.graphics.model.Shape;
|
||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
|
||||
@ -138,12 +138,12 @@ public class WorldRenderProgram extends ShapeRenderProgram {
|
||||
public void preprocess(Shape shape) {
|
||||
super.preprocess(shape);
|
||||
|
||||
for (Face face : shape.getFaces()) {
|
||||
for (ShapePart face : shape.getParts()) {
|
||||
computeNormals(face);
|
||||
}
|
||||
}
|
||||
|
||||
private void computeNormals(Face face) {
|
||||
private void computeNormals(ShapePart face) {
|
||||
Vec3 a = Vectors.grab3();
|
||||
Vec3 b = Vectors.grab3();
|
||||
Vec3 c = Vectors.grab3();
|
||||
@ -183,7 +183,7 @@ public class WorldRenderProgram extends ShapeRenderProgram {
|
||||
normal.normalize();
|
||||
}
|
||||
|
||||
private void loadVertexPosition(Face face, int index, Vec3 result) {
|
||||
private void loadVertexPosition(ShapePart face, int index, Vec3 result) {
|
||||
ByteBuffer vertices = face.getVertices();
|
||||
int offset = vertices.position() + index * getBytesPerVertex();
|
||||
|
||||
@ -194,12 +194,16 @@ public class WorldRenderProgram extends ShapeRenderProgram {
|
||||
);
|
||||
}
|
||||
|
||||
private void saveVertexNormal(Face face, int index, Vec3 normal) {
|
||||
private void saveVertexNormal(ShapePart face, int index, Vec3 normal) {
|
||||
ByteBuffer vertices = face.getVertices();
|
||||
int offset = vertices.position() + index * getBytesPerVertex() + (3 * Float.BYTES +
|
||||
4 * Float.BYTES +
|
||||
2 * Float.BYTES);
|
||||
|
||||
if (!Float.isNaN(vertices.getFloat(offset + 0 * Float.BYTES))) {
|
||||
return; // normals are forced
|
||||
}
|
||||
|
||||
vertices.putFloat(offset + 0 * Float.BYTES, normal.x);
|
||||
vertices.putFloat(offset + 1 * Float.BYTES, normal.y);
|
||||
vertices.putFloat(offset + 2 * Float.BYTES, normal.z);
|
||||
@ -212,7 +216,7 @@ public class WorldRenderProgram extends ShapeRenderProgram {
|
||||
return new WRPVertexBuilder();
|
||||
}
|
||||
|
||||
private static class WRPVertexBuilder implements VertexBuilder {
|
||||
public static class WRPVertexBuilder implements VertexBuilder {
|
||||
// TODO Throw VertexBuilders out the window and rewrite completely.
|
||||
// I want to _extend_ VBs, not re-implement them for children of SRP
|
||||
|
||||
@ -220,11 +224,17 @@ public class WorldRenderProgram extends ShapeRenderProgram {
|
||||
final Vec3 position;
|
||||
final Vec4 colorMultiplier;
|
||||
final Vec2 textureCoords;
|
||||
final Vec3 normals;
|
||||
|
||||
Vertex(Vec3 position, Vec4 colorMultiplier, Vec2 textureCoords) {
|
||||
this(position, colorMultiplier, textureCoords, new Vec3(Float.NaN, Float.NaN, Float.NaN));
|
||||
}
|
||||
|
||||
Vertex(Vec3 position, Vec4 colorMultiplier, Vec2 textureCoords, Vec3 normals) {
|
||||
this.position = position;
|
||||
this.colorMultiplier = colorMultiplier;
|
||||
this.textureCoords = textureCoords;
|
||||
this.normals = normals;
|
||||
}
|
||||
}
|
||||
|
||||
@ -292,6 +302,24 @@ public class WorldRenderProgram extends ShapeRenderProgram {
|
||||
return this;
|
||||
}
|
||||
|
||||
public VertexBuilder addVertex(
|
||||
Vec3 position,
|
||||
Vec4 colorMultiplier,
|
||||
Vec2 textureCoords,
|
||||
Vec3 normals
|
||||
) {
|
||||
vertices.add(
|
||||
new Vertex(
|
||||
new Vec3(position),
|
||||
new Vec4(colorMultiplier),
|
||||
new Vec2(textureCoords),
|
||||
new Vec3(normals)
|
||||
)
|
||||
);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer assemble() {
|
||||
ByteBuffer result = BufferUtils.createByteBuffer(
|
||||
@ -309,9 +337,9 @@ public class WorldRenderProgram extends ShapeRenderProgram {
|
||||
.putFloat(v.colorMultiplier.w)
|
||||
.putFloat(v.textureCoords.x)
|
||||
.putFloat(v.textureCoords.y)
|
||||
.putFloat(Float.NaN)
|
||||
.putFloat(Float.NaN)
|
||||
.putFloat(Float.NaN);
|
||||
.putFloat(v.normals.x)
|
||||
.putFloat(v.normals.y)
|
||||
.putFloat(v.normals.z);
|
||||
}
|
||||
|
||||
result.flip();
|
||||
|
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.graphics.world.hud;
|
||||
|
||||
import glm.vec._3.Vec3;
|
||||
import glm.vec._4.Vec4;
|
||||
import ru.windcorp.jputil.functions.FloatSupplier;
|
||||
import ru.windcorp.progressia.client.graphics.Colors;
|
||||
import ru.windcorp.progressia.client.graphics.backend.Usage;
|
||||
import ru.windcorp.progressia.client.graphics.flat.FlatRenderProgram;
|
||||
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||
import ru.windcorp.progressia.client.graphics.model.Shape;
|
||||
import ru.windcorp.progressia.client.graphics.model.ShapeParts;
|
||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
||||
|
||||
public class Bar extends Component {
|
||||
|
||||
private static final int THICKNESS = 5;
|
||||
|
||||
private final boolean isVertical;
|
||||
|
||||
private final FloatSupplier value;
|
||||
private final FloatSupplier maxValue;
|
||||
|
||||
private final Vec4 color;
|
||||
private final Vec4 backgroundColor;
|
||||
|
||||
private static Renderable unitSquare = null;
|
||||
|
||||
public Bar(String name, boolean isVertical, Vec4 color, FloatSupplier value, FloatSupplier maxValue) {
|
||||
super(name);
|
||||
this.isVertical = isVertical;
|
||||
this.value = value;
|
||||
this.maxValue = maxValue;
|
||||
|
||||
this.color = color;
|
||||
this.backgroundColor = Colors.mix(color, Colors.WHITE, 0.75f, null);
|
||||
|
||||
if (unitSquare == null) {
|
||||
unitSquare = new Shape(
|
||||
Usage.STATIC,
|
||||
FlatRenderProgram.getDefault(),
|
||||
ShapeParts.createRectangle(
|
||||
FlatRenderProgram.getDefault(),
|
||||
null,
|
||||
Colors.WHITE,
|
||||
new Vec3(0, 0, 0),
|
||||
new Vec3(1, 0, 0),
|
||||
new Vec3(0, 1, 0),
|
||||
false
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
setPreferredSize(THICKNESS, THICKNESS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assembleSelf(RenderTarget target) {
|
||||
target.addCustomRenderer(this::renderSelf);
|
||||
}
|
||||
|
||||
private void renderSelf(ShapeRenderHelper renderer) {
|
||||
renderer.pushTransform()
|
||||
.translate(getX(), getY(), 0)
|
||||
.scale(getWidth(), getHeight(), 1);
|
||||
|
||||
float length = value.getAsFloat() / maxValue.getAsFloat();
|
||||
if (length < 0) {
|
||||
length = 0;
|
||||
} else if (length > 1) {
|
||||
length = 1;
|
||||
}
|
||||
|
||||
// TODO why is the order reverse????
|
||||
renderRectangle(renderer, color, length);
|
||||
renderRectangle(renderer, backgroundColor, 1);
|
||||
|
||||
renderer.popTransform();
|
||||
}
|
||||
|
||||
private void renderRectangle(ShapeRenderHelper renderer, Vec4 color, float length) {
|
||||
renderer.pushColorMultiplier().mul(color);
|
||||
if (length != 1) {
|
||||
renderer.pushTransform().scale(isVertical ? 1 : length, isVertical ? length : 1, 1);
|
||||
}
|
||||
|
||||
unitSquare.render(renderer);
|
||||
|
||||
if (length != 1) {
|
||||
renderer.popTransform();
|
||||
}
|
||||
renderer.popColorMultiplier();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.graphics.world.hud;
|
||||
|
||||
import glm.vec._2.i.Vec2i;
|
||||
import ru.windcorp.progressia.client.graphics.ExponentAnimation;
|
||||
import ru.windcorp.progressia.client.graphics.backend.InputTracker;
|
||||
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
||||
import ru.windcorp.progressia.client.world.entity.SpeciesRender;
|
||||
import ru.windcorp.progressia.client.world.entity.SpeciesRenderRegistry;
|
||||
import ru.windcorp.progressia.common.world.entity.EntityDataPlayer;
|
||||
import ru.windcorp.progressia.common.world.entity.SpeciesData.Hand;
|
||||
import ru.windcorp.progressia.common.world.item.inventory.ItemContainerHand;
|
||||
|
||||
public class CursorHUD extends Component {
|
||||
|
||||
private class CursorBoundSlot {
|
||||
|
||||
private final SlotComponent component;
|
||||
private final Renderable renderable;
|
||||
|
||||
/**
|
||||
* 0 is not selected, 1 is selected
|
||||
*/
|
||||
private final ExponentAnimation selection = new ExponentAnimation(10, 0);
|
||||
private final double angle;
|
||||
|
||||
public CursorBoundSlot(SlotComponent component, double angle) {
|
||||
this.component = component;
|
||||
this.angle = angle;
|
||||
|
||||
Vec2i size = component.getPreferredSize();
|
||||
component.setBounds(-size.x / 2, -size.y / 2, size);
|
||||
|
||||
this.renderable = component.assembleToRenderable();
|
||||
|
||||
if (player.getHandCount() == 1) {
|
||||
// Disable opening animation hint
|
||||
selection.setValue(1);
|
||||
}
|
||||
}
|
||||
|
||||
public void render(ShapeRenderHelper renderer) {
|
||||
|
||||
float target = player.getSelectedHand() == component.getSlot().getContainer() ? 1 : 0;
|
||||
float sel = selection.updateForFrame(target);
|
||||
|
||||
float distance = CursorHUD.this.distance * (1 - sel);
|
||||
float x = (float) Math.cos(angle) * distance;
|
||||
float y = (float) Math.sin(angle) * distance;
|
||||
float scale = 0.5f + 0.5f * sel;
|
||||
|
||||
renderer.pushTransform().translate(x, y, 0).scale(scale);
|
||||
|
||||
boolean popColor = false;
|
||||
if (sel > 0.5f && component.getSlot().isEmpty()) {
|
||||
renderer.pushColorMultiplier().mul(1, 1, 1, 1 - 2 * (sel - 0.5f));
|
||||
popColor = true;
|
||||
}
|
||||
|
||||
renderable.render(renderer);
|
||||
|
||||
if (popColor) {
|
||||
renderer.popColorMultiplier();
|
||||
}
|
||||
|
||||
renderer.popTransform();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final EntityDataPlayer player;
|
||||
|
||||
private final float distance = 50;
|
||||
private final double startAngle = Math.PI / 4;
|
||||
private final double endAngle = -3 * Math.PI / 4;
|
||||
|
||||
private final CursorBoundSlot[] slots;
|
||||
|
||||
public CursorHUD(String name, EntityDataPlayer player) {
|
||||
super(name);
|
||||
this.player = player;
|
||||
|
||||
this.slots = new CursorBoundSlot[player.getHandCount()];
|
||||
|
||||
// This produces NaN when there is only one hand, but then it is unused
|
||||
double angleStep = (endAngle - startAngle) / (slots.length - 1);
|
||||
|
||||
double angle = startAngle;
|
||||
for (int i = 0; i < slots.length; ++i) {
|
||||
|
||||
SpeciesRender speciesRender = SpeciesRenderRegistry.getInstance().get(player);
|
||||
|
||||
ItemContainerHand container = player.getHand(i);
|
||||
Hand hand = container.getHand();
|
||||
|
||||
SlotComponent component = new SlotComponent(name + ".Hand" + hand.getName(), container, 0)
|
||||
.setBackground(speciesRender.getHandBackground(hand));
|
||||
|
||||
addChild(component);
|
||||
|
||||
slots[i] = new CursorBoundSlot(component, angle);
|
||||
|
||||
angle += angleStep;
|
||||
|
||||
}
|
||||
|
||||
setLayout(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assembleChildren(RenderTarget target) {
|
||||
|
||||
target.addCustomRenderer(renderer -> {
|
||||
|
||||
renderer.pushTransform().translate(
|
||||
(float) InputTracker.getCursorX(),
|
||||
(float) InputTracker.getCursorY(),
|
||||
0
|
||||
);
|
||||
|
||||
for (CursorBoundSlot slot : slots) {
|
||||
slot.render(renderer);
|
||||
}
|
||||
|
||||
renderer.popTransform();
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.graphics.world.hud;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
|
||||
import ru.windcorp.progressia.client.Client;
|
||||
import ru.windcorp.progressia.client.events.NewLocalEntityEvent;
|
||||
import ru.windcorp.progressia.client.graphics.GUI;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||
import ru.windcorp.progressia.client.world.item.inventory.InventoryComponent;
|
||||
import ru.windcorp.progressia.client.world.item.inventory.InventoryRender;
|
||||
import ru.windcorp.progressia.client.world.item.inventory.InventoryRenderRegistry;
|
||||
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
||||
import ru.windcorp.progressia.common.world.item.inventory.Inventory;
|
||||
import ru.windcorp.progressia.common.world.item.inventory.event.InventoryClosingEvent;
|
||||
import ru.windcorp.progressia.common.world.item.inventory.event.InventoryOpenedEvent;
|
||||
|
||||
public class HUDManager implements HUDWorkspace {
|
||||
|
||||
private final Client client;
|
||||
private final LayerHUD layer;
|
||||
|
||||
public HUDManager(Client client) {
|
||||
this.client = client;
|
||||
this.layer = new LayerHUD(this);
|
||||
client.subscribe(new Object() {
|
||||
@Subscribe
|
||||
public void onLocalEntityChanged(NewLocalEntityEvent e) {
|
||||
if (e.getNewEntity() != null) {
|
||||
e.getNewEntity().subscribe(HUDManager.this);
|
||||
}
|
||||
if (e.getPreviousEntity() != null) {
|
||||
e.getPreviousEntity().unsubscribe(HUDManager.this);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void install() {
|
||||
GUI.addTopLayer(layer);
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
GUI.removeLayer(layer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openInventory(InventoryComponent component) {
|
||||
InventoryWindow window = new InventoryWindow("Window", component, this);
|
||||
layer.getWindowManager().addWindow(window);
|
||||
}
|
||||
|
||||
public void closeEverything() {
|
||||
System.err.println("closeEverything NYI");
|
||||
}
|
||||
|
||||
public boolean isHidden() {
|
||||
return layer.isHidden();
|
||||
}
|
||||
|
||||
public void setHidden(boolean hide) {
|
||||
layer.setHidden(hide);
|
||||
}
|
||||
|
||||
public boolean isInventoryShown() {
|
||||
return layer.isInventoryShown();
|
||||
}
|
||||
|
||||
public void setInventoryShown(boolean showInventory) {
|
||||
layer.setInventoryShown(showInventory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Client getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
private void onInventoryOpened(InventoryOpenedEvent event) {
|
||||
Inventory inventory = event.getInventory();
|
||||
InventoryRender render = InventoryRenderRegistry.getInstance().get(inventory.getId());
|
||||
|
||||
if (render == null) {
|
||||
throw CrashReports.report(null, "InventoryRender not found for ID %s", inventory.getId());
|
||||
}
|
||||
|
||||
try {
|
||||
InventoryComponent component = render.createComponent(inventory, this);
|
||||
openInventory(component);
|
||||
} catch (Exception e) {
|
||||
throw CrashReports.report(e, "Could not open inventory %s", inventory.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
private void onInventoryClosing(InventoryClosingEvent event) {
|
||||
Inventory inventory = event.getInventory();
|
||||
|
||||
for (Component component : layer.getWindowManager().getChildren()) {
|
||||
if (component instanceof InventoryWindow) {
|
||||
InventoryWindow window = (InventoryWindow) component;
|
||||
if (window.getContent().getInventory() == inventory) {
|
||||
layer.getWindowManager().closeWindow(window);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.graphics.world.hud;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.texture.Atlases;
|
||||
import ru.windcorp.progressia.client.graphics.texture.SimpleTexture;
|
||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||
import ru.windcorp.progressia.client.graphics.texture.Atlases.AtlasGroup;
|
||||
import ru.windcorp.progressia.common.resource.ResourceManager;
|
||||
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
||||
|
||||
public class HUDTextures {
|
||||
|
||||
private static final AtlasGroup HUD_ATLAS_GROUP = new AtlasGroup("HUD", 1 << 12);
|
||||
|
||||
private static ItemAmountTypeface itemAmountTypeface = null;
|
||||
|
||||
public static Texture getHUDTexture(String name) {
|
||||
return new SimpleTexture(
|
||||
Atlases.getSprite(
|
||||
ResourceManager.getTextureResource("gui/" + name),
|
||||
HUD_ATLAS_GROUP
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static AtlasGroup getHUDAtlasGroup() {
|
||||
return HUD_ATLAS_GROUP;
|
||||
}
|
||||
|
||||
public static ItemAmountTypeface getItemAmountTypeface() {
|
||||
return itemAmountTypeface;
|
||||
}
|
||||
|
||||
public static void loadItemAmountTypeface() {
|
||||
try {
|
||||
itemAmountTypeface = new ItemAmountTypeface();
|
||||
} catch (IOException e) {
|
||||
throw CrashReports.report(e, "Could not load item amount typeface");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.graphics.world.hud;
|
||||
|
||||
import ru.windcorp.progressia.client.Client;
|
||||
import ru.windcorp.progressia.client.graphics.world.LocalPlayer;
|
||||
import ru.windcorp.progressia.client.world.item.inventory.InventoryComponent;
|
||||
import ru.windcorp.progressia.common.world.entity.EntityDataPlayer;
|
||||
import ru.windcorp.progressia.common.world.item.inventory.ItemContainerHand;
|
||||
|
||||
public interface HUDWorkspace {
|
||||
|
||||
Client getClient();
|
||||
|
||||
default LocalPlayer getPlayer() {
|
||||
return getClient().getLocalPlayer();
|
||||
}
|
||||
|
||||
default EntityDataPlayer getPlayerEntity() {
|
||||
return getClient().getLocalPlayer().getEntity();
|
||||
}
|
||||
|
||||
default ItemContainerHand getHand() {
|
||||
return getClient().getLocalPlayer().getEntity().getSelectedHand();
|
||||
}
|
||||
|
||||
void openInventory(InventoryComponent component);
|
||||
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.graphics.world.hud;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import glm.vec._2.i.Vec2i;
|
||||
import ru.windcorp.progressia.client.graphics.ExponentAnimation;
|
||||
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Group;
|
||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
|
||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutBorderHorizontal;
|
||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutHorizontal;
|
||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||
import ru.windcorp.progressia.client.world.entity.SpeciesRender;
|
||||
import ru.windcorp.progressia.client.world.entity.SpeciesRenderRegistry;
|
||||
import ru.windcorp.progressia.common.world.entity.EntityDataPlayer;
|
||||
import ru.windcorp.progressia.common.world.entity.SpeciesData.Hand;
|
||||
|
||||
public class HandsHUD extends Component {
|
||||
|
||||
public class ScaledSlotComponent extends Component {
|
||||
|
||||
private final SlotComponent slotComponent;
|
||||
private final ExponentAnimation selected = new ExponentAnimation(10, 1);
|
||||
|
||||
public ScaledSlotComponent(SlotComponent component) {
|
||||
super(component.getName() + ".Scaled");
|
||||
this.slotComponent = component;
|
||||
addChild(component);
|
||||
|
||||
Vec2i size = slotComponent.getPreferredSize();
|
||||
setPreferredSize(size.x * 2, size.y * 2);
|
||||
slotComponent.setBounds(-size.x / 2, 0, size);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assembleChildren(RenderTarget target) {
|
||||
Renderable renderable = slotComponent.assembleToRenderable();
|
||||
|
||||
target.addCustomRenderer(renderer -> {
|
||||
float scale = manager.getHand() == slotComponent.getSlot().getContainer() ? 2 : 1;
|
||||
renderer.pushTransform()
|
||||
.translate(getX() + getWidth() / 2, getY(), 0)
|
||||
.scale(selected.updateForFrame(scale));
|
||||
|
||||
renderable.render(renderer);
|
||||
|
||||
renderer.popTransform();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public enum Side {
|
||||
LEFT("Left", LayoutBorderHorizontal.LEFT, 0.0),
|
||||
RIGHT("Right", LayoutBorderHorizontal.RIGHT, 1.0),
|
||||
CENTER("Center", LayoutBorderHorizontal.CENTER, 0.5);
|
||||
|
||||
private final String ccName;
|
||||
private final Object lbhHint;
|
||||
private final double align;
|
||||
|
||||
private Side(String ccName, Object lbhHint, double align) {
|
||||
this.ccName = ccName;
|
||||
this.lbhHint = lbhHint;
|
||||
this.align = align;
|
||||
}
|
||||
}
|
||||
|
||||
private final HUDManager manager;
|
||||
|
||||
public HandsHUD(String name, HUDManager manager) {
|
||||
super(name);
|
||||
this.manager = manager;
|
||||
|
||||
EntityDataPlayer entity = manager.getPlayerEntity();
|
||||
String speciesId = entity.getSpecies().getId();
|
||||
SpeciesRender speciesRender = SpeciesRenderRegistry.getInstance().get(speciesId);
|
||||
|
||||
Map<Side, Component> containers = Maps.toMap(
|
||||
Arrays.asList(Side.values()),
|
||||
side -> new Group(name + "." + side.ccName, new LayoutHorizontal(15))
|
||||
);
|
||||
|
||||
for (int i = 0; i < entity.getHandCount(); ++i) {
|
||||
|
||||
Hand hand = entity.getSpecies().getHands().get(i);
|
||||
|
||||
SlotComponent display = new SlotComponent(name + "." + hand.getName(), entity.getHand(i), 0)
|
||||
.setBackground(speciesRender.getHandBackground(hand), this::shouldRenderHandPlaceholder)
|
||||
.setScale(2);
|
||||
|
||||
Component scaledDisplay = new ScaledSlotComponent(display);
|
||||
|
||||
containers.get(speciesRender.getHandSide(hand)).addChild(scaledDisplay);
|
||||
|
||||
}
|
||||
|
||||
setLayout(new LayoutBorderHorizontal());
|
||||
|
||||
containers.forEach((side, comp) -> {
|
||||
addChild(
|
||||
new Group(name + "." + side.ccName + ".Aligner", new LayoutAlign(side.align, 0.5, 0), comp)
|
||||
.setLayoutHint(side.lbhHint)
|
||||
);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private boolean shouldRenderHandPlaceholder() {
|
||||
return manager.isInventoryShown();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.graphics.world.hud;
|
||||
|
||||
import glm.vec._2.i.Vec2i;
|
||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Button;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Label;
|
||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutFill;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
|
||||
import ru.windcorp.progressia.client.graphics.input.WheelScrollEvent;
|
||||
import ru.windcorp.progressia.common.Units;
|
||||
import ru.windcorp.progressia.common.world.item.ItemDataContainer;
|
||||
import ru.windcorp.progressia.common.world.item.inventory.ItemContainer;
|
||||
import ru.windcorp.progressia.common.world.item.inventory.ItemSlot;
|
||||
import ru.windcorp.progressia.common.world.item.inventory.Items;
|
||||
|
||||
public class InteractiveSlotComponent extends Button {
|
||||
|
||||
private static final double MIN_PICK_ALL_DELAY = Units.get("0.5 s");
|
||||
|
||||
private double lastMainAction = Double.NEGATIVE_INFINITY;
|
||||
|
||||
private final SlotComponent slotComponent;
|
||||
private final HUDWorkspace workspace;
|
||||
|
||||
public InteractiveSlotComponent(String name, ItemContainer container, int index, HUDWorkspace workspace) {
|
||||
this(name, new SlotComponent(name, container, index), workspace);
|
||||
}
|
||||
|
||||
public InteractiveSlotComponent(SlotComponent component, HUDWorkspace workspace) {
|
||||
this(component.getName() + ".Interactive", component, workspace);
|
||||
}
|
||||
|
||||
public InteractiveSlotComponent(String name, SlotComponent component, HUDWorkspace workspace) {
|
||||
super(name, (Label) null);
|
||||
this.slotComponent = component;
|
||||
this.workspace = workspace;
|
||||
|
||||
Vec2i size = slotComponent.getPreferredSize().add(2 * BORDER);
|
||||
setPreferredSize(size);
|
||||
|
||||
addChild(this.slotComponent);
|
||||
setLayout(new LayoutFill(MARGIN));
|
||||
|
||||
addListeners();
|
||||
}
|
||||
|
||||
private void addListeners() {
|
||||
addAction(button -> onMainAction());
|
||||
|
||||
addKeyListener(KeyMatcher.RMB, this::onAltAction);
|
||||
|
||||
addInputListener(WheelScrollEvent.class, event -> {
|
||||
if (event.hasVerticalMovement()) {
|
||||
onSingleMoveAction(event.isDown());
|
||||
event.consume();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onMainAction() {
|
||||
ItemSlot handSlot = workspace.getHand().slot();
|
||||
ItemSlot invSlot = getSlot();
|
||||
if (invSlot == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean success = false;
|
||||
|
||||
double now = GraphicsInterface.getTime();
|
||||
if (now - lastMainAction < MIN_PICK_ALL_DELAY) {
|
||||
lastMainAction = Double.NEGATIVE_INFINITY;
|
||||
pickAll(handSlot);
|
||||
success = true;
|
||||
} else {
|
||||
lastMainAction = now;
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
success = Items.pour(handSlot, invSlot) != 0;
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
success = Items.swap(handSlot, invSlot);
|
||||
}
|
||||
|
||||
if (!success && handSlot.isEmpty()) {
|
||||
success = Items.pour(invSlot, handSlot) != 0;
|
||||
}
|
||||
|
||||
if (success) {
|
||||
requestReassembly();
|
||||
}
|
||||
}
|
||||
|
||||
private void pickAll(ItemSlot handSlot) {
|
||||
int maxIndex = getSlot().getContainer().getMaxIndex();
|
||||
for (int index = 0; index < maxIndex; ++index) {
|
||||
Items.pour(new ItemSlot(getSlot().getContainer(), index), handSlot);
|
||||
if (handSlot.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onAltAction(KeyEvent e) {
|
||||
ItemSlot handSlot = workspace.getHand().slot();
|
||||
ItemSlot invSlot = getSlot();
|
||||
if (invSlot == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean success = false;
|
||||
|
||||
if (handSlot.isEmpty()) {
|
||||
success = tryToOpen(invSlot);
|
||||
}
|
||||
|
||||
if (!success && handSlot.isEmpty()) {
|
||||
success = Items.pour(invSlot, handSlot, invSlot.getCount() / 2) != 0;
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
success = Items.pour(handSlot, invSlot, 1) != 0;
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
success = Items.swap(handSlot, invSlot);
|
||||
}
|
||||
|
||||
if (success) {
|
||||
requestReassembly();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean tryToOpen(ItemSlot invSlot) {
|
||||
if (invSlot.getCount() != 1) {
|
||||
return false;
|
||||
}
|
||||
if (!(invSlot.getItem() instanceof ItemDataContainer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ItemDataContainer item = (ItemDataContainer) invSlot.getItem();
|
||||
return item.open(workspace.getPlayerEntity()) != null;
|
||||
}
|
||||
|
||||
private void onSingleMoveAction(boolean fromHand) {
|
||||
ItemSlot handSlot = workspace.getHand().slot();
|
||||
ItemSlot invSlot = getSlot();
|
||||
|
||||
ItemSlot from = fromHand ? handSlot : invSlot;
|
||||
ItemSlot into = fromHand ? invSlot : handSlot;
|
||||
|
||||
if (Items.pour(from, into, 1) != 0) {
|
||||
requestReassembly();
|
||||
}
|
||||
}
|
||||
|
||||
public ItemSlot getSlot() {
|
||||
return slotComponent.getSlot();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.graphics.world.hud;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Group;
|
||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
|
||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutBorderHorizontal;
|
||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
|
||||
import ru.windcorp.progressia.client.world.entity.SpeciesRender;
|
||||
import ru.windcorp.progressia.client.world.entity.SpeciesRenderRegistry;
|
||||
import ru.windcorp.progressia.common.world.entity.EntityDataPlayer;
|
||||
import ru.windcorp.progressia.common.world.entity.SpeciesData.EquipmentSlot;
|
||||
|
||||
public class InventoryHUD extends Component {
|
||||
|
||||
public enum Side {
|
||||
LEFT("Left", LayoutBorderHorizontal.LEFT),
|
||||
RIGHT("Right", LayoutBorderHorizontal.RIGHT);
|
||||
|
||||
private final String ccName;
|
||||
private final Object lbhHint;
|
||||
|
||||
private Side(String ccName, Object lbhHint) {
|
||||
this.ccName = ccName;
|
||||
this.lbhHint = lbhHint;
|
||||
}
|
||||
}
|
||||
|
||||
public InventoryHUD(String name, HUDWorkspace workspace) {
|
||||
super(name);
|
||||
setLayout(new LayoutBorderHorizontal());
|
||||
|
||||
EntityDataPlayer entity = workspace.getPlayerEntity();
|
||||
String speciesId = entity.getSpecies().getId();
|
||||
SpeciesRender speciesRender = SpeciesRenderRegistry.getInstance().get(speciesId);
|
||||
|
||||
Map<Side, Component> containers = Maps.toMap(
|
||||
Arrays.asList(Side.values()),
|
||||
side -> new Group(name + "." + side.ccName, new LayoutVertical(15))
|
||||
);
|
||||
|
||||
for (int i = 0; i < entity.getEquipmentCount(); ++i) {
|
||||
|
||||
EquipmentSlot slot = entity.getSpecies().getEquipmentSlots().get(i);
|
||||
|
||||
SlotComponent display = new SlotComponent(name + "." + slot.getName(), entity.getEquipmentSlot(i), 0)
|
||||
.setBackground(speciesRender.getEquipmentSlotBackground(slot))
|
||||
.setScale(2);
|
||||
|
||||
InteractiveSlotComponent interactiveDisplay = new InteractiveSlotComponent(
|
||||
display,
|
||||
workspace
|
||||
);
|
||||
|
||||
containers.get(speciesRender.getEquipmentSlotSide(slot)).addChild(interactiveDisplay);
|
||||
|
||||
}
|
||||
|
||||
containers.forEach((side, comp) -> {
|
||||
addChild(
|
||||
new Group(name + "." + side.ccName + ".Aligner", new LayoutAlign(0, 1, 0), comp)
|
||||
.setLayoutHint(side.lbhHint)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.graphics.world.hud;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
|
||||
import glm.vec._2.Vec2;
|
||||
import glm.vec._4.Vec4;
|
||||
import ru.windcorp.progressia.client.graphics.Colors;
|
||||
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
||||
import ru.windcorp.progressia.client.graphics.font.Font;
|
||||
import ru.windcorp.progressia.client.graphics.font.Typeface;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Button;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||
import ru.windcorp.progressia.client.graphics.gui.DragManager;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Group;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Label;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Panel;
|
||||
import ru.windcorp.progressia.client.graphics.gui.event.DragEvent;
|
||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutBorderHorizontal;
|
||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutFill;
|
||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
|
||||
import ru.windcorp.progressia.client.localization.MutableString;
|
||||
import ru.windcorp.progressia.client.localization.MutableStringConcat;
|
||||
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
|
||||
import ru.windcorp.progressia.client.world.item.inventory.InventoryComponent;
|
||||
|
||||
public class InventoryWindow extends Panel {
|
||||
|
||||
private static final String CLOSE_CHAR = "\u2715";
|
||||
private static final String HANDLE_CHAR = "\u2800";
|
||||
private static final Vec4 CLOSE_BUTTON_IDLE = Colors.toVector(0xFFBC1515);
|
||||
private static final Vec4 CLOSE_BUTTON_HOVER = Colors.toVector(0xFFFA6464);
|
||||
private static final Vec4 CLOSE_BUTTON_PRESSED = Colors.BLACK;
|
||||
|
||||
private final InventoryComponent content;
|
||||
private final HUDWorkspace workspace;
|
||||
|
||||
private final Vec2 relativePosition = new Vec2(Float.NaN);
|
||||
|
||||
public InventoryWindow(String name, InventoryComponent component, HUDWorkspace workspace) {
|
||||
super(name, new LayoutVertical(15, 15));
|
||||
this.content = component;
|
||||
this.workspace = workspace;
|
||||
|
||||
Group titleBar = new Group(getName() + ".TitleBar", new LayoutBorderHorizontal());
|
||||
titleBar.addChild(createLabel(component).setLayoutHint(LayoutBorderHorizontal.CENTER));
|
||||
titleBar.addChild(createCloseButton(component).setLayoutHint(LayoutBorderHorizontal.RIGHT));
|
||||
|
||||
new DragManager().install(titleBar);
|
||||
titleBar.addListener(new Object() {
|
||||
@Subscribe
|
||||
public void onWindowDragged(DragEvent e) {
|
||||
Vec2 change = new Vec2((float) e.getCurrentChangeX(), (float) e.getCurrentChangeY());
|
||||
change.div(getParent().getWidth(), getParent().getHeight());
|
||||
relativePosition.add(change);
|
||||
requestReassembly();
|
||||
}
|
||||
});
|
||||
|
||||
addChild(titleBar);
|
||||
|
||||
addChild(component);
|
||||
}
|
||||
|
||||
private Label createLabel(InventoryComponent component) {
|
||||
String translationKey = "Inventory." + component.getInventory().getId() + ".Title";
|
||||
MutableString titleText = new MutableStringConcat(
|
||||
HANDLE_CHAR + " ",
|
||||
new MutableStringLocalized(translationKey)
|
||||
);
|
||||
Font titleFont = new Font().deriveBold().withColor(Colors.BLACK).withAlign(Typeface.ALIGN_LEFT);
|
||||
|
||||
return new Label(getName() + ".Title", titleFont, titleText);
|
||||
}
|
||||
|
||||
private WindowedHUD getManager() {
|
||||
Component parent = getParent();
|
||||
if (parent instanceof WindowedHUD) {
|
||||
return (WindowedHUD) parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the relativePosition
|
||||
*/
|
||||
public Vec2 getRelativePosition() {
|
||||
return relativePosition;
|
||||
}
|
||||
|
||||
private Component createCloseButton(InventoryComponent component) {
|
||||
|
||||
Button button = new Button(getName() + ".CloseButton", CLOSE_CHAR) {
|
||||
|
||||
@Override
|
||||
protected void assembleSelf(RenderTarget target) {
|
||||
|
||||
Vec4 color = CLOSE_BUTTON_IDLE;
|
||||
if (isPressed()) {
|
||||
color = CLOSE_BUTTON_PRESSED;
|
||||
} else if (isHovered()) {
|
||||
color = CLOSE_BUTTON_HOVER;
|
||||
}
|
||||
|
||||
if (hasLabel()) {
|
||||
getLabel().setFont(getLabel().getFont().withColor(color));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
button.addAction(b -> {
|
||||
|
||||
content.getInventory().close(workspace.getPlayerEntity());
|
||||
|
||||
WindowedHUD manager = getManager();
|
||||
if (manager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
manager.closeWindow(this);
|
||||
|
||||
});
|
||||
|
||||
button.setLayout(new LayoutFill());
|
||||
|
||||
int height = button.getLabel().getFont().getHeight(button.getLabel().getCurrentText());
|
||||
button.setPreferredSize(height, height);
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
public InventoryComponent getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.graphics.world.hud;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.flat.FlatRenderProgram;
|
||||
import ru.windcorp.progressia.client.graphics.font.SpriteTypeface;
|
||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
|
||||
import ru.windcorp.progressia.client.graphics.texture.ComplexTexture;
|
||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||
import ru.windcorp.progressia.client.graphics.texture.TextureLoader;
|
||||
import ru.windcorp.progressia.client.graphics.texture.TexturePrimitive;
|
||||
import ru.windcorp.progressia.client.graphics.texture.TextureSettings;
|
||||
import ru.windcorp.progressia.common.resource.ResourceManager;
|
||||
|
||||
public class ItemAmountTypeface extends SpriteTypeface {
|
||||
|
||||
public static final int HEIGHT = 5;
|
||||
public static final int WIDTH = 3;
|
||||
|
||||
private final Texture[] textures = new Texture[10];
|
||||
|
||||
public ItemAmountTypeface() throws IOException {
|
||||
super("ItemAmount", HEIGHT, 1);
|
||||
|
||||
ComplexTexture atlas = new ComplexTexture(new TexturePrimitive(
|
||||
TextureLoader.loadPixels(
|
||||
ResourceManager.getTextureResource("gui/ItemAmountTypeface"),
|
||||
new TextureSettings(false)
|
||||
).getData()
|
||||
), 30, 5);
|
||||
|
||||
for (int i = 0; i <= 9; ++i) {
|
||||
textures[i] = atlas.get(i * WIDTH, 0, WIDTH, HEIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Texture getTexture(char c) {
|
||||
if (!supports(c))
|
||||
return textures[0];
|
||||
return textures[c - '0'];
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShapeRenderProgram getProgram() {
|
||||
return FlatRenderProgram.getDefault();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(char c) {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.graphics.world.hud;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
|
||||
import ru.windcorp.progressia.client.events.NewLocalEntityEvent;
|
||||
import ru.windcorp.progressia.client.graphics.GUI;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Components;
|
||||
import ru.windcorp.progressia.client.graphics.gui.GUILayer;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Group;
|
||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutFill;
|
||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
|
||||
import ru.windcorp.progressia.client.graphics.input.bus.InputBus;
|
||||
import ru.windcorp.progressia.test.controls.TestPlayerControls;
|
||||
|
||||
public class LayerHUD extends GUILayer {
|
||||
|
||||
private final HUDManager manager;
|
||||
private WindowedHUD windowManager = null;
|
||||
|
||||
private boolean showInventory = false;
|
||||
private boolean isHidden = false;
|
||||
|
||||
public LayerHUD(HUDManager manager) {
|
||||
super("LayerHUD", new LayoutFill(15));
|
||||
this.manager = manager;
|
||||
|
||||
setCursorPolicy(CursorPolicy.INDIFFERENT);
|
||||
|
||||
manager.getClient().subscribe(this);
|
||||
|
||||
getRoot().addKeyListener(new KeyMatcher("Escape"), e -> {
|
||||
setInventoryShown(!showInventory);
|
||||
e.consume();
|
||||
}, InputBus.Option.IGNORE_FOCUS);
|
||||
|
||||
getRoot().addKeyListener(new KeyMatcher("E"), e -> {
|
||||
setInventoryShown(!showInventory);
|
||||
e.consume();
|
||||
}, InputBus.Option.IGNORE_FOCUS);
|
||||
|
||||
getRoot().addKeyListener(new KeyMatcher("Left Control"), e -> {
|
||||
TestPlayerControls.getInstance().getInventoryControls().switchHandsWithCtrl(e);
|
||||
e.consume();
|
||||
}, InputBus.Option.IGNORE_ACTION, InputBus.Option.IGNORE_FOCUS);
|
||||
|
||||
getRoot().addKeyListener(new KeyMatcher("Right Control"), e -> {
|
||||
TestPlayerControls.getInstance().getInventoryControls().switchHandsWithCtrl(e);
|
||||
e.consume();
|
||||
}, InputBus.Option.IGNORE_ACTION, InputBus.Option.IGNORE_FOCUS);
|
||||
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
private void onEntityChanged(NewLocalEntityEvent e) {
|
||||
while (!getRoot().getChildren().isEmpty()) {
|
||||
getRoot().removeChild(getRoot().getChild(0));
|
||||
}
|
||||
|
||||
if (e.getNewEntity() == null) {
|
||||
getRoot().requestReassembly();
|
||||
return;
|
||||
}
|
||||
|
||||
getRoot().addChild(new PermanentHUD(getName() + ".Permanent", manager));
|
||||
|
||||
Component inventoryGroup = new Group(getName() + ".InventoryGroup", new LayoutFill());
|
||||
|
||||
inventoryGroup.addChild(new InventoryHUD(getName() + ".Equipment", manager));
|
||||
|
||||
windowManager = new WindowedHUD(getName() + ".Windows");
|
||||
inventoryGroup.addChild(windowManager);
|
||||
|
||||
inventoryGroup.addChild(new CursorHUD(getName() + ".Cursor", manager.getPlayerEntity()));
|
||||
|
||||
getRoot().addChild(Components.hide(inventoryGroup, () -> !showInventory));
|
||||
|
||||
getRoot().requestReassembly();
|
||||
}
|
||||
|
||||
public WindowedHUD getWindowManager() {
|
||||
return windowManager;
|
||||
}
|
||||
|
||||
public boolean isInventoryShown() {
|
||||
return showInventory;
|
||||
}
|
||||
|
||||
public void setInventoryShown(boolean showInventory) {
|
||||
this.showInventory = showInventory;
|
||||
updateCursorPolicy();
|
||||
}
|
||||
|
||||
public boolean isHidden() {
|
||||
return isHidden;
|
||||
}
|
||||
|
||||
public void setHidden(boolean isHidden) {
|
||||
this.isHidden = isHidden;
|
||||
updateCursorPolicy();
|
||||
}
|
||||
|
||||
private void updateCursorPolicy() {
|
||||
if (showInventory && !isHidden) {
|
||||
setCursorPolicy(CursorPolicy.REQUIRE);
|
||||
} else {
|
||||
setCursorPolicy(CursorPolicy.INDIFFERENT);
|
||||
}
|
||||
|
||||
GUI.updateLayer(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleInput(InputEvent event) {
|
||||
if (isHidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
super.handleInput(event);
|
||||
|
||||
if (getCursorPolicy() == CursorPolicy.REQUIRE) {
|
||||
event.consume();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doRender() {
|
||||
if (isHidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
super.doRender();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.graphics.world.hud;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutBorderVertical;
|
||||
import ru.windcorp.progressia.common.world.entity.EntityDataPlayer;
|
||||
|
||||
public class PermanentHUD extends Component {
|
||||
|
||||
public PermanentHUD(String name, HUDManager manager) {
|
||||
super(name);
|
||||
setLayout(new LayoutBorderVertical());
|
||||
|
||||
EntityDataPlayer entity = manager.getPlayerEntity();
|
||||
if (entity == null) {
|
||||
throw new IllegalStateException("Player " + manager.getPlayer() + " does not have an associated entity");
|
||||
}
|
||||
|
||||
addChild(new HandsHUD(name + ".Hands", manager).setLayoutHint(LayoutBorderVertical.UP));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.graphics.world.hud;
|
||||
|
||||
import java.util.function.BooleanSupplier;
|
||||
import glm.mat._4.Mat4;
|
||||
import ru.windcorp.progressia.client.graphics.Colors;
|
||||
import ru.windcorp.progressia.client.graphics.backend.InputTracker;
|
||||
import ru.windcorp.progressia.client.graphics.flat.FlatRenderProgram;
|
||||
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
||||
import ru.windcorp.progressia.client.graphics.font.Font;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||
import ru.windcorp.progressia.client.graphics.gui.DynamicLabel;
|
||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
|
||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
||||
import ru.windcorp.progressia.client.graphics.model.Shapes.PgmBuilder;
|
||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||
import ru.windcorp.progressia.client.world.item.ItemRenderRegistry;
|
||||
import ru.windcorp.progressia.client.world.item.ItemRenderable;
|
||||
import ru.windcorp.progressia.common.world.item.ItemData;
|
||||
import ru.windcorp.progressia.common.world.item.ItemDataContainer;
|
||||
import ru.windcorp.progressia.common.world.item.inventory.ItemContainer;
|
||||
import ru.windcorp.progressia.common.world.item.inventory.ItemSlot;
|
||||
|
||||
public class SlotComponent extends Component {
|
||||
|
||||
static final float TEXTURE_SIZE = 24;
|
||||
|
||||
private static boolean drawVirtualSlots;
|
||||
static {
|
||||
String key = SlotComponent.class.getName() + ".drawVirtualSlots";
|
||||
drawVirtualSlots = Boolean.parseBoolean(System.getProperty(key, "true"));
|
||||
}
|
||||
|
||||
private static Renderable containerOpenDecoration = null;
|
||||
private static Renderable containerOpenableDecoration = null;
|
||||
|
||||
private final ItemSlot slot;
|
||||
|
||||
private float scale = 2;
|
||||
|
||||
private ItemRenderable itemRenderer = null;
|
||||
|
||||
private int amountDisplayInt = 0;
|
||||
private String amountDisplayString = "";
|
||||
|
||||
private Renderable background = null;
|
||||
private BooleanSupplier backgroundCondition = null;
|
||||
|
||||
public SlotComponent(String name, ItemContainer container, int index) {
|
||||
super(name);
|
||||
this.slot = new ItemSlot(container, index);
|
||||
|
||||
setScale(2);
|
||||
|
||||
Font sizeFont = new Font(HUDTextures.getItemAmountTypeface()).deriveOutlined().withScale(2);
|
||||
addChild(new DynamicLabel(getName() + ".Size", sizeFont, () -> amountDisplayString, getPreferredSize().x));
|
||||
|
||||
setLayout(new LayoutAlign(0, 0, 0));
|
||||
|
||||
if (containerOpenDecoration == null) {
|
||||
containerOpenDecoration = new PgmBuilder(
|
||||
FlatRenderProgram.getDefault(),
|
||||
HUDTextures.getHUDTexture("DecorationContainerOpen")
|
||||
).setSize(TEXTURE_SIZE + 2).setOrigin(-1, -1, 0).create();
|
||||
}
|
||||
|
||||
if (containerOpenableDecoration == null) {
|
||||
containerOpenableDecoration = new PgmBuilder(
|
||||
FlatRenderProgram.getDefault(),
|
||||
HUDTextures.getHUDTexture("DecorationContainerOpenable")
|
||||
).setSize(TEXTURE_SIZE + 2).setOrigin(-1, -1, 0).create();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the container
|
||||
*/
|
||||
public ItemContainer getSlotContainer() {
|
||||
return slot.getContainer();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the index
|
||||
*/
|
||||
public int getSlotIndex() {
|
||||
return slot.getIndex();
|
||||
}
|
||||
|
||||
public ItemSlot getSlot() {
|
||||
return slot;
|
||||
}
|
||||
|
||||
public SlotComponent setScale(float scale) {
|
||||
this.scale = scale;
|
||||
|
||||
int side = (int) (TEXTURE_SIZE * scale);
|
||||
setPreferredSize(side, side);
|
||||
invalidate();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public SlotComponent setBackground(Texture texture, BooleanSupplier when) {
|
||||
background = new PgmBuilder(FlatRenderProgram.getDefault(), texture).setSize(TEXTURE_SIZE).create();
|
||||
setBackgroundDisplayCondition(when);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SlotComponent setBackground(Texture texture) {
|
||||
return setBackground(texture, null);
|
||||
}
|
||||
|
||||
public SlotComponent setBackgroundDisplayCondition(BooleanSupplier backgroundCondition) {
|
||||
this.backgroundCondition = backgroundCondition;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assembleSelf(RenderTarget target) {
|
||||
super.assembleSelf(target);
|
||||
assembleItem(target);
|
||||
}
|
||||
|
||||
private void updateItemRenderer() {
|
||||
ItemData contents;
|
||||
contents = slot.getItem();
|
||||
|
||||
if (contents == null) {
|
||||
itemRenderer = null;
|
||||
amountDisplayInt = 0;
|
||||
amountDisplayString = "";
|
||||
} else {
|
||||
if (itemRenderer == null || itemRenderer.getData() != contents) {
|
||||
itemRenderer = ItemRenderRegistry.getInstance().get(contents.getId()).createRenderable(contents);
|
||||
}
|
||||
|
||||
int newAmount = slot.getCount();
|
||||
if (newAmount != amountDisplayInt) {
|
||||
amountDisplayInt = newAmount;
|
||||
amountDisplayString = newAmount == 1 ? "" : Integer.toString(newAmount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void assembleItem(RenderTarget target) {
|
||||
target.pushTransform(new Mat4().translate(getX(), getY(), 0).scale(scale, scale, 1));
|
||||
target.addCustomRenderer(renderer -> {
|
||||
|
||||
updateItemRenderer();
|
||||
|
||||
if (drawVirtualSlots && getSlot() == null) {
|
||||
renderer.pushColorMultiplier().set(Colors.DEBUG_GREEN);
|
||||
containerOpenDecoration.render(renderer);
|
||||
renderer.popColorMultiplier();
|
||||
return;
|
||||
}
|
||||
|
||||
if (itemRenderer != null) {
|
||||
itemRenderer.render(renderer);
|
||||
renderDecorations(renderer);
|
||||
} else if (background != null) {
|
||||
if (backgroundCondition == null || backgroundCondition.getAsBoolean()) {
|
||||
background.render(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
target.popTransform();
|
||||
}
|
||||
|
||||
private void renderDecorations(ShapeRenderHelper renderer) {
|
||||
ItemData contents = getSlot().getItem();
|
||||
|
||||
if (contents instanceof ItemDataContainer) {
|
||||
ItemDataContainer asContainer = (ItemDataContainer) contents;
|
||||
|
||||
if (asContainer.isOpen()) {
|
||||
renderer.pushColorMultiplier().mul(Colors.BLUE);
|
||||
containerOpenDecoration.render(renderer);
|
||||
renderer.popColorMultiplier();
|
||||
} else {
|
||||
|
||||
double dx = InputTracker.getCursorX() - (getX() + getWidth() / 2);
|
||||
double dy = InputTracker.getCursorY() - (getY() + getHeight() / 2);
|
||||
double distanceToCursorSquared = dx*dx + dy*dy;
|
||||
final double maxDistanceSquared = (scale * TEXTURE_SIZE * 4) * (scale * TEXTURE_SIZE * 4);
|
||||
|
||||
float opacity = (float) (1 - distanceToCursorSquared / maxDistanceSquared);
|
||||
|
||||
if (opacity > 0) {
|
||||
renderer.pushColorMultiplier().mul(Colors.BLUE.x, Colors.BLUE.y, Colors.BLUE.z, opacity);
|
||||
containerOpenableDecoration.render(renderer);
|
||||
renderer.popColorMultiplier();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.graphics.world.hud;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||
|
||||
public class WindowedHUD extends Component {
|
||||
|
||||
public WindowedHUD(String name) {
|
||||
super(name);
|
||||
setLayout(new WindowedLayout());
|
||||
}
|
||||
|
||||
public void addWindow(InventoryWindow window) {
|
||||
addChild(window);
|
||||
window.setSize(window.getPreferredSize());
|
||||
|
||||
centerWindow(window);
|
||||
}
|
||||
|
||||
public void closeWindow(InventoryWindow window) {
|
||||
removeChild(window);
|
||||
requestReassembly();
|
||||
}
|
||||
|
||||
private void centerWindow(InventoryWindow window) {
|
||||
window.setPosition(
|
||||
(getWidth() - window.getWidth()) / 2,
|
||||
(getHeight() - window.getHeight()) / 2
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.graphics.world.hud;
|
||||
|
||||
import glm.Glm;
|
||||
import glm.vec._2.Vec2;
|
||||
import glm.vec._2.i.Vec2i;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||
import ru.windcorp.progressia.client.graphics.gui.Layout;
|
||||
|
||||
public class WindowedLayout implements Layout {
|
||||
|
||||
@Override
|
||||
public void layout(Component c) {
|
||||
for (Component component : c.getChildren()) {
|
||||
InventoryWindow window = (InventoryWindow) component;
|
||||
|
||||
Vec2i size = new Vec2i(c.getWidth(), c.getHeight());
|
||||
Glm.min(window.getPreferredSize(), size, size);
|
||||
window.setSize(size);
|
||||
|
||||
Vec2 relPos = window.getRelativePosition();
|
||||
|
||||
if (Float.isNaN(relPos.x) || Float.isNaN(relPos.y)) {
|
||||
relPos.x = 0.5f;
|
||||
relPos.y = 2 / 3.0f;
|
||||
} else {
|
||||
float minPosX = 0;
|
||||
float minPosY = window.getHeight() / (float) c.getHeight();
|
||||
float maxPosX = 1;
|
||||
float maxPosY = 1;
|
||||
|
||||
relPos.x = Glm.clamp(relPos.x, minPosX, maxPosX);
|
||||
relPos.y = Glm.clamp(relPos.y, minPosY, maxPosY);
|
||||
}
|
||||
|
||||
window.setPosition(
|
||||
(int) (relPos.x * c.getWidth() - window.getWidth() / 2.0f),
|
||||
(int) (relPos.y * c.getHeight() - window.getHeight())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec2i calculatePreferredSize(Component c) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}
|
@ -73,6 +73,12 @@ public class Localizer {
|
||||
return language;
|
||||
}
|
||||
|
||||
public List<String> getLanguages() {
|
||||
List<String> result = new ArrayList<>(langList.keySet());
|
||||
result.sort(null);
|
||||
return result;
|
||||
}
|
||||
|
||||
public synchronized String getValue(String key) {
|
||||
if (data == null) {
|
||||
throw new IllegalStateException("Localizer not yet initialized");
|
||||
|
@ -27,25 +27,29 @@ import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
||||
import ru.windcorp.progressia.client.world.block.BlockRender;
|
||||
import ru.windcorp.progressia.client.world.block.BlockRenderRegistry;
|
||||
import ru.windcorp.progressia.client.world.tile.TileRender;
|
||||
import ru.windcorp.progressia.client.world.tile.TileRenderReference;
|
||||
import ru.windcorp.progressia.client.world.tile.TileRenderRegistry;
|
||||
import ru.windcorp.progressia.client.world.tile.TileRenderStack;
|
||||
import ru.windcorp.progressia.common.world.ChunkData;
|
||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
||||
import ru.windcorp.progressia.common.world.generic.GenericChunk;
|
||||
import ru.windcorp.progressia.common.world.tile.TileDataStack;
|
||||
import ru.windcorp.progressia.common.world.DefaultChunkData;
|
||||
import ru.windcorp.progressia.common.world.TileDataReference;
|
||||
import ru.windcorp.progressia.common.world.TileDataStack;
|
||||
import ru.windcorp.progressia.common.world.generic.ChunkGenericRO;
|
||||
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
||||
import ru.windcorp.progressia.common.world.rels.BlockFace;
|
||||
import ru.windcorp.progressia.common.world.rels.RelFace;
|
||||
|
||||
public class ChunkRender
|
||||
implements GenericChunk<ChunkRender, BlockRender, TileRender, TileRenderStack> {
|
||||
implements ChunkGenericRO<BlockRender, TileRender, TileRenderStack, TileRenderReference, ChunkRender> {
|
||||
|
||||
private final WorldRender world;
|
||||
private final ChunkData data;
|
||||
private final DefaultChunkData data;
|
||||
|
||||
private final ChunkRenderModel model;
|
||||
|
||||
private final Map<TileDataStack, TileRenderStackImpl> tileRenderLists = Collections
|
||||
.synchronizedMap(new WeakHashMap<>());
|
||||
|
||||
public ChunkRender(WorldRender world, ChunkData data) {
|
||||
public ChunkRender(WorldRender world, DefaultChunkData data) {
|
||||
this.world = world;
|
||||
this.data = data;
|
||||
this.model = new ChunkRenderModel(this);
|
||||
@ -56,6 +60,11 @@ public class ChunkRender
|
||||
return getData().getPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbsFace getUp() {
|
||||
return getData().getUp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockRender getBlock(Vec3i posInChunk) {
|
||||
return BlockRenderRegistry.getInstance().get(
|
||||
@ -84,11 +93,11 @@ public class ChunkRender
|
||||
return world;
|
||||
}
|
||||
|
||||
public ChunkData getData() {
|
||||
public DefaultChunkData getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public synchronized void markForUpdate() {
|
||||
public void markForUpdate() {
|
||||
getWorld().markChunkForUpdate(getPosition());
|
||||
}
|
||||
|
||||
@ -101,6 +110,28 @@ public class ChunkRender
|
||||
}
|
||||
|
||||
private class TileRenderStackImpl extends TileRenderStack {
|
||||
private class TileRenderReferenceImpl implements TileRenderReference {
|
||||
private final TileDataReference parent;
|
||||
|
||||
public TileRenderReferenceImpl(TileDataReference parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TileRender get() {
|
||||
return TileRenderRegistry.getInstance().get(parent.get().getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndex() {
|
||||
return parent.getIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TileRenderStack getStack() {
|
||||
return TileRenderStackImpl.this;
|
||||
}
|
||||
}
|
||||
|
||||
private final TileDataStack parent;
|
||||
|
||||
@ -119,10 +150,25 @@ public class ChunkRender
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockFace getFace() {
|
||||
public RelFace getFace() {
|
||||
return parent.getFace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TileRenderReference getReference(int index) {
|
||||
return new TileRenderReferenceImpl(parent.getReference(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndexByTag(int tag) {
|
||||
return parent.getIndexByTag(tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTagByIndex(int index) {
|
||||
return parent.getTagByIndex(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TileRender get(int index) {
|
||||
return TileRenderRegistry.getInstance().get(parent.get(index).getId());
|
||||
|
@ -35,8 +35,10 @@ import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerRegistry;
|
||||
import ru.windcorp.progressia.client.world.tile.TileRender;
|
||||
import ru.windcorp.progressia.client.world.tile.TileRenderNone;
|
||||
import ru.windcorp.progressia.client.world.tile.TileRenderStack;
|
||||
import ru.windcorp.progressia.common.world.ChunkData;
|
||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
||||
import ru.windcorp.progressia.common.world.DefaultChunkData;
|
||||
import ru.windcorp.progressia.common.world.generic.GenericChunks;
|
||||
import ru.windcorp.progressia.common.world.rels.AxisRotations;
|
||||
import ru.windcorp.progressia.common.world.rels.RelFace;
|
||||
|
||||
public class ChunkRenderModel implements Renderable {
|
||||
|
||||
@ -53,11 +55,15 @@ public class ChunkRenderModel implements Renderable {
|
||||
public void render(ShapeRenderHelper renderer) {
|
||||
if (model == null) return;
|
||||
|
||||
float offset = DefaultChunkData.BLOCKS_PER_CHUNK / 2 - 0.5f;
|
||||
|
||||
renderer.pushTransform().translate(
|
||||
chunk.getX() * ChunkData.BLOCKS_PER_CHUNK,
|
||||
chunk.getY() * ChunkData.BLOCKS_PER_CHUNK,
|
||||
chunk.getZ() * ChunkData.BLOCKS_PER_CHUNK
|
||||
);
|
||||
chunk.getX() * DefaultChunkData.BLOCKS_PER_CHUNK,
|
||||
chunk.getY() * DefaultChunkData.BLOCKS_PER_CHUNK,
|
||||
chunk.getZ() * DefaultChunkData.BLOCKS_PER_CHUNK
|
||||
).translate(offset, offset, offset)
|
||||
.mul(AxisRotations.getResolutionMatrix4(chunk.getUp()))
|
||||
.translate(-offset, -offset, -offset);
|
||||
|
||||
model.render(renderer);
|
||||
|
||||
@ -71,8 +77,8 @@ public class ChunkRenderModel implements Renderable {
|
||||
|
||||
optimizers.forEach(ChunkRenderOptimizer::startRender);
|
||||
|
||||
chunk.forEachBiC(blockInChunk -> {
|
||||
processBlockAndTiles(blockInChunk, sink);
|
||||
GenericChunks.forEachBiC(relBlockInChunk -> {
|
||||
processBlockAndTiles(relBlockInChunk, sink);
|
||||
});
|
||||
|
||||
for (ChunkRenderOptimizer optimizer : optimizers) {
|
||||
@ -96,16 +102,16 @@ public class ChunkRenderModel implements Renderable {
|
||||
}
|
||||
}
|
||||
|
||||
private void processBlockAndTiles(Vec3i blockInChunk, Builder sink) {
|
||||
processBlock(blockInChunk, sink);
|
||||
private void processBlockAndTiles(Vec3i relBlockInChunk, Builder sink) {
|
||||
processBlock(relBlockInChunk, sink);
|
||||
|
||||
for (BlockFace face : BlockFace.getFaces()) {
|
||||
processTileStack(blockInChunk, face, sink);
|
||||
for (RelFace face : RelFace.getFaces()) {
|
||||
processTileStack(relBlockInChunk, face, sink);
|
||||
}
|
||||
}
|
||||
|
||||
private void processBlock(Vec3i blockInChunk, Builder sink) {
|
||||
BlockRender block = chunk.getBlock(blockInChunk);
|
||||
private void processBlock(Vec3i relBlockInChunk, Builder sink) {
|
||||
BlockRender block = chunk.getBlockRel(relBlockInChunk);
|
||||
|
||||
if (block instanceof BlockRenderNone) {
|
||||
return;
|
||||
@ -113,48 +119,48 @@ public class ChunkRenderModel implements Renderable {
|
||||
|
||||
if (block.needsOwnRenderable()) {
|
||||
sink.addPart(
|
||||
block.createRenderable(chunk.getData(), blockInChunk),
|
||||
new Mat4().identity().translate(blockInChunk.x, blockInChunk.y, blockInChunk.z)
|
||||
block.createRenderable(chunk.getData(), relBlockInChunk),
|
||||
new Mat4().identity().translate(relBlockInChunk.x, relBlockInChunk.y, relBlockInChunk.z)
|
||||
);
|
||||
}
|
||||
|
||||
processBlockWithCROs(block, blockInChunk);
|
||||
processBlockWithCROs(block, relBlockInChunk);
|
||||
}
|
||||
|
||||
private void processBlockWithCROs(BlockRender block, Vec3i blockInChunk) {
|
||||
private void processBlockWithCROs(BlockRender block, Vec3i relBlockInChunk) {
|
||||
for (ChunkRenderOptimizer optimizer : optimizers) {
|
||||
optimizer.addBlock(block, blockInChunk);
|
||||
optimizer.addBlock(block, relBlockInChunk);
|
||||
}
|
||||
}
|
||||
|
||||
private void processTileStack(Vec3i blockInChunk, BlockFace face, Builder sink) {
|
||||
TileRenderStack trs = chunk.getTilesOrNull(blockInChunk, face);
|
||||
private void processTileStack(Vec3i relBlockInChunk, RelFace face, Builder sink) {
|
||||
TileRenderStack trs = chunk.getTilesOrNullRel(relBlockInChunk, face);
|
||||
|
||||
if (trs == null || trs.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
trs.forEach(tile -> processTile(tile, blockInChunk, face, sink));
|
||||
trs.forEach(tile -> processTile(tile, relBlockInChunk, face, sink));
|
||||
}
|
||||
|
||||
private void processTile(TileRender tile, Vec3i blockInChunk, BlockFace face, Builder sink) {
|
||||
private void processTile(TileRender tile, Vec3i relBlockInChunk, RelFace face, Builder sink) {
|
||||
if (tile instanceof TileRenderNone) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tile.needsOwnRenderable()) {
|
||||
sink.addPart(
|
||||
tile.createRenderable(chunk.getData(), blockInChunk, face),
|
||||
new Mat4().identity().translate(blockInChunk.x, blockInChunk.y, blockInChunk.z)
|
||||
tile.createRenderable(chunk.getData(), relBlockInChunk, face),
|
||||
new Mat4().identity().translate(relBlockInChunk.x, relBlockInChunk.y, relBlockInChunk.z)
|
||||
);
|
||||
}
|
||||
|
||||
processTileWithCROs(tile, blockInChunk, face);
|
||||
processTileWithCROs(tile, relBlockInChunk, face);
|
||||
}
|
||||
|
||||
private void processTileWithCROs(TileRender tile, Vec3i blockInChunk, BlockFace face) {
|
||||
private void processTileWithCROs(TileRender tile, Vec3i relBlockInChunk, RelFace face) {
|
||||
for (ChunkRenderOptimizer optimizer : optimizers) {
|
||||
optimizer.addTile(tile, blockInChunk, face);
|
||||
optimizer.addTile(tile, relBlockInChunk, face);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,10 +21,11 @@ package ru.windcorp.progressia.client.world;
|
||||
import glm.vec._3.i.Vec3i;
|
||||
import ru.windcorp.progressia.common.util.VectorUtil;
|
||||
import ru.windcorp.progressia.common.util.Vectors;
|
||||
import ru.windcorp.progressia.common.world.ChunkData;
|
||||
import ru.windcorp.progressia.common.world.DefaultChunkData;
|
||||
import ru.windcorp.progressia.common.world.ChunkDataListener;
|
||||
import ru.windcorp.progressia.common.world.block.BlockData;
|
||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
||||
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
||||
import ru.windcorp.progressia.common.world.rels.RelFace;
|
||||
import ru.windcorp.progressia.common.world.tile.TileData;
|
||||
|
||||
class ChunkUpdateListener implements ChunkDataListener {
|
||||
@ -36,14 +37,14 @@ class ChunkUpdateListener implements ChunkDataListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChunkChanged(ChunkData chunk) {
|
||||
public void onChunkChanged(DefaultChunkData chunk) {
|
||||
world.getChunk(chunk).markForUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChunkLoaded(ChunkData chunk) {
|
||||
public void onChunkLoaded(DefaultChunkData chunk) {
|
||||
Vec3i cursor = new Vec3i();
|
||||
for (BlockFace face : BlockFace.getFaces()) {
|
||||
for (AbsFace face : AbsFace.getFaces()) {
|
||||
cursor.set(chunk.getX(), chunk.getY(), chunk.getZ());
|
||||
cursor.add(face.getVector());
|
||||
world.markChunkForUpdate(cursor);
|
||||
@ -51,22 +52,22 @@ class ChunkUpdateListener implements ChunkDataListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChunkBlockChanged(ChunkData chunk, Vec3i blockInChunk, BlockData previous, BlockData current) {
|
||||
public void onChunkBlockChanged(DefaultChunkData chunk, Vec3i blockInChunk, BlockData previous, BlockData current) {
|
||||
onLocationChanged(chunk, blockInChunk);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChunkTilesChanged(
|
||||
ChunkData chunk,
|
||||
DefaultChunkData chunk,
|
||||
Vec3i blockInChunk,
|
||||
BlockFace face,
|
||||
RelFace face,
|
||||
TileData tile,
|
||||
boolean wasAdded
|
||||
) {
|
||||
onLocationChanged(chunk, blockInChunk);
|
||||
}
|
||||
|
||||
private void onLocationChanged(ChunkData chunk, Vec3i blockInChunk) {
|
||||
private void onLocationChanged(DefaultChunkData chunk, Vec3i blockInChunk) {
|
||||
Vec3i chunkPos = Vectors.grab3i().set(chunk.getX(), chunk.getY(), chunk.getZ());
|
||||
|
||||
checkCoordinate(blockInChunk, chunkPos, VectorUtil.Axis.X);
|
||||
@ -82,7 +83,7 @@ class ChunkUpdateListener implements ChunkDataListener {
|
||||
|
||||
if (block == 0) {
|
||||
diff = -1;
|
||||
} else if (block == ChunkData.BLOCKS_PER_CHUNK - 1) {
|
||||
} else if (block == DefaultChunkData.BLOCKS_PER_CHUNK - 1) {
|
||||
diff = +1;
|
||||
} else {
|
||||
return;
|
||||
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.world;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
||||
|
||||
public abstract class UpdatingRenderable implements Renderable {
|
||||
|
||||
private long stateComputedForFrame = -1;
|
||||
|
||||
/**
|
||||
* Updates the state of this model. This method is invoked exactly once per
|
||||
* renderable per frame before this model is queried for the first time.
|
||||
*/
|
||||
protected void update() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
protected void updateIfNecessary() {
|
||||
if (stateComputedForFrame != GraphicsInterface.getFramesRendered()) {
|
||||
update();
|
||||
stateComputedForFrame = GraphicsInterface.getFramesRendered();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void render(ShapeRenderHelper renderer) {
|
||||
updateIfNecessary();
|
||||
doRender(renderer);
|
||||
}
|
||||
|
||||
protected abstract void doRender(ShapeRenderHelper renderer);
|
||||
|
||||
}
|
@ -34,57 +34,58 @@ import ru.windcorp.progressia.client.world.block.BlockRender;
|
||||
import ru.windcorp.progressia.client.world.entity.EntityRenderRegistry;
|
||||
import ru.windcorp.progressia.client.world.entity.EntityRenderable;
|
||||
import ru.windcorp.progressia.client.world.tile.TileRender;
|
||||
import ru.windcorp.progressia.client.world.tile.TileRenderReference;
|
||||
import ru.windcorp.progressia.client.world.tile.TileRenderStack;
|
||||
import ru.windcorp.progressia.common.util.VectorUtil;
|
||||
import ru.windcorp.progressia.common.util.Vectors;
|
||||
import ru.windcorp.progressia.common.world.ChunkData;
|
||||
import ru.windcorp.progressia.common.world.DefaultChunkData;
|
||||
import ru.windcorp.progressia.common.world.ChunkDataListeners;
|
||||
import ru.windcorp.progressia.common.world.WorldData;
|
||||
import ru.windcorp.progressia.common.world.DefaultWorldData;
|
||||
import ru.windcorp.progressia.common.world.WorldDataListener;
|
||||
import ru.windcorp.progressia.common.world.entity.EntityData;
|
||||
import ru.windcorp.progressia.common.world.generic.ChunkSet;
|
||||
import ru.windcorp.progressia.common.world.generic.ChunkSets;
|
||||
import ru.windcorp.progressia.common.world.generic.GenericWorld;
|
||||
import ru.windcorp.progressia.common.world.generic.WorldGenericRO;
|
||||
|
||||
public class WorldRender
|
||||
implements GenericWorld<BlockRender, TileRender, TileRenderStack, ChunkRender, EntityRenderable> {
|
||||
implements WorldGenericRO<BlockRender, TileRender, TileRenderStack, TileRenderReference, ChunkRender, EntityRenderable> {
|
||||
|
||||
private final WorldData data;
|
||||
private final DefaultWorldData data;
|
||||
private final Client client;
|
||||
|
||||
private final Map<ChunkData, ChunkRender> chunks = Collections.synchronizedMap(new HashMap<>());
|
||||
private final Map<DefaultChunkData, ChunkRender> chunks = Collections.synchronizedMap(new HashMap<>());
|
||||
private final Map<EntityData, EntityRenderable> entityModels = Collections.synchronizedMap(new WeakHashMap<>());
|
||||
|
||||
private final ChunkSet chunksToUpdate = ChunkSets.newSyncHashSet();
|
||||
|
||||
public WorldRender(WorldData data, Client client) {
|
||||
public WorldRender(DefaultWorldData data, Client client) {
|
||||
this.data = data;
|
||||
this.client = client;
|
||||
|
||||
data.addListener(ChunkDataListeners.createAdder(new ChunkUpdateListener(this)));
|
||||
data.addListener(new WorldDataListener() {
|
||||
@Override
|
||||
public void onChunkLoaded(WorldData world, ChunkData chunk) {
|
||||
public void onChunkLoaded(DefaultWorldData world, DefaultChunkData chunk) {
|
||||
addChunk(chunk);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeChunkUnloaded(WorldData world, ChunkData chunk) {
|
||||
public void beforeChunkUnloaded(DefaultWorldData world, DefaultChunkData chunk) {
|
||||
removeChunk(chunk);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void addChunk(ChunkData chunk) {
|
||||
protected void addChunk(DefaultChunkData chunk) {
|
||||
chunks.put(chunk, new ChunkRender(WorldRender.this, chunk));
|
||||
markChunkForUpdate(chunk.getPosition());
|
||||
}
|
||||
|
||||
protected void removeChunk(ChunkData chunk) {
|
||||
protected void removeChunk(DefaultChunkData chunk) {
|
||||
chunks.remove(chunk);
|
||||
}
|
||||
|
||||
public WorldData getData() {
|
||||
public DefaultWorldData getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
@ -92,7 +93,7 @@ public class WorldRender
|
||||
return client;
|
||||
}
|
||||
|
||||
public ChunkRender getChunk(ChunkData chunkData) {
|
||||
public ChunkRender getChunk(DefaultChunkData chunkData) {
|
||||
return chunks.get(chunkData);
|
||||
}
|
||||
|
||||
@ -111,6 +112,13 @@ public class WorldRender
|
||||
return entityModels.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityRenderable getEntity(long entityId) {
|
||||
EntityData entityData = getData().getEntity(entityId);
|
||||
if (entityData == null) return null;
|
||||
return getEntityRenderable(entityData);
|
||||
}
|
||||
|
||||
public void render(ShapeRenderHelper renderer) {
|
||||
updateChunks();
|
||||
|
||||
|
@ -18,26 +18,19 @@
|
||||
|
||||
package ru.windcorp.progressia.client.world.block;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
||||
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
|
||||
import ru.windcorp.progressia.common.world.ChunkData;
|
||||
import ru.windcorp.progressia.common.world.generic.GenericBlock;
|
||||
import ru.windcorp.progressia.common.world.DefaultChunkData;
|
||||
import ru.windcorp.progressia.common.world.generic.BlockGeneric;
|
||||
import glm.vec._3.i.Vec3i;
|
||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||
|
||||
public abstract class BlockRender extends Namespaced implements GenericBlock {
|
||||
public abstract class BlockRender extends Namespaced implements BlockGeneric {
|
||||
|
||||
public BlockRender(String id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
public void render(ShapeRenderHelper renderer) {
|
||||
throw new UnsupportedOperationException(
|
||||
"BlockRender.render() not implemented in " + this
|
||||
);
|
||||
}
|
||||
|
||||
public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk) {
|
||||
public Renderable createRenderable(DefaultChunkData chunk, Vec3i relBlockInChunk) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ package ru.windcorp.progressia.client.world.block;
|
||||
import glm.vec._3.i.Vec3i;
|
||||
import ru.windcorp.progressia.client.graphics.model.EmptyModel;
|
||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||
import ru.windcorp.progressia.common.world.ChunkData;
|
||||
import ru.windcorp.progressia.common.world.DefaultChunkData;
|
||||
|
||||
public class BlockRenderNone extends BlockRender {
|
||||
|
||||
@ -30,7 +30,7 @@ public class BlockRenderNone extends BlockRender {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk) {
|
||||
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk) {
|
||||
return EmptyModel.getInstance();
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@
|
||||
package ru.windcorp.progressia.client.world.block;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
||||
import ru.windcorp.progressia.common.world.rels.RelFace;
|
||||
|
||||
public class BlockRenderOpaqueCube extends BlockRenderTexturedCube {
|
||||
|
||||
@ -29,8 +29,8 @@ public class BlockRenderOpaqueCube extends BlockRenderTexturedCube {
|
||||
Texture bottomTexture,
|
||||
Texture northTexture,
|
||||
Texture southTexture,
|
||||
Texture eastTexture,
|
||||
Texture westTexture
|
||||
Texture westTexture,
|
||||
Texture eastTexture
|
||||
) {
|
||||
super(
|
||||
id,
|
||||
@ -38,8 +38,8 @@ public class BlockRenderOpaqueCube extends BlockRenderTexturedCube {
|
||||
bottomTexture,
|
||||
northTexture,
|
||||
southTexture,
|
||||
eastTexture,
|
||||
westTexture
|
||||
westTexture,
|
||||
eastTexture
|
||||
);
|
||||
}
|
||||
|
||||
@ -55,8 +55,20 @@ public class BlockRenderOpaqueCube extends BlockRenderTexturedCube {
|
||||
);
|
||||
}
|
||||
|
||||
public BlockRenderOpaqueCube(String id, Texture topTexture, Texture bottomTexture, Texture sideTexture) {
|
||||
this(
|
||||
id,
|
||||
topTexture,
|
||||
bottomTexture,
|
||||
sideTexture,
|
||||
sideTexture,
|
||||
sideTexture,
|
||||
sideTexture
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaque(BlockFace face) {
|
||||
public boolean isOpaque(RelFace face) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -18,9 +18,8 @@
|
||||
|
||||
package ru.windcorp.progressia.client.world.block;
|
||||
|
||||
import static ru.windcorp.progressia.common.world.block.BlockFace.*;
|
||||
import static ru.windcorp.progressia.common.world.rels.AbsFace.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@ -29,22 +28,23 @@ import glm.vec._3.i.Vec3i;
|
||||
import glm.vec._4.Vec4;
|
||||
import ru.windcorp.progressia.client.graphics.Colors;
|
||||
import ru.windcorp.progressia.client.graphics.backend.Usage;
|
||||
import ru.windcorp.progressia.client.graphics.model.Face;
|
||||
import ru.windcorp.progressia.client.graphics.model.Faces;
|
||||
import ru.windcorp.progressia.client.graphics.model.ShapePart;
|
||||
import ru.windcorp.progressia.client.graphics.model.ShapeParts;
|
||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||
import ru.windcorp.progressia.client.graphics.model.Shape;
|
||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
|
||||
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSurface.BlockOptimizedSurface;
|
||||
import ru.windcorp.progressia.common.util.Vectors;
|
||||
import ru.windcorp.progressia.common.world.ChunkData;
|
||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
||||
import ru.windcorp.progressia.common.world.DefaultChunkData;
|
||||
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
||||
import ru.windcorp.progressia.common.world.rels.RelFace;
|
||||
|
||||
public abstract class BlockRenderTexturedCube
|
||||
extends BlockRender
|
||||
implements BlockOptimizedSurface {
|
||||
|
||||
private final Map<BlockFace, Texture> textures = new HashMap<>();
|
||||
private final Map<RelFace, Texture> textures;
|
||||
|
||||
public BlockRenderTexturedCube(
|
||||
String id,
|
||||
@ -52,65 +52,59 @@ public abstract class BlockRenderTexturedCube
|
||||
Texture bottomTexture,
|
||||
Texture northTexture,
|
||||
Texture southTexture,
|
||||
Texture eastTexture,
|
||||
Texture westTexture
|
||||
Texture westTexture,
|
||||
Texture eastTexture
|
||||
) {
|
||||
super(id);
|
||||
|
||||
textures.put(TOP, topTexture);
|
||||
textures.put(BOTTOM, bottomTexture);
|
||||
textures.put(NORTH, northTexture);
|
||||
textures.put(SOUTH, southTexture);
|
||||
textures.put(EAST, eastTexture);
|
||||
textures.put(WEST, westTexture);
|
||||
this.textures = RelFace.mapToFaces(topTexture, bottomTexture, northTexture, southTexture, westTexture, eastTexture);
|
||||
}
|
||||
|
||||
public Texture getTexture(BlockFace blockFace) {
|
||||
public Texture getTexture(RelFace blockFace) {
|
||||
return textures.get(blockFace);
|
||||
}
|
||||
|
||||
public Vec4 getColorMultiplier(BlockFace blockFace) {
|
||||
public Vec4 getColorMultiplier(RelFace blockFace) {
|
||||
return Colors.WHITE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void getFaces(
|
||||
ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace,
|
||||
public final void getShapeParts(
|
||||
DefaultChunkData chunk, Vec3i blockInChunk, RelFace blockFace,
|
||||
boolean inner,
|
||||
Consumer<Face> output,
|
||||
Consumer<ShapePart> output,
|
||||
Vec3 offset
|
||||
) {
|
||||
output.accept(createFace(chunk, blockInChunk, blockFace, inner, offset));
|
||||
}
|
||||
|
||||
private Face createFace(
|
||||
ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace,
|
||||
private ShapePart createFace(
|
||||
DefaultChunkData chunk, Vec3i blockInChunk, RelFace blockFace,
|
||||
boolean inner,
|
||||
Vec3 offset
|
||||
) {
|
||||
return Faces.createBlockFace(
|
||||
return ShapeParts.createBlockFace(
|
||||
WorldRenderProgram.getDefault(),
|
||||
getTexture(blockFace),
|
||||
getColorMultiplier(blockFace),
|
||||
offset,
|
||||
blockFace,
|
||||
blockFace.resolve(AbsFace.POS_Z),
|
||||
inner
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk) {
|
||||
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk) {
|
||||
boolean opaque = isBlockOpaque();
|
||||
|
||||
Face[] faces = new Face[BLOCK_FACE_COUNT + (opaque ? BLOCK_FACE_COUNT : 0)];
|
||||
ShapePart[] faces = new ShapePart[BLOCK_FACE_COUNT + (opaque ? BLOCK_FACE_COUNT : 0)];
|
||||
|
||||
for (int i = 0; i < BLOCK_FACE_COUNT; ++i) {
|
||||
faces[i] = createFace(chunk, blockInChunk, BlockFace.getFaces().get(i), false, Vectors.ZERO_3);
|
||||
faces[i] = createFace(chunk, blockInChunk, RelFace.getFaces().get(i), false, Vectors.ZERO_3);
|
||||
}
|
||||
|
||||
if (!opaque) {
|
||||
for (int i = 0; i < BLOCK_FACE_COUNT; ++i) {
|
||||
faces[i + BLOCK_FACE_COUNT] = createFace(chunk, blockInChunk, BlockFace.getFaces().get(i), true, Vectors.ZERO_3);
|
||||
faces[i + BLOCK_FACE_COUNT] = createFace(chunk, blockInChunk, RelFace.getFaces().get(i), true, Vectors.ZERO_3);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@
|
||||
package ru.windcorp.progressia.client.world.block;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
||||
import ru.windcorp.progressia.common.world.rels.RelFace;
|
||||
|
||||
public class BlockRenderTransparentCube extends BlockRenderTexturedCube {
|
||||
|
||||
@ -29,8 +29,8 @@ public class BlockRenderTransparentCube extends BlockRenderTexturedCube {
|
||||
Texture bottomTexture,
|
||||
Texture northTexture,
|
||||
Texture southTexture,
|
||||
Texture eastTexture,
|
||||
Texture westTexture
|
||||
Texture westTexture,
|
||||
Texture eastTexture
|
||||
) {
|
||||
super(
|
||||
id,
|
||||
@ -38,8 +38,8 @@ public class BlockRenderTransparentCube extends BlockRenderTexturedCube {
|
||||
bottomTexture,
|
||||
northTexture,
|
||||
southTexture,
|
||||
eastTexture,
|
||||
westTexture
|
||||
westTexture,
|
||||
eastTexture
|
||||
);
|
||||
}
|
||||
|
||||
@ -55,8 +55,20 @@ public class BlockRenderTransparentCube extends BlockRenderTexturedCube {
|
||||
);
|
||||
}
|
||||
|
||||
public BlockRenderTransparentCube(String id, Texture topTexture, Texture bottomTexture, Texture sideTexture) {
|
||||
this(
|
||||
id,
|
||||
topTexture,
|
||||
bottomTexture,
|
||||
sideTexture,
|
||||
sideTexture,
|
||||
sideTexture,
|
||||
sideTexture
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaque(BlockFace face) {
|
||||
public boolean isOpaque(RelFace face) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ import ru.windcorp.progressia.client.world.ChunkRender;
|
||||
import ru.windcorp.progressia.client.world.block.BlockRender;
|
||||
import ru.windcorp.progressia.client.world.tile.TileRender;
|
||||
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
|
||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
||||
import ru.windcorp.progressia.common.world.rels.RelFace;
|
||||
|
||||
/**
|
||||
* Chunk render optimizer (CRO) is an object that produces optimized models for
|
||||
@ -35,6 +35,12 @@ import ru.windcorp.progressia.common.world.block.BlockFace;
|
||||
* tiles. An example of a CRO is {@link ChunkRenderOptimizerSurface}: it removes
|
||||
* block surfaces and tiles that it knows cannot be seen, thus significantly
|
||||
* reducing total polygon count.
|
||||
* <p>
|
||||
* As with everything related to rendering chunks, CROs are interacted with
|
||||
* using the relative local chunk coordinate system. In this coordinate system,
|
||||
* the coordinates are the chunk coordinates relativized using the chunks's up
|
||||
* direction. In simpler terms, coordinates are {@code [0; BLOCKS_PER_CHUNK)}
|
||||
* and Z is always up.
|
||||
* <h3>CRO lifecycle</h3>
|
||||
* A CRO instance is created by {@link ChunkRenderOptimizerRegistry}. It may
|
||||
* then be used to work on multiple chunks sequentially. Each chunk is processed
|
||||
@ -44,7 +50,7 @@ import ru.windcorp.progressia.common.world.block.BlockFace;
|
||||
* instance.</li>
|
||||
* <li>{@link #startRender()} is invoked. The CRO must reset its state.</li>
|
||||
* <li>{@link #addBlock(BlockRender, Vec3i)} and
|
||||
* {@link #addTile(TileRender, Vec3i, BlockFace)} are invoked for each block and
|
||||
* {@link #addTile(TileRender, Vec3i, RelFace)} are invoked for each block and
|
||||
* tile that this CRO should optimize. {@code addTile} specifies tiles in order
|
||||
* of ascension within a tile stack.</li>
|
||||
* <li>{@link #endRender()} is invoked. The CRO may perform any pending
|
||||
@ -98,12 +104,13 @@ public abstract class ChunkRenderOptimizer extends Namespaced {
|
||||
* method is only invoked once per block. This method is not necessarily
|
||||
* invoked for each block.
|
||||
*
|
||||
* @param block a {@link BlockRender} instance describing the block.
|
||||
* @param block a {@link BlockRender} instance describing the
|
||||
* block.
|
||||
* It corresponds to
|
||||
* {@code getChunk().getBlock(blockInChunk)}.
|
||||
* @param blockInChunk the position of the block
|
||||
* @param relBlockInChunk the relative position of the block
|
||||
*/
|
||||
public abstract void addBlock(BlockRender block, Vec3i blockInChunk);
|
||||
public abstract void addBlock(BlockRender block, Vec3i relBlockInChunk);
|
||||
|
||||
/**
|
||||
* Requests that this CRO processes the provided tile. This method may only
|
||||
@ -113,10 +120,11 @@ public abstract class ChunkRenderOptimizer extends Namespaced {
|
||||
* this method is invoked for lower tiles first.
|
||||
*
|
||||
* @param tile a {@link BlockRender} instance describing the tile
|
||||
* @param blockInChunk the position of the block that the tile belongs to
|
||||
* @param relBlockInChunk the relative position of the block that the tile
|
||||
* belongs to
|
||||
* @param blockFace the face that the tile belongs to
|
||||
*/
|
||||
public abstract void addTile(TileRender tile, Vec3i blockInChunk, BlockFace blockFace);
|
||||
public abstract void addTile(TileRender tile, Vec3i relBlockInChunk, RelFace blockFace);
|
||||
|
||||
/**
|
||||
* Requests that the CRO assembles and outputs its model. This method may
|
||||
|
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.world.cro;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import glm.vec._3.i.Vec3i;
|
||||
import ru.windcorp.progressia.client.graphics.backend.Usage;
|
||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||
import ru.windcorp.progressia.client.graphics.model.Shape;
|
||||
import ru.windcorp.progressia.client.graphics.model.ShapePart;
|
||||
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
|
||||
import ru.windcorp.progressia.client.world.block.BlockRender;
|
||||
import ru.windcorp.progressia.client.world.tile.TileRender;
|
||||
import ru.windcorp.progressia.common.world.DefaultChunkData;
|
||||
import ru.windcorp.progressia.common.world.rels.RelFace;
|
||||
|
||||
public class ChunkRenderOptimizerSimple extends ChunkRenderOptimizer {
|
||||
|
||||
public interface BlockOptimizedSimple {
|
||||
|
||||
void getShapeParts(
|
||||
DefaultChunkData chunk,
|
||||
Vec3i relBlockInChunk,
|
||||
Consumer<ShapePart> output
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
public interface TileOptimizedCustom {
|
||||
|
||||
void getShapeParts(
|
||||
DefaultChunkData chunk,
|
||||
Vec3i relBlockInChunk,
|
||||
RelFace blockFace,
|
||||
Consumer<ShapePart> output
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
private final Collection<ShapePart> parts = new ArrayList<>();
|
||||
private final Consumer<ShapePart> partAdder = parts::add;
|
||||
|
||||
public ChunkRenderOptimizerSimple(String id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startRender() {
|
||||
parts.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBlock(BlockRender block, Vec3i relBlockInChunk) {
|
||||
if (block instanceof BlockOptimizedSimple) {
|
||||
((BlockOptimizedSimple) block).getShapeParts(chunk.getData(), relBlockInChunk, partAdder);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTile(TileRender tile, Vec3i relBlockInChunk, RelFace blockFace) {
|
||||
if (tile instanceof TileOptimizedCustom) {
|
||||
((TileOptimizedCustom) tile).getShapeParts(chunk.getData(), relBlockInChunk, blockFace, partAdder);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Renderable endRender() {
|
||||
|
||||
if (parts.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Shape(
|
||||
Usage.STATIC,
|
||||
WorldRenderProgram.getDefault(),
|
||||
parts.toArray(new ShapePart[parts.size()])
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -18,9 +18,9 @@
|
||||
|
||||
package ru.windcorp.progressia.client.world.cro;
|
||||
|
||||
import static ru.windcorp.progressia.common.world.ChunkData.BLOCKS_PER_CHUNK;
|
||||
import static ru.windcorp.progressia.common.world.block.BlockFace.BLOCK_FACE_COUNT;
|
||||
import static ru.windcorp.progressia.common.world.generic.GenericTileStack.TILES_PER_FACE;
|
||||
import static ru.windcorp.progressia.common.world.DefaultChunkData.BLOCKS_PER_CHUNK;
|
||||
import static ru.windcorp.progressia.common.world.generic.TileGenericStackRO.TILES_PER_FACE;
|
||||
import static ru.windcorp.progressia.common.world.rels.AbsFace.BLOCK_FACE_COUNT;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@ -29,7 +29,7 @@ import java.util.function.Consumer;
|
||||
import glm.vec._3.Vec3;
|
||||
import glm.vec._3.i.Vec3i;
|
||||
import ru.windcorp.progressia.client.graphics.backend.Usage;
|
||||
import ru.windcorp.progressia.client.graphics.model.Face;
|
||||
import ru.windcorp.progressia.client.graphics.model.ShapePart;
|
||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||
import ru.windcorp.progressia.client.graphics.model.Shape;
|
||||
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
|
||||
@ -37,8 +37,9 @@ import ru.windcorp.progressia.client.world.ChunkRender;
|
||||
import ru.windcorp.progressia.client.world.block.BlockRender;
|
||||
import ru.windcorp.progressia.client.world.tile.TileRender;
|
||||
import ru.windcorp.progressia.common.util.Vectors;
|
||||
import ru.windcorp.progressia.common.world.ChunkData;
|
||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
||||
import ru.windcorp.progressia.common.world.DefaultChunkData;
|
||||
import ru.windcorp.progressia.common.world.generic.GenericChunks;
|
||||
import ru.windcorp.progressia.common.world.rels.RelFace;
|
||||
|
||||
public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
|
||||
|
||||
@ -52,39 +53,42 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
|
||||
private static interface OptimizedSurface {
|
||||
|
||||
/**
|
||||
* Creates and outputs a set of faces that correspond to this surface.
|
||||
* The coordinates of the face vertices must be in chunk coordinate
|
||||
* system.
|
||||
* Creates and outputs a set of shape parts that correspond to this
|
||||
* surface. The coordinates of the face vertices must be in chunk
|
||||
* coordinate system.
|
||||
*
|
||||
* @param chunk the chunk that contains the requested face
|
||||
* @param blockInChunk the block in chunk
|
||||
* @param relBlockInChunk the relative block in chunk
|
||||
* @param blockFace the requested face
|
||||
* @param inner whether this face should be visible from inside
|
||||
* @param inner whether this face should be visible from
|
||||
* inside
|
||||
* ({@code true}) or outside ({@code false})
|
||||
* @param output a consumer that the created faces must be given
|
||||
* to
|
||||
* @param offset an additional offset that must be applied to all
|
||||
* @param output a consumer that the created shape parts must
|
||||
* be
|
||||
* given to
|
||||
* @param offset an additional offset that must be applied to
|
||||
* all
|
||||
* vertices
|
||||
*/
|
||||
void getFaces(
|
||||
ChunkData chunk,
|
||||
Vec3i blockInChunk,
|
||||
BlockFace blockFace,
|
||||
void getShapeParts(
|
||||
DefaultChunkData chunk,
|
||||
Vec3i relBlockInChunk,
|
||||
RelFace blockFace,
|
||||
boolean inner,
|
||||
Consumer<Face> output,
|
||||
Consumer<ShapePart> output,
|
||||
Vec3 offset /* kostyl 156% */
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns the opacity of the surface identified by the provided
|
||||
* {@link BlockFace}.
|
||||
* {@link RelFace}.
|
||||
* Opaque surfaces prevent surfaces behind them from being included in
|
||||
* chunk models.
|
||||
*
|
||||
* @param blockFace the face to query
|
||||
* @return {@code true} iff the surface is opaque.
|
||||
*/
|
||||
boolean isOpaque(BlockFace blockFace);
|
||||
boolean isOpaque(RelFace blockFace);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -159,29 +163,29 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBlock(BlockRender block, Vec3i pos) {
|
||||
public void addBlock(BlockRender block, Vec3i relBlockInChunk) {
|
||||
if (!(block instanceof BlockOptimizedSurface))
|
||||
return;
|
||||
|
||||
BlockOptimizedSurface bos = (BlockOptimizedSurface) block;
|
||||
addBlock(pos, bos);
|
||||
addBlock(relBlockInChunk, bos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTile(TileRender tile, Vec3i pos, BlockFace face) {
|
||||
public void addTile(TileRender tile, Vec3i relBlockInChunk, RelFace face) {
|
||||
if (!(tile instanceof TileOptimizedSurface))
|
||||
return;
|
||||
|
||||
TileOptimizedSurface tos = (TileOptimizedSurface) tile;
|
||||
addTile(pos, face, tos);
|
||||
addTile(relBlockInChunk, face, tos);
|
||||
}
|
||||
|
||||
protected void addBlock(Vec3i pos, BlockOptimizedSurface block) {
|
||||
getBlock(pos).block = block;
|
||||
private void addBlock(Vec3i relBlockInChunk, BlockOptimizedSurface block) {
|
||||
getBlock(relBlockInChunk).block = block;
|
||||
}
|
||||
|
||||
private void addTile(Vec3i pos, BlockFace face, TileOptimizedSurface tile) {
|
||||
FaceInfo faceInfo = getFace(pos, face);
|
||||
private void addTile(Vec3i relBlockInChunk, RelFace face, TileOptimizedSurface tile) {
|
||||
FaceInfo faceInfo = getFace(relBlockInChunk, face);
|
||||
|
||||
int index = faceInfo.tileCount;
|
||||
faceInfo.tileCount++;
|
||||
@ -197,63 +201,58 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
|
||||
}
|
||||
}
|
||||
|
||||
protected BlockInfo getBlock(Vec3i cursor) {
|
||||
return data[cursor.x][cursor.y][cursor.z];
|
||||
protected BlockInfo getBlock(Vec3i relBlockInChunk) {
|
||||
return data[relBlockInChunk.x][relBlockInChunk.y][relBlockInChunk.z];
|
||||
}
|
||||
|
||||
protected FaceInfo getFace(Vec3i cursor, BlockFace face) {
|
||||
return getBlock(cursor).faces[face.getId()];
|
||||
protected FaceInfo getFace(Vec3i relBlockInChunk, RelFace face) {
|
||||
return getBlock(relBlockInChunk).faces[face.getId()];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Renderable endRender() {
|
||||
Collection<Face> shapeFaces = new ArrayList<>(
|
||||
Collection<ShapePart> shapeParts = new ArrayList<>(
|
||||
BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * 3
|
||||
);
|
||||
|
||||
Vec3i cursor = new Vec3i();
|
||||
Consumer<Face> consumer = shapeFaces::add;
|
||||
Consumer<ShapePart> consumer = shapeParts::add;
|
||||
|
||||
for (cursor.x = 0; cursor.x < BLOCKS_PER_CHUNK; ++cursor.x) {
|
||||
for (cursor.y = 0; cursor.y < BLOCKS_PER_CHUNK; ++cursor.y) {
|
||||
for (cursor.z = 0; cursor.z < BLOCKS_PER_CHUNK; ++cursor.z) {
|
||||
processInnerFaces(cursor, consumer);
|
||||
processOuterFaces(cursor, consumer);
|
||||
}
|
||||
}
|
||||
}
|
||||
GenericChunks.forEachBiC(relBlockInChunk -> {
|
||||
processInnerFaces(relBlockInChunk, consumer);
|
||||
processOuterFaces(relBlockInChunk, consumer);
|
||||
});
|
||||
|
||||
if (shapeFaces.isEmpty()) {
|
||||
if (shapeParts.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Shape(
|
||||
Usage.STATIC,
|
||||
WorldRenderProgram.getDefault(),
|
||||
shapeFaces.toArray(new Face[shapeFaces.size()])
|
||||
shapeParts.toArray(new ShapePart[shapeParts.size()])
|
||||
);
|
||||
}
|
||||
|
||||
private void processOuterFaces(
|
||||
Vec3i blockInChunk,
|
||||
Consumer<Face> output
|
||||
Vec3i relBlockInChunk,
|
||||
Consumer<ShapePart> output
|
||||
) {
|
||||
for (BlockFace blockFace : BlockFace.getFaces()) {
|
||||
processOuterFace(blockInChunk, blockFace, output);
|
||||
for (RelFace blockFace : RelFace.getFaces()) {
|
||||
processOuterFace(relBlockInChunk, blockFace, output);
|
||||
}
|
||||
}
|
||||
|
||||
private void processOuterFace(Vec3i blockInChunk, BlockFace blockFace, Consumer<Face> output) {
|
||||
if (!shouldRenderOuterFace(blockInChunk, blockFace))
|
||||
private void processOuterFace(Vec3i relBlockInChunk, RelFace blockFace, Consumer<ShapePart> output) {
|
||||
if (!shouldRenderOuterFace(relBlockInChunk, blockFace))
|
||||
return;
|
||||
|
||||
FaceInfo info = getFace(blockInChunk, blockFace);
|
||||
FaceInfo info = getFace(relBlockInChunk, blockFace);
|
||||
|
||||
if (info.tileCount == 0 && info.block.block == null)
|
||||
return;
|
||||
|
||||
Vec3 faceOrigin = new Vec3(blockInChunk.x, blockInChunk.y, blockInChunk.z);
|
||||
Vec3 offset = new Vec3(blockFace.getFloatVector()).mul(OVERLAY_OFFSET);
|
||||
Vec3 faceOrigin = new Vec3(relBlockInChunk.x, relBlockInChunk.y, relBlockInChunk.z);
|
||||
Vec3 offset = new Vec3(blockFace.getRelFloatVector()).mul(OVERLAY_OFFSET);
|
||||
|
||||
for (
|
||||
int layer = info.topOpaqueSurface;
|
||||
@ -264,32 +263,29 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
|
||||
if (surface == null)
|
||||
continue; // layer may be BLOCK_LAYER, then block may be null
|
||||
|
||||
surface.getFaces(chunk.getData(), blockInChunk, blockFace, false, output, faceOrigin);
|
||||
surface.getShapeParts(chunk.getData(), relBlockInChunk, blockFace, false, output, faceOrigin);
|
||||
|
||||
faceOrigin.add(offset);
|
||||
}
|
||||
}
|
||||
|
||||
private void processInnerFaces(
|
||||
Vec3i blockInChunk,
|
||||
Consumer<Face> output
|
||||
) {
|
||||
for (BlockFace blockFace : BlockFace.getFaces()) {
|
||||
processInnerFace(blockInChunk, blockFace, output);
|
||||
private void processInnerFaces(Vec3i relBlockInChunk, Consumer<ShapePart> output) {
|
||||
for (RelFace blockFace : RelFace.getFaces()) {
|
||||
processInnerFace(relBlockInChunk, blockFace, output);
|
||||
}
|
||||
}
|
||||
|
||||
private void processInnerFace(Vec3i blockInChunk, BlockFace blockFace, Consumer<Face> output) {
|
||||
if (!shouldRenderInnerFace(blockInChunk, blockFace))
|
||||
private void processInnerFace(Vec3i relBlockInChunk, RelFace blockFace, Consumer<ShapePart> output) {
|
||||
if (!shouldRenderInnerFace(relBlockInChunk, blockFace))
|
||||
return;
|
||||
|
||||
FaceInfo info = getFace(blockInChunk, blockFace);
|
||||
FaceInfo info = getFace(relBlockInChunk, blockFace);
|
||||
|
||||
if (info.tileCount == 0 && info.block.block == null)
|
||||
return;
|
||||
|
||||
Vec3 faceOrigin = new Vec3(blockInChunk.x, blockInChunk.y, blockInChunk.z);
|
||||
Vec3 offset = new Vec3(blockFace.getFloatVector()).mul(OVERLAY_OFFSET);
|
||||
Vec3 faceOrigin = new Vec3(relBlockInChunk.x, relBlockInChunk.y, relBlockInChunk.z);
|
||||
Vec3 offset = new Vec3(blockFace.getRelFloatVector()).mul(OVERLAY_OFFSET);
|
||||
|
||||
for (
|
||||
int layer = FaceInfo.BLOCK_LAYER;
|
||||
@ -300,35 +296,35 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
|
||||
if (surface == null)
|
||||
continue; // layer may be BLOCK_LAYER, then block may be null
|
||||
|
||||
surface.getFaces(chunk.getData(), blockInChunk, blockFace, true, output, faceOrigin);
|
||||
surface.getShapeParts(chunk.getData(), relBlockInChunk, blockFace, true, output, faceOrigin);
|
||||
|
||||
faceOrigin.add(offset);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldRenderOuterFace(Vec3i blockInChunk, BlockFace face) {
|
||||
blockInChunk.add(face.getVector());
|
||||
private boolean shouldRenderOuterFace(Vec3i relBlockInChunk, RelFace face) {
|
||||
relBlockInChunk.add(face.getRelVector());
|
||||
try {
|
||||
return shouldRenderWhenFacing(blockInChunk, face);
|
||||
return shouldRenderWhenFacing(relBlockInChunk, face);
|
||||
} finally {
|
||||
blockInChunk.sub(face.getVector());
|
||||
relBlockInChunk.sub(face.getRelVector());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldRenderInnerFace(Vec3i blockInChunk, BlockFace face) {
|
||||
return shouldRenderWhenFacing(blockInChunk, face);
|
||||
private boolean shouldRenderInnerFace(Vec3i relBlockInChunk, RelFace face) {
|
||||
return shouldRenderWhenFacing(relBlockInChunk, face);
|
||||
}
|
||||
|
||||
private boolean shouldRenderWhenFacing(Vec3i blockInChunk, BlockFace face) {
|
||||
if (chunk.containsBiC(blockInChunk)) {
|
||||
return shouldRenderWhenFacingLocal(blockInChunk, face);
|
||||
private boolean shouldRenderWhenFacing(Vec3i relBlockInChunk, RelFace face) {
|
||||
if (GenericChunks.containsBiC(relBlockInChunk)) {
|
||||
return shouldRenderWhenFacingLocal(relBlockInChunk, face);
|
||||
} else {
|
||||
return shouldRenderWhenFacingNeighbor(blockInChunk, face);
|
||||
return shouldRenderWhenFacingNeighbor(relBlockInChunk, face);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldRenderWhenFacingLocal(Vec3i blockInChunk, BlockFace face) {
|
||||
BlockOptimizedSurface block = getBlock(blockInChunk).block;
|
||||
private boolean shouldRenderWhenFacingLocal(Vec3i relBlockInChunk, RelFace face) {
|
||||
BlockOptimizedSurface block = getBlock(relBlockInChunk).block;
|
||||
|
||||
if (block == null) {
|
||||
return true;
|
||||
@ -340,36 +336,37 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean shouldRenderWhenFacingNeighbor(Vec3i blockInLocalChunk, BlockFace face) {
|
||||
Vec3i blockInChunk = Vectors.grab3i().set(blockInLocalChunk.x, blockInLocalChunk.y, blockInLocalChunk.z);
|
||||
private boolean shouldRenderWhenFacingNeighbor(Vec3i relBlockInLocalChunk, RelFace face) {
|
||||
Vec3i blockInChunk = Vectors.grab3i();
|
||||
chunk.resolve(relBlockInLocalChunk, blockInChunk);
|
||||
Vec3i chunkPos = Vectors.grab3i().set(chunk.getX(), chunk.getY(), chunk.getZ());
|
||||
|
||||
try {
|
||||
// Determine blockInChunk and chunkPos
|
||||
if (blockInLocalChunk.x == -1) {
|
||||
if (blockInChunk.x == -1) {
|
||||
blockInChunk.x = BLOCKS_PER_CHUNK - 1;
|
||||
chunkPos.x -= 1;
|
||||
} else if (blockInLocalChunk.x == BLOCKS_PER_CHUNK) {
|
||||
} else if (blockInChunk.x == BLOCKS_PER_CHUNK) {
|
||||
blockInChunk.x = 0;
|
||||
chunkPos.x += 1;
|
||||
} else if (blockInLocalChunk.y == -1) {
|
||||
} else if (blockInChunk.y == -1) {
|
||||
blockInChunk.y = BLOCKS_PER_CHUNK - 1;
|
||||
chunkPos.y -= 1;
|
||||
} else if (blockInLocalChunk.y == BLOCKS_PER_CHUNK) {
|
||||
} else if (blockInChunk.y == BLOCKS_PER_CHUNK) {
|
||||
blockInChunk.y = 0;
|
||||
chunkPos.y += 1;
|
||||
} else if (blockInLocalChunk.z == -1) {
|
||||
} else if (blockInChunk.z == -1) {
|
||||
blockInChunk.z = BLOCKS_PER_CHUNK - 1;
|
||||
chunkPos.z -= 1;
|
||||
} else if (blockInLocalChunk.z == BLOCKS_PER_CHUNK) {
|
||||
} else if (blockInChunk.z == BLOCKS_PER_CHUNK) {
|
||||
blockInChunk.z = 0;
|
||||
chunkPos.z += 1;
|
||||
} else {
|
||||
throw new AssertionError(
|
||||
"Requested incorrent neighbor ("
|
||||
+ blockInLocalChunk.x + "; "
|
||||
+ blockInLocalChunk.y + "; "
|
||||
+ blockInLocalChunk.z + ")"
|
||||
+ relBlockInLocalChunk.x + "; "
|
||||
+ relBlockInLocalChunk.y + "; "
|
||||
+ relBlockInLocalChunk.z + ")"
|
||||
);
|
||||
}
|
||||
|
||||
@ -382,8 +379,11 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
|
||||
return true;
|
||||
|
||||
BlockOptimizedSurface bos = (BlockOptimizedSurface) block;
|
||||
if (!bos.isOpaque(face))
|
||||
RelFace rotatedFace = face.rotate(this.chunk.getUp(), chunk.getUp());
|
||||
|
||||
if (!bos.isOpaque(rotatedFace)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
|
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Progressia
|
||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package ru.windcorp.progressia.client.world.entity;
|
||||
|
||||
import ru.windcorp.progressia.common.world.entity.EntityData;
|
||||
import ru.windcorp.progressia.common.world.entity.EntityDataPlayer;
|
||||
|
||||
public class EntityRenderPlayer extends EntityRender {
|
||||
|
||||
public EntityRenderPlayer(String id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityRenderable createRenderable(EntityData entity) {
|
||||
EntityDataPlayer playerEntity = (EntityDataPlayer) entity;
|
||||
|
||||
String speciesId = playerEntity.getSpecies().getId();
|
||||
SpeciesRender speciesRender = SpeciesRenderRegistry.getInstance().get(speciesId);
|
||||
|
||||
return speciesRender.createRenderable(playerEntity);
|
||||
}
|
||||
|
||||
}
|
@ -19,11 +19,11 @@
|
||||
package ru.windcorp.progressia.client.world.entity;
|
||||
|
||||
import glm.vec._3.Vec3;
|
||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||
import ru.windcorp.progressia.client.world.UpdatingRenderable;
|
||||
import ru.windcorp.progressia.common.world.entity.EntityData;
|
||||
import ru.windcorp.progressia.common.world.generic.GenericEntity;
|
||||
import ru.windcorp.progressia.common.world.generic.EntityGeneric;
|
||||
|
||||
public abstract class EntityRenderable implements Renderable, GenericEntity {
|
||||
public abstract class EntityRenderable extends UpdatingRenderable implements EntityGeneric {
|
||||
|
||||
private final EntityData data;
|
||||
|
||||
@ -45,7 +45,41 @@ public abstract class EntityRenderable implements Renderable, GenericEntity {
|
||||
return getData().getId();
|
||||
}
|
||||
|
||||
public void getViewPoint(Vec3 output) {
|
||||
@Override
|
||||
public long getEntityId() {
|
||||
return getData().getEntityId();
|
||||
}
|
||||
|
||||
public final Vec3 getLookingAt(Vec3 output) {
|
||||
if (output == null) output = new Vec3();
|
||||
updateIfNecessary();
|
||||
doGetLookingAt(output);
|
||||
return output;
|
||||
}
|
||||
|
||||
protected void doGetLookingAt(Vec3 output) {
|
||||
output.set(getData().getLookingAt());
|
||||
}
|
||||
|
||||
public final Vec3 getUpVector(Vec3 output) {
|
||||
if (output == null) output = new Vec3();
|
||||
updateIfNecessary();
|
||||
doGetUpVector(output);
|
||||
return output;
|
||||
}
|
||||
|
||||
protected void doGetUpVector(Vec3 output) {
|
||||
output.set(getData().getUpVector());
|
||||
}
|
||||
|
||||
public final Vec3 getViewPoint(Vec3 output) {
|
||||
if (output == null) output = new Vec3();
|
||||
updateIfNecessary();
|
||||
doGetViewPoint(output);
|
||||
return output;
|
||||
}
|
||||
|
||||
protected void doGetViewPoint(Vec3 output) {
|
||||
output.set(0, 0, 0);
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user