Compare commits
30 Commits
v0.0.0-tec
...
moduleSyst
Author | SHA1 | Date | |
---|---|---|---|
8c7c37c9c0 | |||
3b24ee0531 | |||
953614a7d8 | |||
7f57510ac8 | |||
c7842935ba | |||
f520d4b2c6 | |||
5a06788652 | |||
56e9be727b | |||
e795d76367 | |||
a9724d9d4c | |||
3859db5b27 | |||
513feb1093 | |||
531a8c99c3
|
|||
32851b8fb0
|
|||
c49fdfa5ff
|
|||
9d7f69892b
|
|||
73d24d36f4 | |||
bdb3bff570 | |||
6997bb1104 | |||
eac0a34516 | |||
127d1c3d87 | |||
26a35f306c
|
|||
52f3f653d8 | |||
fc85eb5658
|
|||
260562310a
|
|||
85edc07c75 | |||
fcf225f9c7 | |||
942c665d73 | |||
5a521fc131 | |||
361d795ab1 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -30,8 +30,11 @@ build_packages/*
|
||||
!build_packages/NSIS
|
||||
build_packages/NSIS/*
|
||||
!build_packages/NSIS/ProgressiaInstaller.nsi
|
||||
!build_packages/NSIS/logo.ico
|
||||
!build_packages/NSIS/left_side.bmp
|
||||
|
||||
# ... and except build_packages/DEB/template
|
||||
!build_packages/DEB
|
||||
build_packages/DEB/*
|
||||
!build_packages/DEB/template
|
||||
*.log
|
||||
|
@ -16,7 +16,7 @@ temperature mechanics and a parallelism-capable server.
|
||||
- GNU/Linux (x64, arm32 or arm64), Windows XP or later (x64 or x86) or MacOS (x64)
|
||||
- Java 8 or later
|
||||
- OpenGL 2.1 or later
|
||||
- Probably at least 4 GiB RAM
|
||||
- Probably about 0.5 GiB RAM
|
||||
- Less than 1 GiB of storage space
|
||||
|
||||
See [Build Guide](docs/building/BuildGuide.md) for compilation requirements.
|
||||
|
@ -111,6 +111,8 @@ switch (OperatingSystem.current()) {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation 'org.jetbrains:annotations:20.1.0'
|
||||
implementation platform("org.lwjgl:lwjgl-bom:$lwjglVersion")
|
||||
|
||||
implementation "org.lwjgl:lwjgl"
|
||||
|
@ -128,6 +128,7 @@ buildWindowsInstaller() {
|
||||
{
|
||||
cp -r 'build/libs/lib' 'build_packages/NSIS/lib' &&
|
||||
cp 'build/libs/Progressia.jar' 'build_packages/NSIS/Progressia.jar' &&
|
||||
cp 'LICENSE' 'build_packages/NSIS/LICENSE.txt' &&
|
||||
echo "------ NSIS ------" &&
|
||||
makensis "$configurationFile" &&
|
||||
echo "---- NSIS END ----" &&
|
||||
@ -144,6 +145,9 @@ buildWindowsInstaller() {
|
||||
if [ -e 'build_packages/NSIS/Progressia.jar' ]; then
|
||||
rm 'build_packages/NSIS/Progressia.jar'
|
||||
fi
|
||||
if [ -e 'build_packages/NSIS/LICENSE.txt' ]; then
|
||||
rm 'build_packages/NSIS/LICENSE.txt'
|
||||
fi
|
||||
echo "Cleaned up"
|
||||
} || {
|
||||
echoerr "Could not clean up after building Windows installer"
|
||||
|
@ -10,16 +10,32 @@
|
||||
;--------------------------------
|
||||
;General
|
||||
|
||||
!define PROJECT_NAME "Progressia"
|
||||
|
||||
; MUI Settings / Icons
|
||||
!define MUI_ICON "logo.ico"
|
||||
;!define MUI_UNICON ;Uninstall icon
|
||||
|
||||
; MUI Settings / Header
|
||||
; !define MUI_HEADERIMAGE
|
||||
; !define MUI_HEADERIMAGE_RIGHT
|
||||
; !define MUI_HEADERIMAGE_BITMAP "${NSISDIR}\Contrib\Graphics\Header\orange-r-nsis.bmp"
|
||||
; !define MUI_HEADERIMAGE_UNBITMAP "${NSISDIR}\Contrib\Graphics\Header\orange-uninstall-r-nsis.bmp"
|
||||
|
||||
; MUI Settings / Wizard
|
||||
!define MUI_WELCOMEFINISHPAGE_BITMAP "left_side.bmp"
|
||||
!define MUI_UNWELCOMEFINISHPAGE_BITMAP "left_side.bmp"
|
||||
|
||||
;Name and file
|
||||
Name "Progressia"
|
||||
OutFile "ProgressiaInstaller.exe"
|
||||
Name "${PROJECT_NAME}"
|
||||
OutFile "${PROJECT_NAME}Installer.exe"
|
||||
Unicode True
|
||||
|
||||
;Default installation folder
|
||||
InstallDir "$PROGRAMFILES\Progressia"
|
||||
InstallDir "$PROGRAMFILES\${PROJECT_NAME}"
|
||||
|
||||
;Get installation folder from registry if available
|
||||
InstallDirRegKey HKLM "Software\Progressia" "Install_Dir"
|
||||
InstallDirRegKey HKLM "Software\${PROJECT_NAME}" ""
|
||||
|
||||
;Request application privileges for Windows Vista
|
||||
RequestExecutionLevel admin
|
||||
@ -33,14 +49,18 @@
|
||||
;Pages
|
||||
|
||||
!insertmacro MUI_PAGE_WELCOME
|
||||
;!insertmacro MUI_PAGE_LICENSE "${NSISDIR}\Docs\Modern UI\License.txt"
|
||||
!insertmacro MUI_PAGE_LICENSE "LICENSE.txt"
|
||||
!insertmacro MUI_PAGE_COMPONENTS
|
||||
!insertmacro MUI_PAGE_DIRECTORY
|
||||
!insertmacro MUI_PAGE_INSTFILES
|
||||
!define MUI_FINISHPAGE_RUN
|
||||
!define MUI_FINISHPAGE_RUN_TEXT "Start ${PROJECT_NAME}"
|
||||
!define MUI_FINISHPAGE_RUN_FUNCTION "LaunchLink"
|
||||
!insertmacro MUI_PAGE_FINISH
|
||||
|
||||
!insertmacro MUI_UNPAGE_WELCOME
|
||||
!insertmacro MUI_UNPAGE_CONFIRM
|
||||
!insertmacro MUI_UNPAGE_COMPONENTS
|
||||
!insertmacro MUI_UNPAGE_INSTFILES
|
||||
!insertmacro MUI_UNPAGE_FINISH
|
||||
|
||||
@ -52,12 +72,15 @@
|
||||
;--------------------------------
|
||||
;Installer Sections
|
||||
|
||||
Section "Install Progressia" SecDummy
|
||||
Section "Install ${PROJECT_NAME}" SEC0000
|
||||
|
||||
SectionIn RO ;Make it read-only
|
||||
SetOutPath "$INSTDIR"
|
||||
SetOverwrite on
|
||||
|
||||
;Files
|
||||
File Progressia.jar
|
||||
File logo.ico
|
||||
File /r lib
|
||||
|
||||
;Store installation folder
|
||||
@ -65,22 +88,24 @@ Section "Install Progressia" SecDummy
|
||||
|
||||
;Create uninstaller
|
||||
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Progressia" "DisplayName" "Progressia (remove only)"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Progressia" "UninstallString" "$INSTDIR\Uninstall.exe"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PROJECT_NAME}" "DisplayName" "${PROJECT_NAME} (remove only)"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PROJECT_NAME}" "UninstallString" "$INSTDIR\Uninstall.exe"
|
||||
WriteUninstaller "$INSTDIR\Uninstall.exe"
|
||||
|
||||
SectionEnd
|
||||
|
||||
;--------------------------------
|
||||
;Descriptions
|
||||
Section "Create Desktop Shortcut" SEC0001
|
||||
SetOutPath "$APPDATA\${PROJECT_NAME}"
|
||||
CreateShortCut "$DESKTOP\${PROJECT_NAME}.lnk" "$INSTDIR\${PROJECT_NAME}.jar" "" "$INSTDIR\logo.ico"
|
||||
SectionEnd
|
||||
|
||||
;Language strings
|
||||
LangString DESC_SecDummy ${LANG_ENGLISH} "A test section."
|
||||
Section "Start Menu Shortcuts" SEC0002
|
||||
|
||||
;Assign language strings to sections
|
||||
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
|
||||
!insertmacro MUI_DESCRIPTION_TEXT ${SecDummy} $(DESC_SecDummy)
|
||||
!insertmacro MUI_FUNCTION_DESCRIPTION_END
|
||||
CreateDirectory "$SMPROGRAMS\${PROJECT_NAME}"
|
||||
CreateShortcut "$SMPROGRAMS\${PROJECT_NAME}\Uninstall.lnk" "$INSTDIR\Uninstall.exe"
|
||||
CreateShortcut "$SMPROGRAMS\${PROJECT_NAME}\${PROJECT_NAME}.lnk" "$INSTDIR\${PROJECT_NAME}.jar" "" "$INSTDIR\logo.ico"
|
||||
|
||||
SectionEnd
|
||||
|
||||
;--------------------------------
|
||||
;Uninstaller Section
|
||||
@ -92,9 +117,45 @@ Section "Uninstall"
|
||||
Delete $INSTDIR\Uninstall.exe
|
||||
Delete $INSTDIR\Progressia.jar
|
||||
Delete $INSTDIR\lib\*.*
|
||||
Delete $INSTDIR\logo.ico
|
||||
|
||||
RMDir /r "$INSTDIR"
|
||||
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Progressia"
|
||||
DeleteRegKey HKLM "Software\Progressia"
|
||||
RMDir $INSTDIR\lib
|
||||
|
||||
Delete $DESKTOP\${PROJECT_NAME}.lnk
|
||||
|
||||
Delete $SMPROGRAMS\${PROJECT_NAME}\Uninstall.lnk
|
||||
Delete $SMPROGRAMS\${PROJECT_NAME}\${PROJECT_NAME}.lnk
|
||||
|
||||
RMDir $INSTDIR
|
||||
|
||||
RMDir /r $SMPROGRAMS\${PROJECT_NAME}
|
||||
|
||||
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PROJECT_NAME}"
|
||||
DeleteRegKey HKLM "Software\${PROJECT_NAME}"
|
||||
|
||||
SectionEnd
|
||||
|
||||
Section "un.Remove user data"
|
||||
|
||||
RMDir /r "$APPDATA\${PROJECT_NAME}"
|
||||
|
||||
SectionEnd
|
||||
|
||||
;--------------------------------
|
||||
;Functions
|
||||
|
||||
Function LaunchLink
|
||||
SetOutPath "$APPDATA\${PROJECT_NAME}"
|
||||
ExecShell "" "$INSTDIR\${PROJECT_NAME}.jar"
|
||||
FunctionEnd
|
||||
|
||||
;--------------------------------
|
||||
;Descriptions
|
||||
|
||||
;Language strings
|
||||
LangString DESC_SecDummy ${LANG_ENGLISH} "Install ${PROJECT_NAME}."
|
||||
|
||||
;Assign language strings to sections
|
||||
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
|
||||
!insertmacro MUI_DESCRIPTION_TEXT ${SEC0000} $(DESC_SecDummy)
|
||||
!insertmacro MUI_FUNCTION_DESCRIPTION_END
|
BIN
build_packages/NSIS/left_side.bmp
Normal file
BIN
build_packages/NSIS/left_side.bmp
Normal file
Binary file not shown.
After Width: | Height: | Size: 151 KiB |
BIN
build_packages/NSIS/logo.ico
Normal file
BIN
build_packages/NSIS/logo.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 187 KiB |
69
docs/CONTRIBUTING.md
Normal file
69
docs/CONTRIBUTING.md
Normal file
@ -0,0 +1,69 @@
|
||||
# Contributing Guidelines
|
||||
|
||||
This document lists conventions adopted by Progressia developers.
|
||||
|
||||
## git
|
||||
|
||||
### Branches
|
||||
Progressia repository contains a `master` branch and several "feature" branches.
|
||||
|
||||
`master` is expected to contain a version of the game suitable for demonstration and forking/branching. Do not commit directly to `master` without OLEGSHA's approval.
|
||||
- `master` must always correctly build without compiler warnings (see below).
|
||||
- `master` must always pass all unit tests.
|
||||
- `master` must always be able to launch successfully.
|
||||
- `master` must always only contain working features.
|
||||
- `master` should not contain excessive debug code.
|
||||
- `master` must always have its code and filenames formatted (see below).
|
||||
|
||||
"Feature" branches are branches dedicated to the development of a single feature. When the feature reaches completion the branch is merged into `master` and removed. Intermediate merges into `master` may occur when some fitting milestone is reached. Intermediate merges from `master` may be done as necessary. Merges between "feature" branches should generally be avoided.
|
||||
|
||||
When beginning work on a new feature, create a new branch based on `master` (or on another "feature" branch if absolutely necessary). Use `all-small-with-dashes` to name the branch: `add-trees` or `rebalance-plastics` are good names. Do not fix unrelated bugs or work on unrelated features in a "feature" branch - create a new one, switch to an existing one or commit directly to `master` if the changes are small enough.
|
||||
|
||||
"Feature" branches may not be formatted properly. Formatting is required before merging into `master` or other branches.
|
||||
|
||||
### Commits
|
||||
- Commits must leave the branch in a state that builds without compiler warnings (see below).
|
||||
- Changes should be grouped in commits semantically. Avoid committing many small related changes in sequence; if necessary, wait and accumulate them. Avoid committing unrelated changes together; if necessary, split staged changes into several commits. This should normally result in about 1-2 commits for a day's work.
|
||||
- Commit bulk changes (renaming, formatting, ...) separately. Don't ever commit whitespace changes outside formatting commits.
|
||||
- Message format:
|
||||
|
||||
```
|
||||
Short description of changes
|
||||
<empty line>
|
||||
- Enumeration of changes
|
||||
- Nest when appropriate
|
||||
- Use dashes only
|
||||
- List not needed for small commits
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
Changed packages for relations, renamed Face to ShapePart
|
||||
|
||||
- Added BlockRelation as an abstract superclass to existing relations
|
||||
- Must be given an absolute "up" direction before use
|
||||
- Moved AbsFace, AbsRelation and BlockRelation to .world.rels
|
||||
- Renamed Face to ShapePart to reduce confusion with AbsFace
|
||||
```
|
||||
|
||||
- Only commit changes described in the commit message. Please double-check staged files before committing.
|
||||
- Avoid merge conflicts. Pull before committing.
|
||||
- Better sign commits than not. See: [git](https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work), [GitHub](https://docs.github.com/en/github/authenticating-to-github/managing-commit-signature-verification).
|
||||
|
||||
## Code
|
||||
|
||||
### Warnings
|
||||
Make sure that all committed code contains no compiler warnings. This specifically includes unused imports, unused private members, missing `@Override`s and warnings related to generics.
|
||||
|
||||
Warnings about unknown tokens in `@SuppressWarnings` are temporarily ignored. Please disable them in your IDE.
|
||||
|
||||
### Code Style
|
||||
Formatting code is important.
|
||||
|
||||
- The format is specified within the files inside `/templates_and_presets/eclipse_ide`. Import the specifications into Eclipse or IntelliJ IDEA and use the IDEs' format feature. Alternatively format the code manually in accordance with existing files.
|
||||
- Only use tabs for indentation. Never indent with spaces even when wrapping lines. This is to ensure that indentation does not break when tab width is different.
|
||||
- Don't use `I` prefix for interfaces (not `IDoable` but `Doable`).
|
||||
- Prioritize readability over compactness. Do not hesitate to use (very) long identifiers if they aid comprehension.
|
||||
- Document all mathematics unless it is trivial, especially when using math notation for variable names.
|
||||
- Use proper English when writing comments. Avoid boxes in comments. Use `//` for single-line comments.
|
@ -40,6 +40,7 @@ public class ProgressiaLauncher {
|
||||
CrashReports.registerProvider(new OpenALContextProvider());
|
||||
CrashReports.registerProvider(new ArgsContextProvider());
|
||||
CrashReports.registerProvider(new LanguageContextProvider());
|
||||
CrashReports.registerProvider(new ScreenContextProvider());
|
||||
// Analyzers
|
||||
CrashReports.registerAnalyzer(new OutOfMemoryAnalyzer());
|
||||
|
||||
|
@ -28,10 +28,12 @@ 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.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;
|
||||
|
||||
public class ClientProxy implements Proxy {
|
||||
|
||||
@ -56,9 +58,13 @@ public class ClientProxy implements Proxy {
|
||||
Atlases.loadAllAtlases();
|
||||
|
||||
AudioSystem.initialize();
|
||||
TaskManager.getInstance().startLoading();
|
||||
|
||||
|
||||
ServerState.startServer();
|
||||
ClientState.connectToLocalServer();
|
||||
|
||||
TestMusicPlayer.start();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import ru.windcorp.progressia.client.audio.backend.AudioReader;
|
||||
import ru.windcorp.progressia.client.audio.backend.Listener;
|
||||
import ru.windcorp.progressia.client.audio.backend.SoundType;
|
||||
import ru.windcorp.progressia.client.audio.backend.Speaker;
|
||||
import ru.windcorp.progressia.common.resource.Resource;
|
||||
|
||||
import static org.lwjgl.openal.AL11.*;
|
||||
import static org.lwjgl.openal.ALC10.*;
|
||||
@ -40,7 +41,6 @@ public class AudioManager {
|
||||
|
||||
private static List<Speaker> soundSpeakers = new ArrayList<>(SOUNDS_NUM);
|
||||
private static Speaker musicSpeaker;
|
||||
private static ArrayList<SoundType> soundsBuffer = new ArrayList<>();
|
||||
|
||||
public static void initAL() {
|
||||
String defaultDeviceName = alcGetString(
|
||||
@ -82,31 +82,19 @@ public class AudioManager {
|
||||
return speaker;
|
||||
}
|
||||
|
||||
private static SoundType findSoundType(String soundID) throws Exception {
|
||||
for (SoundType s : soundsBuffer) {
|
||||
if (s.getId().equals(soundID)) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
throw new Exception(
|
||||
"ERROR: The selected sound is not loaded or" +
|
||||
" not exists"
|
||||
);
|
||||
}
|
||||
|
||||
public static Speaker initSpeaker(String soundID) {
|
||||
public static Speaker initSpeaker(SoundType st) {
|
||||
Speaker speaker = getLastSpeaker();
|
||||
try {
|
||||
findSoundType(soundID).initSpeaker(speaker);
|
||||
st.initSpeaker(speaker);
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
return speaker;
|
||||
}
|
||||
|
||||
public static Speaker initMusicSpeaker(String soundID) {
|
||||
public static Speaker initMusicSpeaker(SoundType st) {
|
||||
try {
|
||||
findSoundType(soundID).initSpeaker(musicSpeaker);
|
||||
st.initSpeaker(musicSpeaker);
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
@ -120,11 +108,11 @@ public class AudioManager {
|
||||
}
|
||||
}
|
||||
|
||||
public static void loadSound(String path, String id, AudioFormat format) {
|
||||
public static void loadSound(Resource resource, String id, AudioFormat format) {
|
||||
if (format == AudioFormat.MONO) {
|
||||
soundsBuffer.add(AudioReader.readAsMono(path, id));
|
||||
AudioRegistry.getInstance().register(AudioReader.readAsMono(resource, id));
|
||||
} else {
|
||||
soundsBuffer.add(AudioReader.readAsStereo(path, id));
|
||||
AudioRegistry.getInstance().register(AudioReader.readAsStereo(resource, id));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.audio;
|
||||
|
||||
import ru.windcorp.progressia.client.audio.backend.SoundType;
|
||||
import ru.windcorp.progressia.common.util.namespaces.NamespacedInstanceRegistry;
|
||||
|
||||
public class AudioRegistry extends NamespacedInstanceRegistry<SoundType> {
|
||||
|
||||
private static final AudioRegistry INSTANCE = new AudioRegistry();
|
||||
|
||||
/**
|
||||
* @return the instance
|
||||
*/
|
||||
public static AudioRegistry getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
}
|
@ -18,17 +18,33 @@
|
||||
|
||||
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() {
|
||||
AudioManager.loadSound(
|
||||
"assets/sounds/block_destroy_clap.ogg",
|
||||
ResourceManager.getResource("assets/sounds/block_destroy_clap.ogg"),
|
||||
"Progressia:BlockDestroy",
|
||||
AudioFormat.MONO
|
||||
);
|
||||
|
@ -19,72 +19,37 @@
|
||||
package ru.windcorp.progressia.client.audio;
|
||||
|
||||
import glm.vec._3.Vec3;
|
||||
import ru.windcorp.progressia.client.audio.backend.SoundType;
|
||||
import ru.windcorp.progressia.client.audio.backend.Speaker;
|
||||
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
|
||||
|
||||
public class Music extends Namespaced {
|
||||
private Vec3 position = new Vec3();
|
||||
private Vec3 velocity = new Vec3();
|
||||
private float pitch = 1.0f;
|
||||
private float gain = 1.0f;
|
||||
public class Music
|
||||
extends Sound {
|
||||
|
||||
|
||||
|
||||
public Music(SoundType soundType, int timeLength, float pitch, float gain) {
|
||||
super(soundType, timeLength, new Vec3(), new Vec3(), pitch, gain);
|
||||
}
|
||||
|
||||
public Music(SoundType soundType) {
|
||||
super(soundType);
|
||||
}
|
||||
|
||||
public Music(String id, int timeLength, float pitch, float gain) {
|
||||
super(id, timeLength, new Vec3(), new Vec3(), pitch, gain);
|
||||
}
|
||||
|
||||
public Music(String id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
public Music(
|
||||
String id,
|
||||
Vec3 position,
|
||||
Vec3 velocity,
|
||||
float pitch,
|
||||
float gain
|
||||
) {
|
||||
this(id);
|
||||
this.position = position;
|
||||
this.velocity = velocity;
|
||||
this.pitch = pitch;
|
||||
this.gain = gain;
|
||||
@Override
|
||||
protected Speaker initSpeaker() {
|
||||
return AudioManager.initMusicSpeaker(soundType);
|
||||
}
|
||||
|
||||
public void play(boolean loop) {
|
||||
Speaker speaker = AudioManager.initMusicSpeaker(this.getId());
|
||||
speaker.setGain(gain);
|
||||
speaker.setPitch(pitch);
|
||||
speaker.setPosition(position);
|
||||
speaker.setVelocity(velocity);
|
||||
|
||||
if (loop) {
|
||||
speaker.playLoop();
|
||||
} else {
|
||||
speaker.play();
|
||||
}
|
||||
}
|
||||
|
||||
public void setGain(float gain) {
|
||||
this.gain = gain;
|
||||
}
|
||||
|
||||
public void setPitch(float pitch) {
|
||||
this.pitch = pitch;
|
||||
}
|
||||
|
||||
public void setVelocity(Vec3 velocity) {
|
||||
this.velocity = velocity;
|
||||
}
|
||||
|
||||
public Vec3 getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public float getGain() {
|
||||
return gain;
|
||||
}
|
||||
|
||||
public Vec3 getVelocity() {
|
||||
return velocity;
|
||||
}
|
||||
|
||||
public float getPitch() {
|
||||
return pitch;
|
||||
@Override
|
||||
public void setPosition(Vec3 position) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
@ -19,23 +19,30 @@
|
||||
package ru.windcorp.progressia.client.audio;
|
||||
|
||||
import glm.vec._3.Vec3;
|
||||
import ru.windcorp.progressia.client.audio.backend.SoundType;
|
||||
import ru.windcorp.progressia.client.audio.backend.Speaker;
|
||||
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
|
||||
|
||||
public class SoundEffect
|
||||
extends Namespaced {
|
||||
public class Sound {
|
||||
|
||||
private Vec3 position = new Vec3();
|
||||
private Vec3 velocity = new Vec3();
|
||||
private float pitch = 1.0f;
|
||||
private float gain = 1.0f;
|
||||
protected Vec3 position = new Vec3(0f, 0f, 0f);
|
||||
protected Vec3 velocity = new Vec3(0f, 0f, 0f);
|
||||
protected float pitch = 1.0f;
|
||||
protected float gain = 1.0f;
|
||||
protected int timeLength = 0;
|
||||
|
||||
public SoundEffect(String id) {
|
||||
super(id);
|
||||
protected SoundType soundType;
|
||||
|
||||
public Sound(SoundType soundType) {
|
||||
this.soundType = soundType;
|
||||
}
|
||||
|
||||
public SoundEffect(
|
||||
public Sound(String id) {
|
||||
this(AudioRegistry.getInstance().get(id));
|
||||
}
|
||||
|
||||
public Sound(
|
||||
String id,
|
||||
int timeLength,
|
||||
Vec3 position,
|
||||
Vec3 velocity,
|
||||
float pitch,
|
||||
@ -48,8 +55,27 @@ public class SoundEffect
|
||||
this.gain = gain;
|
||||
}
|
||||
|
||||
public Sound(
|
||||
SoundType soundType,
|
||||
int timeLength,
|
||||
Vec3 position,
|
||||
Vec3 velocity,
|
||||
float pitch,
|
||||
float gain
|
||||
) {
|
||||
this(soundType);
|
||||
this.position = position;
|
||||
this.velocity = velocity;
|
||||
this.pitch = pitch;
|
||||
this.gain = gain;
|
||||
}
|
||||
|
||||
protected Speaker initSpeaker() {
|
||||
return AudioManager.initSpeaker(soundType);
|
||||
}
|
||||
|
||||
public void play(boolean loop) {
|
||||
Speaker speaker = AudioManager.initSpeaker(this.getId());
|
||||
Speaker speaker = initSpeaker();
|
||||
speaker.setGain(gain);
|
||||
speaker.setPitch(pitch);
|
||||
speaker.setPosition(position);
|
||||
@ -93,4 +119,9 @@ public class SoundEffect
|
||||
public float getPitch() {
|
||||
return pitch;
|
||||
}
|
||||
|
||||
public double getDuration() {
|
||||
return soundType.getDuration();
|
||||
}
|
||||
|
||||
}
|
@ -33,13 +33,11 @@ public class AudioReader {
|
||||
}
|
||||
|
||||
// TODO fix converting from mono-stereo
|
||||
private static SoundType readAsSpecified(String path, String id, int format) {
|
||||
private static SoundType readAsSpecified(Resource resource, String id, int format) {
|
||||
IntBuffer channelBuffer = BufferUtils.createIntBuffer(1);
|
||||
IntBuffer rateBuffer = BufferUtils.createIntBuffer(1);
|
||||
|
||||
Resource res = ResourceManager.getResource(path);
|
||||
|
||||
ShortBuffer rawAudio = decodeVorbis(res, channelBuffer, rateBuffer);
|
||||
ShortBuffer rawAudio = decodeVorbis(resource, channelBuffer, rateBuffer);
|
||||
|
||||
return new SoundType(
|
||||
id,
|
||||
@ -49,12 +47,12 @@ public class AudioReader {
|
||||
);
|
||||
}
|
||||
|
||||
public static SoundType readAsMono(String path, String id) {
|
||||
return readAsSpecified(path, id, AL_FORMAT_MONO16);
|
||||
public static SoundType readAsMono(Resource resource, String id) {
|
||||
return readAsSpecified(resource, id, AL_FORMAT_MONO16);
|
||||
}
|
||||
|
||||
public static SoundType readAsStereo(String path, String id) {
|
||||
return readAsSpecified(path, id, AL_FORMAT_STEREO16);
|
||||
public static SoundType readAsStereo(Resource resource, String id) {
|
||||
return readAsSpecified(resource, id, AL_FORMAT_STEREO16);
|
||||
}
|
||||
|
||||
private static ShortBuffer decodeVorbis(
|
||||
|
@ -21,6 +21,9 @@ package ru.windcorp.progressia.client.audio.backend;
|
||||
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
|
||||
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
import org.lwjgl.openal.AL10;
|
||||
|
||||
import static org.lwjgl.openal.AL11.*;
|
||||
|
||||
public class SoundType extends Namespaced {
|
||||
@ -29,6 +32,7 @@ public class SoundType extends Namespaced {
|
||||
private int sampleRate;
|
||||
private int format;
|
||||
private int audioBuffer;
|
||||
private double duration;
|
||||
|
||||
public SoundType(
|
||||
String id,
|
||||
@ -46,9 +50,14 @@ public class SoundType extends Namespaced {
|
||||
private void createAudioBuffer() {
|
||||
this.audioBuffer = alGenBuffers();
|
||||
alBufferData(audioBuffer, format, rawAudio, sampleRate);
|
||||
duration = rawAudio.limit() / (double) sampleRate / (format == AL10.AL_FORMAT_STEREO16 ? 2 : 1);
|
||||
}
|
||||
|
||||
public void initSpeaker(Speaker speaker) {
|
||||
speaker.setAudioData(audioBuffer);
|
||||
}
|
||||
|
||||
public double getDuration() {
|
||||
return duration;
|
||||
}
|
||||
}
|
@ -120,6 +120,7 @@ public class Speaker {
|
||||
}
|
||||
|
||||
public void setAudioData(int audioData) {
|
||||
stop();
|
||||
this.audioData = audioData;
|
||||
alSourcei(this.sourceData, AL_BUFFER, audioData);
|
||||
}
|
||||
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.comms.controls;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||
import ru.windcorp.progressia.common.comms.controls.PacketControl;
|
||||
|
||||
public class ControlTriggerLocalLambda extends ControlTriggerInputBased {
|
||||
|
||||
private final Predicate<InputEvent> predicate;
|
||||
private final Consumer<InputEvent> action;
|
||||
|
||||
public ControlTriggerLocalLambda(
|
||||
String id,
|
||||
Predicate<InputEvent> predicate,
|
||||
Consumer<InputEvent> action
|
||||
) {
|
||||
super(id);
|
||||
|
||||
this.predicate = predicate;
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketControl onInputEvent(InputEvent event) {
|
||||
if (!predicate.test(event))
|
||||
return null;
|
||||
|
||||
action.accept(event);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -143,6 +143,96 @@ public class ControlTriggers {
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
///
|
||||
///
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
public static ControlTriggerInputBased localOf(
|
||||
String id,
|
||||
Consumer<InputEvent> action,
|
||||
Predicate<InputEvent> predicate
|
||||
) {
|
||||
return new ControlTriggerLocalLambda(id, predicate, action);
|
||||
}
|
||||
|
||||
public static ControlTriggerInputBased localOf(
|
||||
String id,
|
||||
Runnable action,
|
||||
Predicate<InputEvent> predicate
|
||||
) {
|
||||
return localOf(
|
||||
id,
|
||||
input -> action.run(),
|
||||
predicate
|
||||
);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <I extends InputEvent> ControlTriggerInputBased localOf(
|
||||
String id,
|
||||
Class<I> inputType,
|
||||
Consumer<I> action,
|
||||
Predicate<I>... predicates
|
||||
) {
|
||||
return localOf(
|
||||
id,
|
||||
createCheckedAction(inputType, action),
|
||||
createCheckedCompoundPredicate(inputType, predicates)
|
||||
);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <I extends InputEvent> ControlTriggerInputBased localOf(
|
||||
String id,
|
||||
Class<I> inputType,
|
||||
Runnable action,
|
||||
Predicate<I>... predicates
|
||||
) {
|
||||
return localOf(
|
||||
id,
|
||||
inputType,
|
||||
input -> action.run(),
|
||||
predicates
|
||||
);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static ControlTriggerInputBased localOf(
|
||||
String id,
|
||||
Consumer<InputEvent> action,
|
||||
Predicate<InputEvent>... predicates
|
||||
) {
|
||||
return localOf(
|
||||
id,
|
||||
InputEvent.class,
|
||||
action,
|
||||
predicates
|
||||
);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <I extends InputEvent> ControlTriggerInputBased localOf(
|
||||
String id,
|
||||
Runnable action,
|
||||
Predicate<InputEvent>... predicates
|
||||
) {
|
||||
return of(
|
||||
id,
|
||||
input -> action.run(),
|
||||
predicates
|
||||
);
|
||||
}
|
||||
|
||||
private static <I extends InputEvent> BiConsumer<InputEvent, ControlData> createCheckedDataWriter(
|
||||
Class<I> inputType,
|
||||
BiConsumer<I, ControlData> dataWriter
|
||||
@ -150,6 +240,13 @@ public class ControlTriggers {
|
||||
return (inputEvent, control) -> dataWriter.accept(inputType.cast(inputEvent), control);
|
||||
}
|
||||
|
||||
private static <I extends InputEvent> Consumer<InputEvent> createCheckedAction(
|
||||
Class<I> inputType,
|
||||
Consumer<I> action
|
||||
) {
|
||||
return inputEvent -> action.accept(inputType.cast(inputEvent));
|
||||
}
|
||||
|
||||
private static <I extends InputEvent> Predicate<InputEvent> createCheckedCompoundPredicate(
|
||||
Class<I> inputType,
|
||||
Predicate<I>[] predicates
|
||||
|
@ -18,11 +18,11 @@
|
||||
|
||||
package ru.windcorp.progressia.client.graphics.backend;
|
||||
|
||||
import static org.lwjgl.opengl.GL11.*;
|
||||
|
||||
import glm.vec._2.i.Vec2i;
|
||||
import org.lwjgl.glfw.GLFWVidMode;
|
||||
|
||||
import static org.lwjgl.glfw.GLFW.*;
|
||||
import static org.lwjgl.opengl.GL11.*;
|
||||
|
||||
public class GraphicsBackend {
|
||||
|
||||
@ -38,9 +38,30 @@ public class GraphicsBackend {
|
||||
|
||||
private static boolean faceCullingEnabled = false;
|
||||
|
||||
private static boolean isFullscreen = false;
|
||||
private static boolean vSyncEnabled = false;
|
||||
private static boolean isGLFWInitialized = false;
|
||||
private static boolean isOpenGLInitialized = false;
|
||||
|
||||
private GraphicsBackend() {
|
||||
}
|
||||
|
||||
public static boolean isGLFWInitialized() {
|
||||
return isGLFWInitialized;
|
||||
}
|
||||
|
||||
static void setGLFWInitialized(boolean isGLFWInitialized) {
|
||||
GraphicsBackend.isGLFWInitialized = isGLFWInitialized;
|
||||
}
|
||||
|
||||
public static boolean isOpenGLInitialized() {
|
||||
return isOpenGLInitialized;
|
||||
}
|
||||
|
||||
static void setOpenGLInitialized(boolean isOpenGLInitialized) {
|
||||
GraphicsBackend.isOpenGLInitialized = isOpenGLInitialized;
|
||||
}
|
||||
|
||||
public static void initialize() {
|
||||
startRenderThread();
|
||||
}
|
||||
@ -128,4 +149,47 @@ public class GraphicsBackend {
|
||||
faceCullingEnabled = useFaceCulling;
|
||||
}
|
||||
|
||||
public static boolean isFullscreen() {
|
||||
return isFullscreen;
|
||||
}
|
||||
|
||||
public static boolean isVSyncEnabled() {
|
||||
return vSyncEnabled;
|
||||
}
|
||||
|
||||
public static void setFullscreen() {
|
||||
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
|
||||
glfwSetWindowMonitor(
|
||||
getWindowHandle(),
|
||||
glfwGetPrimaryMonitor(),
|
||||
0,
|
||||
0,
|
||||
vidmode.width(),
|
||||
vidmode.height(),
|
||||
0);
|
||||
isFullscreen = true;
|
||||
}
|
||||
|
||||
public static void setWindowed() {
|
||||
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
|
||||
glfwSetWindowMonitor(
|
||||
getWindowHandle(),
|
||||
0,
|
||||
(vidmode.width() - getFrameWidth()) / 2,
|
||||
(vidmode.height() - getFrameHeight()) / 2,
|
||||
getFrameWidth(),
|
||||
getFrameHeight(),
|
||||
0);
|
||||
isFullscreen = false;
|
||||
}
|
||||
|
||||
public static void setVSyncEnabled(boolean enable) {
|
||||
glfwSwapInterval(enable ? 1 : 0);
|
||||
vSyncEnabled = enable;
|
||||
}
|
||||
|
||||
public static int getRefreshRate() {
|
||||
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
|
||||
return vidmode.refreshRate();
|
||||
}
|
||||
}
|
||||
|
@ -73,4 +73,13 @@ public class GraphicsInterface {
|
||||
GraphicsBackend.startNextLayer();
|
||||
}
|
||||
|
||||
public static void makeFullscreen(boolean state) {
|
||||
if (state) {
|
||||
GraphicsBackend.setFullscreen();
|
||||
} else {
|
||||
GraphicsBackend.setWindowed();
|
||||
}
|
||||
GraphicsBackend.setVSyncEnabled(GraphicsBackend.isVSyncEnabled());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -50,6 +50,7 @@ class LWJGLInitializer {
|
||||
private static void initializeGLFW() {
|
||||
// TODO Do GLFW error handling: check glfwInit, setup error callback
|
||||
glfwInit();
|
||||
GraphicsBackend.setGLFWInitialized(true);
|
||||
}
|
||||
|
||||
private static void createWindow() {
|
||||
@ -67,7 +68,7 @@ class LWJGLInitializer {
|
||||
glfwSetInputMode(handle, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
|
||||
|
||||
glfwMakeContextCurrent(handle);
|
||||
glfwSwapInterval(0);
|
||||
glfwSwapInterval(0); // TODO: remove after config system is added
|
||||
}
|
||||
|
||||
private static void positionWindow() {
|
||||
@ -87,6 +88,7 @@ class LWJGLInitializer {
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
RenderTaskQueue.schedule(OpenGLObjectTracker::deleteEnqueuedObjects);
|
||||
GraphicsBackend.setOpenGLInitialized(true);
|
||||
}
|
||||
|
||||
private static void setupWindowCallbacks() {
|
||||
|
@ -38,10 +38,6 @@ public class StaticModel extends Model {
|
||||
this.transforms = transforms;
|
||||
}
|
||||
|
||||
public StaticModel(Builder builder) {
|
||||
this(builder.getParts(), builder.getTransforms());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Mat4 getTransform(int partIndex) {
|
||||
return transforms[partIndex];
|
||||
@ -83,6 +79,10 @@ public class StaticModel extends Model {
|
||||
return transforms.toArray(new Mat4[transforms.size()]);
|
||||
}
|
||||
|
||||
public StaticModel build() {
|
||||
return new StaticModel(getParts(), getTransforms());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -188,7 +188,7 @@ public class LayerWorld extends Layer {
|
||||
);
|
||||
}
|
||||
|
||||
return new StaticModel(b);
|
||||
return b.build();
|
||||
}
|
||||
|
||||
private static final float FRICTION_COEFF = Units.get("1e-5f kg/s");
|
||||
|
@ -18,35 +18,20 @@
|
||||
|
||||
package ru.windcorp.progressia.client.world;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import glm.mat._4.Mat4;
|
||||
import glm.vec._3.Vec3;
|
||||
import glm.vec._3.i.Vec3i;
|
||||
import ru.windcorp.progressia.client.graphics.model.Model;
|
||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
||||
import ru.windcorp.progressia.client.graphics.model.StaticModel;
|
||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||
import ru.windcorp.progressia.client.graphics.model.StaticModel.Builder;
|
||||
import ru.windcorp.progressia.client.world.block.BlockRender;
|
||||
import ru.windcorp.progressia.client.world.block.BlockRenderNone;
|
||||
import ru.windcorp.progressia.client.world.block.BlockRenderRegistry;
|
||||
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizer;
|
||||
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSupplier;
|
||||
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizers;
|
||||
import ru.windcorp.progressia.client.world.tile.TileRender;
|
||||
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.TileData;
|
||||
import ru.windcorp.progressia.common.world.tile.TileDataStack;
|
||||
|
||||
public class ChunkRender
|
||||
@ -55,7 +40,7 @@ public class ChunkRender
|
||||
private final WorldRender world;
|
||||
private final ChunkData data;
|
||||
|
||||
private Model model = null;
|
||||
private final ChunkRenderModel model;
|
||||
|
||||
private final Map<TileDataStack, TileRenderStackImpl> tileRenderLists = Collections
|
||||
.synchronizedMap(new WeakHashMap<>());
|
||||
@ -63,6 +48,7 @@ public class ChunkRender
|
||||
public ChunkRender(WorldRender world, ChunkData data) {
|
||||
this.world = world;
|
||||
this.data = data;
|
||||
this.model = new ChunkRenderModel(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -107,165 +93,11 @@ public class ChunkRender
|
||||
}
|
||||
|
||||
public synchronized void render(ShapeRenderHelper renderer) {
|
||||
if (model == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderer.pushTransform().translate(
|
||||
data.getX() * ChunkData.BLOCKS_PER_CHUNK,
|
||||
data.getY() * ChunkData.BLOCKS_PER_CHUNK,
|
||||
data.getZ() * ChunkData.BLOCKS_PER_CHUNK
|
||||
);
|
||||
|
||||
model.render(renderer);
|
||||
|
||||
renderer.popTransform();
|
||||
}
|
||||
|
||||
public synchronized void update() {
|
||||
Collection<ChunkRenderOptimizer> optimizers = ChunkRenderOptimizers.getAllSuppliers().stream()
|
||||
.map(ChunkRenderOptimizerSupplier::createOptimizer)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
optimizers.forEach(bro -> bro.startRender(this));
|
||||
|
||||
StaticModel.Builder builder = StaticModel.builder();
|
||||
|
||||
Vec3i cursor = new Vec3i();
|
||||
for (int x = 0; x < ChunkData.BLOCKS_PER_CHUNK; ++x) {
|
||||
for (int y = 0; y < ChunkData.BLOCKS_PER_CHUNK; ++y) {
|
||||
for (int z = 0; z < ChunkData.BLOCKS_PER_CHUNK; ++z) {
|
||||
cursor.set(x, y, z);
|
||||
|
||||
buildBlock(cursor, optimizers, builder);
|
||||
buildBlockTiles(cursor, optimizers, builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
optimizers.stream()
|
||||
.map(ChunkRenderOptimizer::endRender)
|
||||
.filter(Objects::nonNull)
|
||||
.forEach(builder::addPart);
|
||||
|
||||
model = new StaticModel(builder);
|
||||
}
|
||||
|
||||
private void buildBlock(
|
||||
Vec3i cursor,
|
||||
Collection<ChunkRenderOptimizer> optimizers,
|
||||
Builder builder
|
||||
) {
|
||||
BlockRender block = getBlock(cursor);
|
||||
|
||||
if (block instanceof BlockRenderNone) {
|
||||
return;
|
||||
}
|
||||
|
||||
forwardBlockToOptimizers(block, cursor, optimizers);
|
||||
|
||||
if (!block.needsOwnRenderable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
addBlockRenderable(block, cursor, builder);
|
||||
}
|
||||
|
||||
private void forwardBlockToOptimizers(
|
||||
BlockRender block,
|
||||
Vec3i cursor,
|
||||
Collection<ChunkRenderOptimizer> optimizers
|
||||
) {
|
||||
optimizers.forEach(cro -> cro.processBlock(block, cursor));
|
||||
}
|
||||
|
||||
private void addBlockRenderable(
|
||||
BlockRender block,
|
||||
Vec3i cursor,
|
||||
Builder builder
|
||||
) {
|
||||
Renderable renderable = block.createRenderable();
|
||||
|
||||
if (renderable == null) {
|
||||
renderable = block::render;
|
||||
}
|
||||
|
||||
builder.addPart(
|
||||
renderable,
|
||||
new Mat4().identity().translate(cursor.x, cursor.y, cursor.z)
|
||||
);
|
||||
}
|
||||
|
||||
private void buildBlockTiles(
|
||||
Vec3i cursor,
|
||||
Collection<ChunkRenderOptimizer> optimizers,
|
||||
Builder builder
|
||||
) {
|
||||
for (BlockFace face : BlockFace.getFaces()) {
|
||||
buildFaceTiles(cursor, face, optimizers, builder);
|
||||
}
|
||||
}
|
||||
|
||||
private void buildFaceTiles(
|
||||
Vec3i cursor,
|
||||
BlockFace face,
|
||||
Collection<ChunkRenderOptimizer> optimizers,
|
||||
Builder builder
|
||||
) {
|
||||
List<TileData> tiles = getData().getTilesOrNull(cursor, face);
|
||||
|
||||
if (tiles == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int layer = 0; layer < tiles.size(); ++layer) {
|
||||
|
||||
if (tiles.get(layer) == null) {
|
||||
System.out.println(tiles.get(layer).getId());
|
||||
}
|
||||
|
||||
buildTile(
|
||||
cursor,
|
||||
face,
|
||||
TileRenderRegistry.getInstance().get(
|
||||
tiles.get(layer).getId()
|
||||
),
|
||||
layer,
|
||||
optimizers,
|
||||
builder
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void buildTile(
|
||||
Vec3i cursor,
|
||||
BlockFace face,
|
||||
TileRender tile,
|
||||
int layer,
|
||||
Collection<ChunkRenderOptimizer> optimizers,
|
||||
Builder builder
|
||||
) {
|
||||
// TODO implement
|
||||
|
||||
Vec3 pos = new Vec3(cursor.x, cursor.y, cursor.z);
|
||||
|
||||
optimizers.forEach(cro -> cro.processTile(tile, cursor, face));
|
||||
|
||||
if (!tile.needsOwnRenderable())
|
||||
return;
|
||||
|
||||
Vec3 offset = new Vec3(
|
||||
face.getVector().x,
|
||||
face.getVector().y,
|
||||
face.getVector().z
|
||||
);
|
||||
|
||||
pos.add(offset.mul(1f / 64));
|
||||
|
||||
builder.addPart(
|
||||
tile.createRenderable(face),
|
||||
new Mat4().identity().translate(pos)
|
||||
);
|
||||
model.update();
|
||||
}
|
||||
|
||||
private class TileRenderStackImpl extends TileRenderStack {
|
||||
|
@ -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.world;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
import glm.mat._4.Mat4;
|
||||
import glm.vec._3.i.Vec3i;
|
||||
import ru.windcorp.progressia.client.graphics.model.Model;
|
||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
||||
import ru.windcorp.progressia.client.graphics.model.StaticModel;
|
||||
import ru.windcorp.progressia.client.graphics.model.StaticModel.Builder;
|
||||
import ru.windcorp.progressia.client.world.block.BlockRender;
|
||||
import ru.windcorp.progressia.client.world.block.BlockRenderNone;
|
||||
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizer;
|
||||
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;
|
||||
|
||||
public class ChunkRenderModel implements Renderable {
|
||||
|
||||
private final ChunkRender chunk;
|
||||
|
||||
private final Collection<ChunkRenderOptimizer> optimizers = new ArrayList<>();
|
||||
private Model model = null;
|
||||
|
||||
public ChunkRenderModel(ChunkRender chunk) {
|
||||
this.chunk = chunk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(ShapeRenderHelper renderer) {
|
||||
if (model == null) return;
|
||||
|
||||
renderer.pushTransform().translate(
|
||||
chunk.getX() * ChunkData.BLOCKS_PER_CHUNK,
|
||||
chunk.getY() * ChunkData.BLOCKS_PER_CHUNK,
|
||||
chunk.getZ() * ChunkData.BLOCKS_PER_CHUNK
|
||||
);
|
||||
|
||||
model.render(renderer);
|
||||
|
||||
renderer.popTransform();
|
||||
}
|
||||
|
||||
public void update() {
|
||||
setupCROs();
|
||||
|
||||
StaticModel.Builder sink = StaticModel.builder();
|
||||
|
||||
optimizers.forEach(ChunkRenderOptimizer::startRender);
|
||||
|
||||
chunk.forEachBiC(blockInChunk -> {
|
||||
processBlockAndTiles(blockInChunk, sink);
|
||||
});
|
||||
|
||||
for (ChunkRenderOptimizer optimizer : optimizers) {
|
||||
Renderable renderable = optimizer.endRender();
|
||||
if (renderable != null) {
|
||||
sink.addPart(renderable);
|
||||
}
|
||||
}
|
||||
|
||||
this.model = sink.build();
|
||||
this.optimizers.clear();
|
||||
}
|
||||
|
||||
private void setupCROs() {
|
||||
Set<String> ids = ChunkRenderOptimizerRegistry.getInstance().keySet();
|
||||
|
||||
for (String id : ids) {
|
||||
ChunkRenderOptimizer optimizer = ChunkRenderOptimizerRegistry.getInstance().create(id);
|
||||
optimizer.setup(chunk);
|
||||
this.optimizers.add(optimizer);
|
||||
}
|
||||
}
|
||||
|
||||
private void processBlockAndTiles(Vec3i blockInChunk, Builder sink) {
|
||||
processBlock(blockInChunk, sink);
|
||||
|
||||
for (BlockFace face : BlockFace.getFaces()) {
|
||||
processTileStack(blockInChunk, face, sink);
|
||||
}
|
||||
}
|
||||
|
||||
private void processBlock(Vec3i blockInChunk, Builder sink) {
|
||||
BlockRender block = chunk.getBlock(blockInChunk);
|
||||
|
||||
if (block instanceof BlockRenderNone) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (block.needsOwnRenderable()) {
|
||||
sink.addPart(
|
||||
block.createRenderable(chunk.getData(), blockInChunk),
|
||||
new Mat4().identity().translate(blockInChunk.x, blockInChunk.y, blockInChunk.z)
|
||||
);
|
||||
}
|
||||
|
||||
processBlockWithCROs(block, blockInChunk);
|
||||
}
|
||||
|
||||
private void processBlockWithCROs(BlockRender block, Vec3i blockInChunk) {
|
||||
for (ChunkRenderOptimizer optimizer : optimizers) {
|
||||
optimizer.addBlock(block, blockInChunk);
|
||||
}
|
||||
}
|
||||
|
||||
private void processTileStack(Vec3i blockInChunk, BlockFace face, Builder sink) {
|
||||
TileRenderStack trs = chunk.getTilesOrNull(blockInChunk, face);
|
||||
|
||||
if (trs == null || trs.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
trs.forEach(tile -> processTile(tile, blockInChunk, face, sink));
|
||||
}
|
||||
|
||||
private void processTile(TileRender tile, Vec3i blockInChunk, BlockFace 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)
|
||||
);
|
||||
}
|
||||
|
||||
processTileWithCROs(tile, blockInChunk, face);
|
||||
}
|
||||
|
||||
private void processTileWithCROs(TileRender tile, Vec3i blockInChunk, BlockFace face) {
|
||||
for (ChunkRenderOptimizer optimizer : optimizers) {
|
||||
optimizer.addTile(tile, blockInChunk, face);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -18,8 +18,14 @@
|
||||
|
||||
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.ChunkDataListener;
|
||||
import ru.windcorp.progressia.common.world.block.BlockData;
|
||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
||||
import ru.windcorp.progressia.common.world.tile.TileData;
|
||||
|
||||
class ChunkUpdateListener implements ChunkDataListener {
|
||||
|
||||
@ -34,4 +40,60 @@ class ChunkUpdateListener implements ChunkDataListener {
|
||||
world.getChunk(chunk).markForUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChunkLoaded(ChunkData chunk) {
|
||||
Vec3i cursor = new Vec3i();
|
||||
for (BlockFace face : BlockFace.getFaces()) {
|
||||
cursor.set(chunk.getX(), chunk.getY(), chunk.getZ());
|
||||
cursor.add(face.getVector());
|
||||
world.markChunkForUpdate(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChunkBlockChanged(ChunkData chunk, Vec3i blockInChunk, BlockData previous, BlockData current) {
|
||||
onLocationChanged(chunk, blockInChunk);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChunkTilesChanged(
|
||||
ChunkData chunk,
|
||||
Vec3i blockInChunk,
|
||||
BlockFace face,
|
||||
TileData tile,
|
||||
boolean wasAdded
|
||||
) {
|
||||
onLocationChanged(chunk, blockInChunk);
|
||||
}
|
||||
|
||||
private void onLocationChanged(ChunkData chunk, Vec3i blockInChunk) {
|
||||
Vec3i chunkPos = Vectors.grab3i().set(chunk.getX(), chunk.getY(), chunk.getZ());
|
||||
|
||||
checkCoordinate(blockInChunk, chunkPos, VectorUtil.Axis.X);
|
||||
checkCoordinate(blockInChunk, chunkPos, VectorUtil.Axis.Y);
|
||||
checkCoordinate(blockInChunk, chunkPos, VectorUtil.Axis.Z);
|
||||
|
||||
Vectors.release(chunkPos);
|
||||
}
|
||||
|
||||
private void checkCoordinate(Vec3i blockInChunk, Vec3i chunkPos, VectorUtil.Axis axis) {
|
||||
int block = VectorUtil.get(blockInChunk, axis);
|
||||
int diff = 0;
|
||||
|
||||
if (block == 0) {
|
||||
diff = -1;
|
||||
} else if (block == ChunkData.BLOCKS_PER_CHUNK - 1) {
|
||||
diff = +1;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
int previousChunkPos = VectorUtil.get(chunkPos, axis);
|
||||
VectorUtil.set(chunkPos, axis, previousChunkPos + diff);
|
||||
|
||||
world.markChunkForUpdate(chunkPos);
|
||||
|
||||
VectorUtil.set(chunkPos, axis, previousChunkPos);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -20,7 +20,9 @@ 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 glm.vec._3.i.Vec3i;
|
||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||
|
||||
public abstract class BlockRender extends Namespaced implements GenericBlock {
|
||||
@ -35,7 +37,7 @@ public abstract class BlockRender extends Namespaced implements GenericBlock {
|
||||
);
|
||||
}
|
||||
|
||||
public Renderable createRenderable() {
|
||||
public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -18,8 +18,10 @@
|
||||
|
||||
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;
|
||||
|
||||
public class BlockRenderNone extends BlockRender {
|
||||
|
||||
@ -28,7 +30,7 @@ public class BlockRenderNone extends BlockRender {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Renderable createRenderable() {
|
||||
public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk) {
|
||||
return EmptyModel.getInstance();
|
||||
}
|
||||
|
||||
|
@ -65,9 +65,4 @@ public class BlockRenderOpaqueCube extends BlockRenderTexturedCube {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsOwnRenderable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,17 +22,27 @@ import static ru.windcorp.progressia.common.world.block.BlockFace.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.model.Shapes;
|
||||
import glm.vec._3.Vec3;
|
||||
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.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.ChunkRenderOptimizerCube.OpaqueCube;
|
||||
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;
|
||||
|
||||
public abstract class BlockRenderTexturedCube
|
||||
extends BlockRender
|
||||
implements OpaqueCube {
|
||||
implements BlockOptimizedSurface {
|
||||
|
||||
private final Map<BlockFace, Texture> textures = new HashMap<>();
|
||||
|
||||
@ -55,22 +65,61 @@ public abstract class BlockRenderTexturedCube
|
||||
textures.put(WEST, westTexture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Texture getTexture(BlockFace face) {
|
||||
return textures.get(face);
|
||||
public Texture getTexture(BlockFace blockFace) {
|
||||
return textures.get(blockFace);
|
||||
}
|
||||
|
||||
public Vec4 getColorMultiplier(BlockFace blockFace) {
|
||||
return Colors.WHITE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Renderable createRenderable() {
|
||||
return new Shapes.PppBuilder(
|
||||
public final void getFaces(
|
||||
ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace,
|
||||
boolean inner,
|
||||
Consumer<Face> output,
|
||||
Vec3 offset
|
||||
) {
|
||||
output.accept(createFace(chunk, blockInChunk, blockFace, inner, offset));
|
||||
}
|
||||
|
||||
private Face createFace(
|
||||
ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace,
|
||||
boolean inner,
|
||||
Vec3 offset
|
||||
) {
|
||||
return Faces.createBlockFace(
|
||||
WorldRenderProgram.getDefault(),
|
||||
getTexture(TOP),
|
||||
getTexture(BOTTOM),
|
||||
getTexture(NORTH),
|
||||
getTexture(SOUTH),
|
||||
getTexture(EAST),
|
||||
getTexture(WEST)
|
||||
).create();
|
||||
getTexture(blockFace),
|
||||
getColorMultiplier(blockFace),
|
||||
offset,
|
||||
blockFace,
|
||||
inner
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk) {
|
||||
boolean opaque = isBlockOpaque();
|
||||
|
||||
Face[] faces = new Face[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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
return new Shape(Usage.STATIC, WorldRenderProgram.getDefault(), faces);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsOwnRenderable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -65,9 +65,4 @@ public class BlockRenderTransparentCube extends BlockRenderTexturedCube {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsOwnRenderable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,27 +19,111 @@
|
||||
package ru.windcorp.progressia.client.world.cro;
|
||||
|
||||
import glm.vec._3.i.Vec3i;
|
||||
import ru.windcorp.progressia.client.graphics.model.Shape;
|
||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||
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;
|
||||
|
||||
public abstract class ChunkRenderOptimizer {
|
||||
/**
|
||||
* Chunk render optimizer (CRO) is an object that produces optimized models for
|
||||
* chunks. CROs are sequentially given information about the blocks and tiles of
|
||||
* a particular chunk, after which they are expected to produce a set of
|
||||
* {@link Renderable}s. As the name suggests, CROs are primarily expected to
|
||||
* output models that are optimized compared to models of individual blocks and
|
||||
* 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.
|
||||
* <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
|
||||
* in the following way:
|
||||
* <ol>
|
||||
* <li>{@link #setup(ChunkRender)} is invoked to provide the {@link ChunkRender}
|
||||
* 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
|
||||
* 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
|
||||
* calculations. The result of the optimization is returned.</li>
|
||||
* </ol>
|
||||
* <p>
|
||||
* Each CRO instance is accessed by a single thread.
|
||||
*/
|
||||
public abstract class ChunkRenderOptimizer extends Namespaced {
|
||||
|
||||
public abstract void startRender(ChunkRender chunk);
|
||||
/**
|
||||
* The chunk that this CRO is currently working on.
|
||||
*/
|
||||
protected ChunkRender chunk = null;
|
||||
|
||||
public abstract void processBlock(
|
||||
BlockRender block,
|
||||
Vec3i posInChunk
|
||||
);
|
||||
/**
|
||||
* Creates a new CRO instance with the specified ID.
|
||||
*
|
||||
* @param id the ID of this CRO
|
||||
*/
|
||||
public ChunkRenderOptimizer(String id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
public abstract void processTile(
|
||||
TileRender tile,
|
||||
Vec3i posInChunk,
|
||||
BlockFace face
|
||||
);
|
||||
/**
|
||||
* This method is invoked before a new chunk processing cycle begins to
|
||||
* specify the chunk. When overriding, {@code super.setup(chunk)} must be
|
||||
* invoked.
|
||||
*
|
||||
* @param chunk the chunk that will be processed next
|
||||
*/
|
||||
public void setup(ChunkRender chunk) {
|
||||
this.chunk = chunk;
|
||||
}
|
||||
|
||||
public abstract Shape endRender();
|
||||
/**
|
||||
* @return the chunk that this CRO is currently working on
|
||||
*/
|
||||
public ChunkRender getChunk() {
|
||||
return chunk;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets this CRO to a state in which a new chunk may be processed.
|
||||
*/
|
||||
public abstract void startRender();
|
||||
|
||||
/**
|
||||
* Requests that this CRO processes the provided block. This method may only
|
||||
* be invoked between {@link #startRender()} and {@link #endRender()}. This
|
||||
* 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.
|
||||
* It corresponds to
|
||||
* {@code getChunk().getBlock(blockInChunk)}.
|
||||
* @param blockInChunk the position of the block
|
||||
*/
|
||||
public abstract void addBlock(BlockRender block, Vec3i blockInChunk);
|
||||
|
||||
/**
|
||||
* Requests that this CRO processes the provided tile. This method may only
|
||||
* be invoked between {@link #startRender()} and {@link #endRender()}. This
|
||||
* method is only invoked once per tile. This method is not necessarily
|
||||
* invoked for each tile. When multiple tiles in a tile stack are requested,
|
||||
* 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 blockFace the face that the tile belongs to
|
||||
*/
|
||||
public abstract void addTile(TileRender tile, Vec3i blockInChunk, BlockFace blockFace);
|
||||
|
||||
/**
|
||||
* Requests that the CRO assembles and outputs its model. This method may
|
||||
* only be invoked after {@link #startRender()}.
|
||||
*
|
||||
* @return the assembled {@link Renderable}.
|
||||
*/
|
||||
public abstract Renderable endRender();
|
||||
|
||||
}
|
||||
|
@ -1,283 +0,0 @@
|
||||
/*
|
||||
* 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 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 java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import glm.vec._3.Vec3;
|
||||
import glm.vec._3.i.Vec3i;
|
||||
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.Shape;
|
||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
|
||||
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.world.block.BlockFace;
|
||||
|
||||
public class ChunkRenderOptimizerCube extends ChunkRenderOptimizer {
|
||||
|
||||
public static interface OpaqueCube {
|
||||
public Texture getTexture(BlockFace face);
|
||||
|
||||
public boolean isOpaque(BlockFace face);
|
||||
|
||||
public boolean isBlockOpaque();
|
||||
}
|
||||
|
||||
public static interface OpaqueSurface {
|
||||
public Texture getTexture(BlockFace face);
|
||||
|
||||
public boolean isOpaque(BlockFace face);
|
||||
}
|
||||
|
||||
private static class BlockInfo {
|
||||
OpaqueCube block;
|
||||
final FaceInfo[] faces = new FaceInfo[BLOCK_FACE_COUNT];
|
||||
|
||||
{
|
||||
for (int i = 0; i < faces.length; ++i) {
|
||||
faces[i] = new FaceInfo();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class FaceInfo {
|
||||
static final int NO_OPAQUE_TILES = -1;
|
||||
|
||||
int topOpaqueTile = NO_OPAQUE_TILES;
|
||||
final OpaqueSurface[] tiles = new OpaqueSurface[TILES_PER_FACE];
|
||||
int tileCount = 0;
|
||||
}
|
||||
|
||||
private final BlockInfo[][][] data = new BlockInfo[BLOCKS_PER_CHUNK][BLOCKS_PER_CHUNK][BLOCKS_PER_CHUNK];
|
||||
|
||||
{
|
||||
for (int x = 0; x < BLOCKS_PER_CHUNK; ++x) {
|
||||
for (int y = 0; y < BLOCKS_PER_CHUNK; ++y) {
|
||||
for (int z = 0; z < BLOCKS_PER_CHUNK; ++z) {
|
||||
data[x][y][z] = new BlockInfo();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startRender(ChunkRender chunk) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processBlock(BlockRender block, Vec3i pos) {
|
||||
if (!(block instanceof OpaqueCube))
|
||||
return;
|
||||
OpaqueCube opaqueCube = (OpaqueCube) block;
|
||||
addBlock(pos, opaqueCube);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processTile(TileRender tile, Vec3i pos, BlockFace face) {
|
||||
if (!(tile instanceof OpaqueSurface))
|
||||
return;
|
||||
OpaqueSurface opaqueTile = (OpaqueSurface) tile;
|
||||
addTile(pos, face, opaqueTile);
|
||||
}
|
||||
|
||||
protected void addBlock(Vec3i pos, OpaqueCube cube) {
|
||||
getBlock(pos).block = cube;
|
||||
}
|
||||
|
||||
private void addTile(Vec3i pos, BlockFace face, OpaqueSurface opaqueTile) {
|
||||
FaceInfo faceInfo = getFace(pos, face);
|
||||
|
||||
int index = faceInfo.tileCount;
|
||||
faceInfo.tileCount++;
|
||||
|
||||
faceInfo.tiles[index] = opaqueTile;
|
||||
|
||||
if (opaqueTile.isOpaque(face)) {
|
||||
faceInfo.topOpaqueTile = index;
|
||||
}
|
||||
}
|
||||
|
||||
protected BlockInfo getBlock(Vec3i cursor) {
|
||||
return data[cursor.x][cursor.y][cursor.z];
|
||||
}
|
||||
|
||||
protected FaceInfo getFace(Vec3i cursor, BlockFace face) {
|
||||
return getBlock(cursor).faces[face.getId()];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape endRender() {
|
||||
|
||||
Collection<Face> shapeFaces = new ArrayList<>(
|
||||
BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * 3
|
||||
);
|
||||
|
||||
Vec3i cursor = new Vec3i();
|
||||
|
||||
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, shapeFaces::add);
|
||||
processOuterFaces(cursor, shapeFaces::add);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Shape(
|
||||
Usage.STATIC,
|
||||
WorldRenderProgram.getDefault(),
|
||||
shapeFaces.toArray(new Face[shapeFaces.size()])
|
||||
);
|
||||
}
|
||||
|
||||
private void processOuterFaces(
|
||||
Vec3i cursor,
|
||||
Consumer<Face> output
|
||||
) {
|
||||
for (BlockFace face : BlockFace.getFaces()) {
|
||||
if (!shouldRenderOuterFace(cursor, face))
|
||||
continue;
|
||||
|
||||
Vec3 faceOrigin = new Vec3(cursor.x, cursor.y, cursor.z);
|
||||
Vec3 offset = new Vec3(face.getVector().x, face.getVector().y, face.getVector().z).mul(1f / 128);
|
||||
|
||||
FaceInfo info = getFace(cursor, face);
|
||||
|
||||
if (info.topOpaqueTile == FaceInfo.NO_OPAQUE_TILES) {
|
||||
OpaqueCube block = getBlock(cursor).block;
|
||||
|
||||
if (block != null) {
|
||||
addFace(
|
||||
faceOrigin,
|
||||
face,
|
||||
getBlock(cursor).block.getTexture(face),
|
||||
output
|
||||
);
|
||||
|
||||
faceOrigin.add(offset);
|
||||
}
|
||||
}
|
||||
|
||||
int startLayer = info.topOpaqueTile;
|
||||
if (startLayer == FaceInfo.NO_OPAQUE_TILES) {
|
||||
startLayer = 0;
|
||||
}
|
||||
|
||||
for (int layer = startLayer; layer < info.tileCount; ++layer) {
|
||||
addFace(
|
||||
faceOrigin,
|
||||
face,
|
||||
info.tiles[layer].getTexture(face),
|
||||
output
|
||||
);
|
||||
|
||||
faceOrigin.add(offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addFace(
|
||||
Vec3 cursor,
|
||||
BlockFace face,
|
||||
Texture texture,
|
||||
Consumer<Face> output
|
||||
) {
|
||||
if (texture == null)
|
||||
return;
|
||||
|
||||
output.accept(
|
||||
Faces.createBlockFace(
|
||||
WorldRenderProgram.getDefault(),
|
||||
texture,
|
||||
Colors.WHITE,
|
||||
new Vec3(cursor),
|
||||
face,
|
||||
false
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private boolean shouldRenderOuterFace(Vec3i cursor, BlockFace face) {
|
||||
cursor.add(face.getVector());
|
||||
try {
|
||||
|
||||
// TODO handle neighboring chunks properly
|
||||
if (!isInBounds(cursor))
|
||||
return true;
|
||||
|
||||
OpaqueCube adjacent = getBlock(cursor).block;
|
||||
|
||||
if (adjacent == null)
|
||||
return true;
|
||||
if (adjacent.isOpaque(face))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
||||
} finally {
|
||||
cursor.sub(face.getVector());
|
||||
}
|
||||
}
|
||||
|
||||
private void processInnerFaces(
|
||||
Vec3i cursor,
|
||||
Consumer<Face> output
|
||||
) {
|
||||
// if (block.isBlockOpaque()) return;
|
||||
//
|
||||
// for (BlockFace face : BlockFace.getFaces()) {
|
||||
//
|
||||
// Texture texture = block.getTexture(face);
|
||||
// if (texture == null) continue;
|
||||
//
|
||||
// output.accept(Faces.createBlockFace(
|
||||
// WorldRenderProgram.getDefault(),
|
||||
// texture,
|
||||
// COLOR_MULTIPLIER,
|
||||
// new Vec3(cursor.x, cursor.y, cursor.z),
|
||||
// face,
|
||||
// true
|
||||
// ));
|
||||
//
|
||||
// }
|
||||
}
|
||||
|
||||
private boolean isInBounds(Vec3i cursor) {
|
||||
return isInBounds(cursor.x) &&
|
||||
isInBounds(cursor.y) &&
|
||||
isInBounds(cursor.z);
|
||||
}
|
||||
|
||||
private boolean isInBounds(int c) {
|
||||
return c >= 0 && c < BLOCKS_PER_CHUNK;
|
||||
}
|
||||
|
||||
}
|
@ -18,28 +18,17 @@
|
||||
|
||||
package ru.windcorp.progressia.client.world.cro;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
import ru.windcorp.progressia.common.util.namespaces.NamespacedFactoryRegistry;
|
||||
|
||||
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
|
||||
public class ChunkRenderOptimizerRegistry extends NamespacedFactoryRegistry<ChunkRenderOptimizer> {
|
||||
|
||||
public abstract class ChunkRenderOptimizerSupplier extends Namespaced {
|
||||
private static final ChunkRenderOptimizerRegistry INSTANCE = new ChunkRenderOptimizerRegistry();
|
||||
|
||||
public ChunkRenderOptimizerSupplier(String id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
public abstract ChunkRenderOptimizer createOptimizer();
|
||||
|
||||
public static ChunkRenderOptimizerSupplier of(
|
||||
String id,
|
||||
Supplier<ChunkRenderOptimizer> supplier
|
||||
) {
|
||||
return new ChunkRenderOptimizerSupplier(id) {
|
||||
@Override
|
||||
public ChunkRenderOptimizer createOptimizer() {
|
||||
return supplier.get();
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @return the instance
|
||||
*/
|
||||
public static ChunkRenderOptimizerRegistry getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,396 @@
|
||||
/*
|
||||
* 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 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 java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
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.Renderable;
|
||||
import ru.windcorp.progressia.client.graphics.model.Shape;
|
||||
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
|
||||
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;
|
||||
|
||||
public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
|
||||
|
||||
private static final float OVERLAY_OFFSET = 1 / 128f;
|
||||
|
||||
/**
|
||||
* A common interface to objects that can provide optimizeable surfaces.
|
||||
* This is an internal interface; use {@link BlockOptimizedSurface} or
|
||||
* {@link TileOptimizedSurface} instead.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @param chunk the chunk that contains the requested face
|
||||
* @param blockInChunk the block in chunk
|
||||
* @param blockFace the requested face
|
||||
* @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
|
||||
* vertices
|
||||
*/
|
||||
void getFaces(
|
||||
ChunkData chunk,
|
||||
Vec3i blockInChunk,
|
||||
BlockFace blockFace,
|
||||
boolean inner,
|
||||
Consumer<Face> output,
|
||||
Vec3 offset /* kostyl 156% */
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns the opacity of the surface identified by the provided
|
||||
* {@link BlockFace}.
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* A block that can be optimized by {@link ChunkRenderOptimizerSurface}.
|
||||
*/
|
||||
public static interface BlockOptimizedSurface extends OptimizedSurface {
|
||||
|
||||
/**
|
||||
* Returns the opacity of the block. Opaque blocks do not expect that
|
||||
* the camera can be inside them. Opaque blocks prevent surfaces that
|
||||
* face them
|
||||
* from being included in chunk models.
|
||||
*
|
||||
* @return {@code true} iff the block is opaque.
|
||||
*/
|
||||
boolean isBlockOpaque();
|
||||
}
|
||||
|
||||
/**
|
||||
* A tile that can be optimized by {@link ChunkRenderOptimizerSurface}.
|
||||
*/
|
||||
public static interface TileOptimizedSurface extends OptimizedSurface {
|
||||
// Empty for now
|
||||
}
|
||||
|
||||
private static class BlockInfo {
|
||||
BlockOptimizedSurface block;
|
||||
final FaceInfo[] faces = new FaceInfo[BLOCK_FACE_COUNT];
|
||||
|
||||
{
|
||||
for (int i = 0; i < faces.length; ++i) {
|
||||
faces[i] = new FaceInfo(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class FaceInfo {
|
||||
static final int BLOCK_LAYER = -1;
|
||||
|
||||
final BlockInfo block;
|
||||
|
||||
int topOpaqueSurface = BLOCK_LAYER;
|
||||
int bottomOpaqueSurface = Integer.MAX_VALUE;
|
||||
|
||||
final TileOptimizedSurface[] tiles = new TileOptimizedSurface[TILES_PER_FACE];
|
||||
int tileCount = 0;
|
||||
|
||||
FaceInfo(BlockInfo block) {
|
||||
this.block = block;
|
||||
}
|
||||
|
||||
OptimizedSurface getSurface(int layer) {
|
||||
return layer == BLOCK_LAYER ? block.block : tiles[layer];
|
||||
}
|
||||
}
|
||||
|
||||
private final BlockInfo[][][] data = new BlockInfo[BLOCKS_PER_CHUNK][BLOCKS_PER_CHUNK][BLOCKS_PER_CHUNK];
|
||||
|
||||
public ChunkRenderOptimizerSurface(String id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startRender() {
|
||||
for (int x = 0; x < BLOCKS_PER_CHUNK; ++x) {
|
||||
for (int y = 0; y < BLOCKS_PER_CHUNK; ++y) {
|
||||
for (int z = 0; z < BLOCKS_PER_CHUNK; ++z) {
|
||||
data[x][y][z] = new BlockInfo();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBlock(BlockRender block, Vec3i pos) {
|
||||
if (!(block instanceof BlockOptimizedSurface))
|
||||
return;
|
||||
|
||||
BlockOptimizedSurface bos = (BlockOptimizedSurface) block;
|
||||
addBlock(pos, bos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTile(TileRender tile, Vec3i pos, BlockFace face) {
|
||||
if (!(tile instanceof TileOptimizedSurface))
|
||||
return;
|
||||
|
||||
TileOptimizedSurface tos = (TileOptimizedSurface) tile;
|
||||
addTile(pos, face, tos);
|
||||
}
|
||||
|
||||
protected void addBlock(Vec3i pos, BlockOptimizedSurface block) {
|
||||
getBlock(pos).block = block;
|
||||
}
|
||||
|
||||
private void addTile(Vec3i pos, BlockFace face, TileOptimizedSurface tile) {
|
||||
FaceInfo faceInfo = getFace(pos, face);
|
||||
|
||||
int index = faceInfo.tileCount;
|
||||
faceInfo.tileCount++;
|
||||
|
||||
faceInfo.tiles[index] = tile;
|
||||
|
||||
if (tile.isOpaque(face)) {
|
||||
faceInfo.topOpaqueSurface = index;
|
||||
|
||||
if (faceInfo.bottomOpaqueSurface == FaceInfo.BLOCK_LAYER) {
|
||||
faceInfo.bottomOpaqueSurface = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected BlockInfo getBlock(Vec3i cursor) {
|
||||
return data[cursor.x][cursor.y][cursor.z];
|
||||
}
|
||||
|
||||
protected FaceInfo getFace(Vec3i cursor, BlockFace face) {
|
||||
return getBlock(cursor).faces[face.getId()];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Renderable endRender() {
|
||||
Collection<Face> shapeFaces = new ArrayList<>(
|
||||
BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * 3
|
||||
);
|
||||
|
||||
Vec3i cursor = new Vec3i();
|
||||
Consumer<Face> consumer = shapeFaces::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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shapeFaces.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Shape(
|
||||
Usage.STATIC,
|
||||
WorldRenderProgram.getDefault(),
|
||||
shapeFaces.toArray(new Face[shapeFaces.size()])
|
||||
);
|
||||
}
|
||||
|
||||
private void processOuterFaces(
|
||||
Vec3i blockInChunk,
|
||||
Consumer<Face> output
|
||||
) {
|
||||
for (BlockFace blockFace : BlockFace.getFaces()) {
|
||||
processOuterFace(blockInChunk, blockFace, output);
|
||||
}
|
||||
}
|
||||
|
||||
private void processOuterFace(Vec3i blockInChunk, BlockFace blockFace, Consumer<Face> output) {
|
||||
if (!shouldRenderOuterFace(blockInChunk, blockFace))
|
||||
return;
|
||||
|
||||
FaceInfo info = getFace(blockInChunk, 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);
|
||||
|
||||
for (
|
||||
int layer = info.topOpaqueSurface;
|
||||
layer < info.tileCount;
|
||||
++layer
|
||||
) {
|
||||
OptimizedSurface surface = info.getSurface(layer);
|
||||
if (surface == null)
|
||||
continue; // layer may be BLOCK_LAYER, then block may be null
|
||||
|
||||
surface.getFaces(chunk.getData(), blockInChunk, 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 processInnerFace(Vec3i blockInChunk, BlockFace blockFace, Consumer<Face> output) {
|
||||
if (!shouldRenderInnerFace(blockInChunk, blockFace))
|
||||
return;
|
||||
|
||||
FaceInfo info = getFace(blockInChunk, 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);
|
||||
|
||||
for (
|
||||
int layer = FaceInfo.BLOCK_LAYER;
|
||||
layer <= info.bottomOpaqueSurface && layer < info.tileCount;
|
||||
++layer
|
||||
) {
|
||||
OptimizedSurface surface = info.getSurface(layer);
|
||||
if (surface == null)
|
||||
continue; // layer may be BLOCK_LAYER, then block may be null
|
||||
|
||||
surface.getFaces(chunk.getData(), blockInChunk, blockFace, true, output, faceOrigin);
|
||||
|
||||
faceOrigin.add(offset);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldRenderOuterFace(Vec3i blockInChunk, BlockFace face) {
|
||||
blockInChunk.add(face.getVector());
|
||||
try {
|
||||
return shouldRenderWhenFacing(blockInChunk, face);
|
||||
} finally {
|
||||
blockInChunk.sub(face.getVector());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldRenderInnerFace(Vec3i blockInChunk, BlockFace face) {
|
||||
return shouldRenderWhenFacing(blockInChunk, face);
|
||||
}
|
||||
|
||||
private boolean shouldRenderWhenFacing(Vec3i blockInChunk, BlockFace face) {
|
||||
if (chunk.containsBiC(blockInChunk)) {
|
||||
return shouldRenderWhenFacingLocal(blockInChunk, face);
|
||||
} else {
|
||||
return shouldRenderWhenFacingNeighbor(blockInChunk, face);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldRenderWhenFacingLocal(Vec3i blockInChunk, BlockFace face) {
|
||||
BlockOptimizedSurface block = getBlock(blockInChunk).block;
|
||||
|
||||
if (block == null) {
|
||||
return true;
|
||||
}
|
||||
if (block.isOpaque(face)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean shouldRenderWhenFacingNeighbor(Vec3i blockInLocalChunk, BlockFace face) {
|
||||
Vec3i blockInChunk = Vectors.grab3i().set(blockInLocalChunk.x, blockInLocalChunk.y, blockInLocalChunk.z);
|
||||
Vec3i chunkPos = Vectors.grab3i().set(chunk.getX(), chunk.getY(), chunk.getZ());
|
||||
|
||||
try {
|
||||
// Determine blockInChunk and chunkPos
|
||||
if (blockInLocalChunk.x == -1) {
|
||||
blockInChunk.x = BLOCKS_PER_CHUNK - 1;
|
||||
chunkPos.x -= 1;
|
||||
} else if (blockInLocalChunk.x == BLOCKS_PER_CHUNK) {
|
||||
blockInChunk.x = 0;
|
||||
chunkPos.x += 1;
|
||||
} else if (blockInLocalChunk.y == -1) {
|
||||
blockInChunk.y = BLOCKS_PER_CHUNK - 1;
|
||||
chunkPos.y -= 1;
|
||||
} else if (blockInLocalChunk.y == BLOCKS_PER_CHUNK) {
|
||||
blockInChunk.y = 0;
|
||||
chunkPos.y += 1;
|
||||
} else if (blockInLocalChunk.z == -1) {
|
||||
blockInChunk.z = BLOCKS_PER_CHUNK - 1;
|
||||
chunkPos.z -= 1;
|
||||
} else if (blockInLocalChunk.z == BLOCKS_PER_CHUNK) {
|
||||
blockInChunk.z = 0;
|
||||
chunkPos.z += 1;
|
||||
} else {
|
||||
throw new AssertionError(
|
||||
"Requested incorrent neighbor ("
|
||||
+ blockInLocalChunk.x + "; "
|
||||
+ blockInLocalChunk.y + "; "
|
||||
+ blockInLocalChunk.z + ")"
|
||||
);
|
||||
}
|
||||
|
||||
ChunkRender chunk = this.chunk.getWorld().getChunk(chunkPos);
|
||||
if (chunk == null)
|
||||
return false;
|
||||
|
||||
BlockRender block = chunk.getBlock(blockInChunk);
|
||||
if (!(block instanceof BlockOptimizedSurface))
|
||||
return true;
|
||||
|
||||
BlockOptimizedSurface bos = (BlockOptimizedSurface) block;
|
||||
if (!bos.isOpaque(face))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
|
||||
} finally {
|
||||
Vectors.release(blockInChunk);
|
||||
Vectors.release(chunkPos);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -19,9 +19,11 @@
|
||||
package ru.windcorp.progressia.client.world.tile;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
||||
import glm.vec._3.i.Vec3i;
|
||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizer;
|
||||
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
|
||||
import ru.windcorp.progressia.common.world.ChunkData;
|
||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
||||
import ru.windcorp.progressia.common.world.generic.GenericTile;
|
||||
|
||||
@ -37,7 +39,7 @@ public class TileRender extends Namespaced implements GenericTile {
|
||||
);
|
||||
}
|
||||
|
||||
public Renderable createRenderable(BlockFace face) {
|
||||
public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk, BlockFace face) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -18,19 +18,10 @@
|
||||
|
||||
package ru.windcorp.progressia.client.world.tile;
|
||||
|
||||
import glm.vec._3.Vec3;
|
||||
import ru.windcorp.progressia.client.graphics.Colors;
|
||||
import ru.windcorp.progressia.client.graphics.backend.Usage;
|
||||
import ru.windcorp.progressia.client.graphics.model.Faces;
|
||||
import ru.windcorp.progressia.client.graphics.model.Shape;
|
||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
|
||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
|
||||
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerCube.OpaqueSurface;
|
||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
||||
|
||||
public class TileRenderGrass extends TileRender implements OpaqueSurface {
|
||||
public class TileRenderGrass extends TileRenderSurface {
|
||||
|
||||
private final Texture topTexture;
|
||||
private final Texture sideTexture;
|
||||
@ -55,27 +46,4 @@ public class TileRenderGrass extends TileRender implements OpaqueSurface {
|
||||
return face == BlockFace.TOP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Renderable createRenderable(BlockFace face) {
|
||||
ShapeRenderProgram program = WorldRenderProgram.getDefault();
|
||||
|
||||
return new Shape(
|
||||
Usage.STATIC,
|
||||
WorldRenderProgram.getDefault(),
|
||||
Faces.createBlockFace(
|
||||
program,
|
||||
getTexture(face),
|
||||
Colors.WHITE,
|
||||
new Vec3(0, 0, 0),
|
||||
face,
|
||||
false
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsOwnRenderable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.tile;
|
||||
|
||||
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.block.BlockFace;
|
||||
|
||||
public class TileRenderNone extends TileRender {
|
||||
|
||||
public TileRenderNone(String id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk, BlockFace face) {
|
||||
return EmptyModel.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsOwnRenderable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@ -18,19 +18,25 @@
|
||||
|
||||
package ru.windcorp.progressia.client.world.tile;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import glm.vec._3.Vec3;
|
||||
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.Shape;
|
||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
|
||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
|
||||
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerCube.OpaqueSurface;
|
||||
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSurface.TileOptimizedSurface;
|
||||
import ru.windcorp.progressia.common.util.Vectors;
|
||||
import ru.windcorp.progressia.common.world.ChunkData;
|
||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
||||
|
||||
public abstract class TileRenderSurface extends TileRender implements OpaqueSurface {
|
||||
public abstract class TileRenderSurface extends TileRender implements TileOptimizedSurface {
|
||||
|
||||
private final Texture texture;
|
||||
|
||||
@ -39,26 +45,51 @@ public abstract class TileRenderSurface extends TileRender implements OpaqueSurf
|
||||
this.texture = texture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Texture getTexture(BlockFace face) {
|
||||
public TileRenderSurface(String id) {
|
||||
this(id, null);
|
||||
}
|
||||
|
||||
public Texture getTexture(BlockFace blockFace) {
|
||||
return texture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Renderable createRenderable(BlockFace face) {
|
||||
ShapeRenderProgram program = WorldRenderProgram.getDefault();
|
||||
public Vec4 getColorMultiplier(BlockFace blockFace) {
|
||||
return Colors.WHITE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void getFaces(
|
||||
ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace,
|
||||
boolean inner,
|
||||
Consumer<Face> output,
|
||||
Vec3 offset
|
||||
) {
|
||||
output.accept(createFace(chunk, blockInChunk, blockFace, inner, offset));
|
||||
}
|
||||
|
||||
private Face createFace(
|
||||
ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace,
|
||||
boolean inner,
|
||||
Vec3 offset
|
||||
) {
|
||||
return Faces.createBlockFace(
|
||||
WorldRenderProgram.getDefault(),
|
||||
getTexture(blockFace),
|
||||
getColorMultiplier(blockFace),
|
||||
offset,
|
||||
blockFace,
|
||||
inner
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace) {
|
||||
return new Shape(
|
||||
Usage.STATIC,
|
||||
WorldRenderProgram.getDefault(),
|
||||
Faces.createBlockFace(
|
||||
program,
|
||||
getTexture(face),
|
||||
Colors.WHITE,
|
||||
new Vec3(0, 0, 0),
|
||||
face,
|
||||
false
|
||||
)
|
||||
|
||||
createFace(chunk, blockInChunk, blockFace, false, Vectors.ZERO_3),
|
||||
createFace(chunk, blockInChunk, blockFace, true, Vectors.ZERO_3)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.common.modules;
|
||||
|
||||
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class Module extends Namespaced {
|
||||
|
||||
private final Set<Task> tasks = new HashSet<>();
|
||||
private final Map<String, String> meta = new HashMap<>();
|
||||
|
||||
/**
|
||||
* @param id the identifier of a task object.
|
||||
* Its format is restricted by {@link Namespaced}.
|
||||
* @see Namespaced#Namespaced
|
||||
*/
|
||||
public Module(String id) {
|
||||
super(id);
|
||||
meta.put("id", id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return meta information of the module as {@link Map}.
|
||||
*/
|
||||
public Map<String, String> getMeta() {
|
||||
return Collections.unmodifiableMap(meta);
|
||||
}
|
||||
|
||||
public Set<Task> getTasks() {
|
||||
return Collections.unmodifiableSet(tasks);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param task that will be attached to the module.
|
||||
* A task can't be added to any module second time.
|
||||
*/
|
||||
public void addTask(Task task) {
|
||||
task.setOwner(this);
|
||||
tasks.add(task);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.common.modules;
|
||||
|
||||
public class ModuleBuilder {
|
||||
private final Module module;
|
||||
|
||||
public ModuleBuilder(String id) {
|
||||
module = new Module(id);
|
||||
}
|
||||
|
||||
public ModuleBuilder AddTask(Task task) {
|
||||
module.addTask(task);
|
||||
return this;
|
||||
}
|
||||
}
|
130
src/main/java/ru/windcorp/progressia/common/modules/Task.java
Normal file
130
src/main/java/ru/windcorp/progressia/common/modules/Task.java
Normal file
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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.common.modules;
|
||||
|
||||
import ru.windcorp.jputil.chars.StringUtil;
|
||||
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
||||
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public abstract class Task
|
||||
extends Namespaced
|
||||
implements Runnable {
|
||||
|
||||
private final Set<Task> requiredTasks = new HashSet<>();
|
||||
private final AtomicBoolean isDone = new AtomicBoolean(false);
|
||||
private final AtomicBoolean isActive = new AtomicBoolean(false);
|
||||
private Module owner;
|
||||
|
||||
/**
|
||||
* @param id the identifier of a task object.
|
||||
* Its format is restricted by {@link Namespaced}.
|
||||
* @see Namespaced#Namespaced
|
||||
*/
|
||||
protected Task(String id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (!canRun()) {
|
||||
List<Task> undoneTasks = new ArrayList<>();
|
||||
for (Iterator<Task> iterator = requiredTasks.iterator(); iterator.hasNext(); ) {
|
||||
Task t = iterator.next();
|
||||
if (!t.isDone()) {
|
||||
undoneTasks.add(t);
|
||||
}
|
||||
}
|
||||
|
||||
throw CrashReports.report(new Throwable(),
|
||||
"The following required Tasks are not done:\n%s",
|
||||
StringUtil.iterableToString(undoneTasks, "\n"));
|
||||
} else if (isDone()) {
|
||||
throw CrashReports.report(new Throwable(),
|
||||
"The task cannot be performed second time");
|
||||
} else {
|
||||
isActive.set(true);
|
||||
perform();
|
||||
isActive.set(false);
|
||||
isDone.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The method is to be invoked in run().
|
||||
* @see Task#run()
|
||||
*/
|
||||
protected abstract void perform();
|
||||
|
||||
public boolean isDone() {
|
||||
return isDone.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if the {@link Task#run()} method is being invoked at the moment or not.
|
||||
*/
|
||||
public boolean isActive() {
|
||||
return isActive.get();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return true - the method is not done and not active
|
||||
* and all requirement tasks are done, false - otherwise.
|
||||
*/
|
||||
public boolean canRun() {
|
||||
if (this.isActive.get() || isDone.get()) return false;
|
||||
for (Iterator<Task> iterator = requiredTasks.iterator(); iterator.hasNext(); ) {
|
||||
Task reqTask = iterator.next();
|
||||
if (!reqTask.isDone()) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public Set<Task> getRequiredTasks() {
|
||||
return Collections.unmodifiableSet(requiredTasks);
|
||||
}
|
||||
|
||||
public void addRequiredTask(Task task) {
|
||||
requiredTasks.add(task);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the module the task is attached to.
|
||||
*/
|
||||
public Module getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param module to which the task will be attached.
|
||||
* Only one module can be the owner of the task.
|
||||
*/
|
||||
public void setOwner(Module module) {
|
||||
if (owner != null) {
|
||||
throw CrashReports.crash(
|
||||
null
|
||||
, "Could not set %s as owner of %s, because %s is already owner of it.",
|
||||
module.getId(), this.getId(), this.owner.getId());
|
||||
} else {
|
||||
owner = module;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,173 @@
|
||||
/*
|
||||
* 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.common.modules;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
||||
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static java.util.concurrent.Executors.newFixedThreadPool;
|
||||
|
||||
public final class TaskManager {
|
||||
private static final TaskManager INSTANCE = new TaskManager();
|
||||
|
||||
private final Set<Task> tasks = new HashSet<>();
|
||||
private final Set<Module> modules = new HashSet<>();
|
||||
private final ExecutorService executorService;
|
||||
|
||||
final AtomicBoolean loadingDone;
|
||||
final AtomicInteger activeThreadsCount;
|
||||
|
||||
private final Map<Thread, Namespaced> loadersMonitorMap;
|
||||
|
||||
private TaskManager() {
|
||||
loadingDone = new AtomicBoolean(false);
|
||||
activeThreadsCount = new AtomicInteger(0);
|
||||
executorService = newFixedThreadPool(
|
||||
Runtime.getRuntime().availableProcessors(), Thread::new);
|
||||
loadersMonitorMap = new HashMap<>(Runtime.getRuntime().availableProcessors());
|
||||
}
|
||||
|
||||
public static TaskManager getInstance() {
|
||||
return TaskManager.INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the module and its tasks that are
|
||||
* to be performed by {@link TaskManager#startLoading()}.
|
||||
*
|
||||
* @param module from where to register tasks for loading.
|
||||
*/
|
||||
public void registerModule(Module module) {
|
||||
tasks.addAll(module.getTasks());
|
||||
modules.add(module);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a task that is to be performed
|
||||
* by {@link TaskManager#startLoading()}.
|
||||
*
|
||||
* @param task to register for loading.
|
||||
*/
|
||||
public void addTask(Task task) {
|
||||
tasks.add(task);
|
||||
}
|
||||
|
||||
public boolean isLoadingDone() {
|
||||
return loadingDone.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* The method to start loading. It will perform every registered task.
|
||||
*/
|
||||
public void startLoading() {
|
||||
LogManager.getLogger().info("Loading is started");
|
||||
int procAmount = Runtime.getRuntime().availableProcessors();
|
||||
for (int i = 0; i < procAmount; i++) {
|
||||
executorService.submit(loaderTask);
|
||||
}
|
||||
|
||||
waitForLoadingEnd();
|
||||
if (!tasks.isEmpty()) {
|
||||
throw CrashReports.crash(null, "Loading is failed");
|
||||
}
|
||||
LogManager.getLogger().info("Loading is finished");
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Task - founded registered task with {@link Task#canRun()} = true;
|
||||
* null - there is no available task found.
|
||||
* @see Task#canRun()
|
||||
*/
|
||||
synchronized Task getRunnableTask() {
|
||||
if (!tasks.isEmpty()) {
|
||||
for (Iterator<Task> iterator = tasks.iterator(); iterator.hasNext(); ) {
|
||||
Task t = iterator.next();
|
||||
if (t.canRun()) {
|
||||
tasks.remove(t);
|
||||
return t;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the thread that is performing this method
|
||||
* to wait until the loading is not done.
|
||||
*/
|
||||
private void waitForLoadingEnd() {
|
||||
synchronized (tasks) {
|
||||
while (!loadingDone.get()) {
|
||||
try {
|
||||
tasks.wait();
|
||||
} catch (InterruptedException ignored) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void stopLoading() {
|
||||
loadingDone.set(true);
|
||||
synchronized (tasks) {
|
||||
tasks.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a map where key is a thread making loading
|
||||
* and where value is a {@link Namespaced} of {@link Task} that is being performed by it
|
||||
* at the moment.
|
||||
* @see Namespaced
|
||||
*/
|
||||
public Map<Thread, Namespaced> getLoadersMonitorMap() {
|
||||
return Collections.unmodifiableMap(loadersMonitorMap);
|
||||
}
|
||||
|
||||
private final Runnable loaderTask = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
while (!loadingDone.get()) {
|
||||
Task t = getRunnableTask();
|
||||
if (t != null) {
|
||||
activeThreadsCount.incrementAndGet();
|
||||
loadersMonitorMap.put(Thread.currentThread(), t);
|
||||
t.run();
|
||||
loadersMonitorMap.put(Thread.currentThread(), null);
|
||||
activeThreadsCount.decrementAndGet();
|
||||
synchronized (tasks) {
|
||||
tasks.notifyAll();
|
||||
}
|
||||
} else if (activeThreadsCount.get() > 0) {
|
||||
try {
|
||||
synchronized (tasks) {
|
||||
tasks.wait();
|
||||
}
|
||||
} catch (InterruptedException ignored) {}
|
||||
} else {
|
||||
stopLoading();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.common.resource;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import ru.windcorp.progressia.Progressia;
|
||||
|
||||
public class ClasspathResourceReader implements ResourceReader {
|
||||
|
||||
@Override
|
||||
public InputStream read(String name) {
|
||||
return Progressia.class.getClassLoader().getResourceAsStream(name);
|
||||
}
|
||||
|
||||
}
|
@ -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.common.resource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public class FilesystemResourceReader implements ResourceReader {
|
||||
|
||||
@Override
|
||||
public InputStream read(String name) {
|
||||
try {
|
||||
return Files.newInputStream(Paths.get(name));
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -30,19 +30,24 @@ import org.lwjgl.BufferUtils;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.io.CharStreams;
|
||||
|
||||
import ru.windcorp.progressia.Progressia;
|
||||
import ru.windcorp.progressia.common.util.Named;
|
||||
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
||||
|
||||
public class Resource extends Named {
|
||||
|
||||
public Resource(String name) {
|
||||
private final ResourceReader resourceReader;
|
||||
|
||||
public Resource(String name, ResourceReader resourceReader) {
|
||||
super(name);
|
||||
this.resourceReader = resourceReader;
|
||||
}
|
||||
|
||||
public InputStream getInputStream() {
|
||||
// TODO Do proper resource lookup
|
||||
return Progressia.class.getClassLoader().getResourceAsStream(getName());
|
||||
return getResourceReader().read(getName());
|
||||
}
|
||||
|
||||
public ResourceReader getResourceReader() {
|
||||
return resourceReader;
|
||||
}
|
||||
|
||||
public Reader getReader() {
|
||||
|
@ -20,8 +20,15 @@ package ru.windcorp.progressia.common.resource;
|
||||
|
||||
public class ResourceManager {
|
||||
|
||||
private static final ResourceReader CLASSPATH_READER = new ClasspathResourceReader();
|
||||
private static final ResourceReader FILESYSTEM_READER = new FilesystemResourceReader();
|
||||
|
||||
public static Resource getResource(String name) {
|
||||
return new Resource(name);
|
||||
return new Resource(name, CLASSPATH_READER);
|
||||
}
|
||||
|
||||
public static Resource getFileResource(String name) {
|
||||
return new Resource(name, FILESYSTEM_READER);
|
||||
}
|
||||
|
||||
public static Resource getTextureResource(String name) {
|
||||
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.common.resource;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public interface ResourceReader {
|
||||
|
||||
InputStream read(String name);
|
||||
|
||||
}
|
@ -203,13 +203,12 @@ public class CrashReports {
|
||||
if (provider == null)
|
||||
continue;
|
||||
|
||||
addSeparator(output);
|
||||
|
||||
try {
|
||||
Map<String, String> buf = new HashMap<>();
|
||||
provider.provideContext(buf);
|
||||
|
||||
if (!buf.isEmpty()) {
|
||||
addSeparator(output);
|
||||
output.append(StringUtil.center(provider.getName(), 80)).append("\n");
|
||||
for (Map.Entry<String, String> entry : buf.entrySet()) {
|
||||
output.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n");
|
||||
|
@ -26,12 +26,12 @@ public class RAMContextProvider implements ContextProvider {
|
||||
|
||||
@Override
|
||||
public void provideContext(Map<String, String> output) {
|
||||
output.put("Max Memory", Long.toString(Runtime.getRuntime().maxMemory() / 1024 / 1024) + " MB");
|
||||
output.put("Total Memory", Long.toString(Runtime.getRuntime().totalMemory() / 1024 / 1024) + " MB");
|
||||
output.put("Free Memory", Long.toString(Runtime.getRuntime().freeMemory() / 1024 / 1024) + " MB");
|
||||
output.put("Max Memory", Runtime.getRuntime().maxMemory() / 1024 / 1024 + " MB");
|
||||
output.put("Total Memory", Runtime.getRuntime().totalMemory() / 1024 / 1024 + " MB");
|
||||
output.put("Free Memory", Runtime.getRuntime().freeMemory() / 1024 / 1024 + " MB");
|
||||
output.put(
|
||||
"Used Memory",
|
||||
Long.toString((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1024 / 1024)
|
||||
(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1024 / 1024
|
||||
+ " MB"
|
||||
);
|
||||
}
|
||||
|
@ -16,38 +16,26 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package ru.windcorp.progressia.client.world.cro;
|
||||
package ru.windcorp.progressia.common.util.crash.providers;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsBackend;
|
||||
import ru.windcorp.progressia.common.util.crash.ContextProvider;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ChunkRenderOptimizers {
|
||||
public class ScreenContextProvider implements ContextProvider {
|
||||
|
||||
private ChunkRenderOptimizers() {
|
||||
@Override
|
||||
public void provideContext(Map<String, String> output) {
|
||||
if (GraphicsBackend.isGLFWInitialized()) {
|
||||
output.put("Refresh rate", GraphicsBackend.getRefreshRate() + " Hz");
|
||||
output.put("Size", GraphicsBackend.getFrameWidth() + "x" + GraphicsBackend.getFrameHeight());
|
||||
output.put("Fullscreen", GraphicsBackend.isFullscreen() ? "enabled" : "disabled");
|
||||
}
|
||||
}
|
||||
|
||||
private static final Map<String, ChunkRenderOptimizerSupplier> SUPPLIERS = new HashMap<>();
|
||||
|
||||
static {
|
||||
register(
|
||||
ChunkRenderOptimizerSupplier.of(
|
||||
"Default:OpaqueCube",
|
||||
ChunkRenderOptimizerCube::new
|
||||
)
|
||||
);
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Screen Context Provider";
|
||||
}
|
||||
|
||||
public static ChunkRenderOptimizerSupplier getSupplier(String id) {
|
||||
return SUPPLIERS.get(id);
|
||||
}
|
||||
|
||||
public static void register(ChunkRenderOptimizerSupplier supplier) {
|
||||
SUPPLIERS.put(supplier.getId(), supplier);
|
||||
}
|
||||
|
||||
public static Collection<ChunkRenderOptimizerSupplier> getAllSuppliers() {
|
||||
return SUPPLIERS.values();
|
||||
}
|
||||
|
||||
}
|
@ -159,4 +159,8 @@ public class Coordinates {
|
||||
return output;
|
||||
}
|
||||
|
||||
public static boolean isOnChunkBorder(int blockInChunk) {
|
||||
return blockInChunk == 0 || blockInChunk == BLOCKS_PER_CHUNK - 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -27,10 +27,12 @@ import glm.vec._3.i.Vec3i;
|
||||
public class BlockRelation {
|
||||
|
||||
private final Vec3i vector = new Vec3i();
|
||||
private final Vec3 floatVector = new Vec3();
|
||||
private final Vec3 normalized = new Vec3();
|
||||
|
||||
public BlockRelation(int x, int y, int z) {
|
||||
vector.set(x, y, z);
|
||||
floatVector.set(x, y, z);
|
||||
normalized.set(x, y, z).normalize();
|
||||
}
|
||||
|
||||
@ -42,6 +44,10 @@ public class BlockRelation {
|
||||
return vector;
|
||||
}
|
||||
|
||||
public Vec3 getFloatVector() {
|
||||
return floatVector;
|
||||
}
|
||||
|
||||
public Vec3 getNormalized() {
|
||||
return normalized;
|
||||
}
|
||||
|
@ -92,6 +92,72 @@ public interface GenericChunk<Self extends GenericChunk<Self, B, T, TS>, B exten
|
||||
return result;
|
||||
}
|
||||
|
||||
default boolean isSurfaceBiC(Vec3i blockInChunk) {
|
||||
int hits = 0;
|
||||
|
||||
if (Coordinates.isOnChunkBorder(blockInChunk.x)) hits++;
|
||||
if (Coordinates.isOnChunkBorder(blockInChunk.y)) hits++;
|
||||
if (Coordinates.isOnChunkBorder(blockInChunk.z)) hits++;
|
||||
|
||||
return hits >= 1;
|
||||
}
|
||||
|
||||
default boolean isSurfaceBiW(Vec3i blockInWorld) {
|
||||
Vec3i v = Vectors.grab3i();
|
||||
|
||||
v = Coordinates.getInWorld(getPosition(), Vectors.ZERO_3i, v);
|
||||
v = blockInWorld.sub(v, v);
|
||||
|
||||
boolean result = isSurfaceBiC(v);
|
||||
|
||||
Vectors.release(v);
|
||||
return result;
|
||||
}
|
||||
|
||||
default boolean isEdgeBiC(Vec3i blockInChunk) {
|
||||
int hits = 0;
|
||||
|
||||
if (Coordinates.isOnChunkBorder(blockInChunk.x)) hits++;
|
||||
if (Coordinates.isOnChunkBorder(blockInChunk.y)) hits++;
|
||||
if (Coordinates.isOnChunkBorder(blockInChunk.z)) hits++;
|
||||
|
||||
return hits >= 2;
|
||||
}
|
||||
|
||||
default boolean isEdgeBiW(Vec3i blockInWorld) {
|
||||
Vec3i v = Vectors.grab3i();
|
||||
|
||||
v = Coordinates.getInWorld(getPosition(), Vectors.ZERO_3i, v);
|
||||
v = blockInWorld.sub(v, v);
|
||||
|
||||
boolean result = isEdgeBiC(v);
|
||||
|
||||
Vectors.release(v);
|
||||
return result;
|
||||
}
|
||||
|
||||
default boolean isVertexBiC(Vec3i blockInChunk) {
|
||||
int hits = 0;
|
||||
|
||||
if (Coordinates.isOnChunkBorder(blockInChunk.x)) hits++;
|
||||
if (Coordinates.isOnChunkBorder(blockInChunk.y)) hits++;
|
||||
if (Coordinates.isOnChunkBorder(blockInChunk.z)) hits++;
|
||||
|
||||
return hits == 3;
|
||||
}
|
||||
|
||||
default boolean isVertexBiW(Vec3i blockInWorld) {
|
||||
Vec3i v = Vectors.grab3i();
|
||||
|
||||
v = Coordinates.getInWorld(getPosition(), Vectors.ZERO_3i, v);
|
||||
v = blockInWorld.sub(v, v);
|
||||
|
||||
boolean result = isVertexBiC(v);
|
||||
|
||||
Vectors.release(v);
|
||||
return result;
|
||||
}
|
||||
|
||||
default void forEachBiC(Consumer<? super Vec3i> action) {
|
||||
VectorUtil.iterateCuboid(
|
||||
0,
|
||||
|
@ -183,6 +183,18 @@ public class Server {
|
||||
return this.serverThread.getTicker().getTPS();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of ticks performed since the server has started. This
|
||||
* value resets on shutdowns. The counter is incremented at the end of a
|
||||
* tick.
|
||||
*
|
||||
* @return the number of times the world has finished a tick since the
|
||||
* server has started.
|
||||
*/
|
||||
public long getUptimeTicks() {
|
||||
return this.serverThread.getTicker().getUptimeTicks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link WorldAccessor} object for this server. Use the
|
||||
* provided accessor to
|
||||
|
@ -82,6 +82,7 @@ public class TickerCoordinator {
|
||||
private boolean isTickStartSet = false;
|
||||
private long tickStart = -1;
|
||||
private double tickLength = 1.0 / 20; // Do something about it
|
||||
private long ticks = 0;
|
||||
|
||||
private final Logger logger = LogManager.getLogger("Ticker Coordinator");
|
||||
|
||||
@ -152,6 +153,10 @@ public class TickerCoordinator {
|
||||
return 1 / tickLength;
|
||||
}
|
||||
|
||||
public long getUptimeTicks() {
|
||||
return ticks;
|
||||
}
|
||||
|
||||
private void onTickStart() {
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
@ -164,6 +169,10 @@ public class TickerCoordinator {
|
||||
tickStart = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
private void onTickEnd() {
|
||||
ticks++;
|
||||
}
|
||||
|
||||
/*
|
||||
* runOneTick & Friends
|
||||
*/
|
||||
@ -183,6 +192,8 @@ public class TickerCoordinator {
|
||||
passes++;
|
||||
}
|
||||
|
||||
onTickEnd();
|
||||
|
||||
logger.debug("Tick complete; run {} passes", passes);
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
@ -191,7 +202,7 @@ public class TickerCoordinator {
|
||||
// ...or almost silently
|
||||
logger.debug("Tick interrupted. WTF?");
|
||||
} catch (Exception e) {
|
||||
crash(e, "Coordinator");
|
||||
throw CrashReports.report(e, "Coordinator");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,7 @@ public class LayerAbout extends GUILayer {
|
||||
new Label(
|
||||
"Version",
|
||||
font,
|
||||
new MutableStringLocalized("LayerAbout.Version").format("TechDemo")
|
||||
new MutableStringLocalized("LayerAbout.Version").format("pre-alpha 1")
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -18,16 +18,12 @@
|
||||
|
||||
package ru.windcorp.progressia.test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import glm.vec._3.Vec3;
|
||||
import glm.vec._4.Vec4;
|
||||
import ru.windcorp.progressia.client.Client;
|
||||
import ru.windcorp.progressia.client.ClientState;
|
||||
import ru.windcorp.progressia.client.graphics.Colors;
|
||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsBackend;
|
||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||
import ru.windcorp.progressia.client.graphics.font.Font;
|
||||
import ru.windcorp.progressia.client.graphics.gui.DynamicLabel;
|
||||
@ -44,6 +40,11 @@ import ru.windcorp.progressia.common.util.dynstr.DynamicStrings;
|
||||
import ru.windcorp.progressia.server.Server;
|
||||
import ru.windcorp.progressia.server.ServerState;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class LayerTestGUI extends GUILayer {
|
||||
|
||||
public LayerTestGUI() {
|
||||
@ -110,6 +111,22 @@ public class LayerTestGUI extends GUILayer {
|
||||
)
|
||||
);
|
||||
|
||||
panel.addChild(
|
||||
new Label(
|
||||
"FullscreenDisplay",
|
||||
font,
|
||||
tmp_dynFormat("LayerTestGUI.IsFullscreen", GraphicsBackend::isFullscreen)
|
||||
)
|
||||
);
|
||||
|
||||
panel.addChild(
|
||||
new Label(
|
||||
"VSyncDisplay",
|
||||
font,
|
||||
tmp_dynFormat("LayerTestGUI.IsVSync", GraphicsBackend::isVSyncEnabled)
|
||||
)
|
||||
);
|
||||
|
||||
panel.addChild(
|
||||
new DynamicLabel(
|
||||
"FPSDisplay",
|
||||
|
@ -33,12 +33,14 @@ import org.lwjgl.glfw.GLFW;
|
||||
import glm.vec._3.Vec3;
|
||||
import glm.vec._3.i.Vec3i;
|
||||
import ru.windcorp.progressia.client.ClientState;
|
||||
import ru.windcorp.progressia.client.audio.SoundEffect;
|
||||
import ru.windcorp.progressia.client.audio.Sound;
|
||||
import ru.windcorp.progressia.client.comms.controls.*;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
|
||||
import ru.windcorp.progressia.client.graphics.world.Selection;
|
||||
import ru.windcorp.progressia.client.world.block.*;
|
||||
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerRegistry;
|
||||
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSurface;
|
||||
import ru.windcorp.progressia.client.world.entity.*;
|
||||
import ru.windcorp.progressia.client.world.tile.*;
|
||||
import ru.windcorp.progressia.common.collision.AABB;
|
||||
@ -285,6 +287,15 @@ public class TestContent {
|
||||
)
|
||||
);
|
||||
logic.register(ControlLogic.of("Test:PlaceTile", TestContent::onTilePlaceReceived));
|
||||
|
||||
triggers.register(
|
||||
ControlTriggers.localOf(
|
||||
"Test:StartNextMusic",
|
||||
KeyEvent.class,
|
||||
TestMusicPlayer::startNextNow,
|
||||
KeyMatcher.of(GLFW.GLFW_KEY_M).matcher()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private static void register(BlockData x) {
|
||||
@ -358,7 +369,7 @@ public class TestContent {
|
||||
|
||||
private static void onBlockBreakTrigger(ControlData control) {
|
||||
((ControlBreakBlockData) control).setBlockInWorld(getSelection().getBlock());
|
||||
SoundEffect sfx = new SoundEffect("Progressia:BlockDestroy");
|
||||
Sound sfx = new Sound("Progressia:BlockDestroy");
|
||||
sfx.setPosition(getSelection().getPoint());
|
||||
sfx.setPitch((float) (Math.random() + 1 * 0.5));
|
||||
sfx.play(false);
|
||||
@ -420,6 +431,7 @@ public class TestContent {
|
||||
|
||||
private static void registerMisc() {
|
||||
ChunkIO.registerCodec(new TestChunkCodec());
|
||||
ChunkRenderOptimizerRegistry.getInstance().register("Core:SurfaceOptimizer", ChunkRenderOptimizerSurface::new);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -200,7 +200,7 @@ public class TestEntityRenderHuman extends EntityRender {
|
||||
).setOrigin(ox, oy, oz).setSize(sx, sy, sz).create()
|
||||
);
|
||||
|
||||
return new StaticModel(b);
|
||||
return b.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -386,7 +386,7 @@ public class TestEntityRenderJavapony extends EntityRender {
|
||||
).setOrigin(16, -4, 8).setSize(4, 8, 4).create()
|
||||
);
|
||||
|
||||
return new StaticModel(b);
|
||||
return b.build();
|
||||
}
|
||||
|
||||
private static Renderable createLeg(
|
||||
@ -427,7 +427,7 @@ public class TestEntityRenderJavapony extends EntityRender {
|
||||
).setOrigin(-8, -8, -32).setSize(16, 16, 32).create()
|
||||
);
|
||||
|
||||
return new StaticModel(b);
|
||||
return b.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
152
src/main/java/ru/windcorp/progressia/test/TestMusicPlayer.java
Normal file
152
src/main/java/ru/windcorp/progressia/test/TestMusicPlayer.java
Normal file
@ -0,0 +1,152 @@
|
||||
/*
|
||||
* 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.test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
|
||||
import ru.windcorp.progressia.client.audio.AudioFormat;
|
||||
import ru.windcorp.progressia.client.audio.AudioManager;
|
||||
import ru.windcorp.progressia.client.audio.AudioRegistry;
|
||||
import ru.windcorp.progressia.client.audio.Music;
|
||||
import ru.windcorp.progressia.client.audio.Sound;
|
||||
import ru.windcorp.progressia.client.audio.backend.SoundType;
|
||||
import ru.windcorp.progressia.common.resource.ResourceManager;
|
||||
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
||||
|
||||
public class TestMusicPlayer implements Runnable {
|
||||
|
||||
private static final int MIN_SILENCE = 15 * 1000; // 15 seconds
|
||||
private static final int MAX_SILENCE = 60 * 1000; // one minute
|
||||
|
||||
private static TestMusicPlayer instance = null;
|
||||
|
||||
private final List<SoundType> compositions = new ArrayList<>();
|
||||
|
||||
private final Random random = new Random();
|
||||
private long nextStart;
|
||||
private Sound lastStarted = null;
|
||||
|
||||
public TestMusicPlayer() {
|
||||
this.nextStart = System.currentTimeMillis();
|
||||
|
||||
instance = this;
|
||||
}
|
||||
|
||||
public static void start() {
|
||||
Thread thread = new Thread(new TestMusicPlayer(), "Music Thread");
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
loadCompositions();
|
||||
|
||||
if (compositions.isEmpty()) {
|
||||
LogManager.getLogger().warn("No music found");
|
||||
return;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
|
||||
try {
|
||||
synchronized (this) {
|
||||
while (true) {
|
||||
long now = System.currentTimeMillis();
|
||||
if (nextStart > now) {
|
||||
wait(nextStart - now);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LogManager.getLogger().warn("Received interrupt in music thread, terminating thread...");
|
||||
return;
|
||||
}
|
||||
|
||||
startNextComposition();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void loadCompositions() {
|
||||
try {
|
||||
|
||||
Path directory = Paths.get("music");
|
||||
|
||||
if (!Files.isDirectory(directory)) {
|
||||
Files.createDirectories(directory);
|
||||
}
|
||||
|
||||
Iterator<Path> it = Files.walk(directory).filter(Files::isRegularFile).iterator();
|
||||
int i = 0;
|
||||
|
||||
while (it.hasNext()) {
|
||||
String file = it.next().toString();
|
||||
if (!file.endsWith(".ogg") && !file.endsWith(".oga")) {
|
||||
LogManager.getLogger().warn("Skipping " + file + ": not .ogg nor .oga");
|
||||
}
|
||||
|
||||
String id = "Progressia:Music" + (i++);
|
||||
|
||||
AudioManager.loadSound(ResourceManager.getFileResource(file.toString()), id, AudioFormat.STEREO);
|
||||
SoundType composition = AudioRegistry.getInstance().get(id);
|
||||
compositions.add(composition);
|
||||
|
||||
LogManager.getLogger().info("Loaded " + file);
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
throw CrashReports.report(e, "Could not load music");
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void startNextComposition() {
|
||||
int index = random.nextInt(compositions.size());
|
||||
SoundType composition = compositions.get(index);
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
long durationInMs = (long) (composition.getDuration() * 1000);
|
||||
long silence = random.nextInt(MAX_SILENCE - MIN_SILENCE) + MIN_SILENCE;
|
||||
|
||||
nextStart = now + durationInMs + silence;
|
||||
|
||||
lastStarted = new Music(composition);
|
||||
lastStarted.play(false);
|
||||
}
|
||||
|
||||
public static void startNextNow() {
|
||||
if (instance == null) return;
|
||||
|
||||
synchronized (instance) {
|
||||
instance.nextStart = System.currentTimeMillis();
|
||||
instance.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -187,6 +187,20 @@ public class TestPlayerControls {
|
||||
handleEscape();
|
||||
break;
|
||||
|
||||
case GLFW.GLFW_KEY_F11:
|
||||
if (!event.isPress())
|
||||
return false;
|
||||
GraphicsInterface.makeFullscreen(!GraphicsBackend.isFullscreen());
|
||||
updateGUI();
|
||||
break;
|
||||
|
||||
case GLFW.GLFW_KEY_F12:
|
||||
if (!event.isPress())
|
||||
return false;
|
||||
GraphicsBackend.setVSyncEnabled(!GraphicsBackend.isVSyncEnabled());
|
||||
updateGUI();
|
||||
break;
|
||||
|
||||
case GLFW.GLFW_KEY_F3:
|
||||
if (!event.isPress())
|
||||
return false;
|
||||
|
@ -20,3 +20,5 @@ LayerTestGUI.PosDisplay.NA.Entity = Pos: entity n/a
|
||||
LayerTestGUI.SelectedBlockDisplay = %s Block: %s
|
||||
LayerTestGUI.SelectedTileDisplay = %s Tile: %s
|
||||
LayerTestGUI.PlacementModeHint = (Blocks %s Tiles: Ctrl + Mouse Wheel)
|
||||
LayerTestGUI.IsFullscreen = Fullscreen: %5s (F11)
|
||||
LayerTestGUI.IsVSync = VSync: %5s (F12)
|
@ -20,3 +20,5 @@ LayerTestGUI.PosDisplay.NA.Entity = Поз: сущность н/д
|
||||
LayerTestGUI.SelectedBlockDisplay = %s Блок: %s
|
||||
LayerTestGUI.SelectedTileDisplay = %s Плитка: %s
|
||||
LayerTestGUI.PlacementModeHint = (Блок %s плитки: Ctrl + прокрутка)
|
||||
LayerTestGUI.IsFullscreen = Полный экран: %5s (F11)
|
||||
LayerTestGUI.IsVSync = Верт. синхр.: %5s (F12)
|
Reference in New Issue
Block a user