30 Commits

Author SHA1 Message Date
8c7c37c9c0 Code cleanup 2021-07-14 18:23:10 +03:00
3b24ee0531 Documentation for module system and minor changes. 2021-07-10 21:46:15 +03:00
953614a7d8 Now you cannot run tasks second time or if requirements are not met 2021-07-10 20:19:57 +03:00
7f57510ac8 Licensed module system. Minor changes
- Added license commetary to each file of module system
- Added default meta info generation for Module
2021-07-10 08:29:09 +03:00
c7842935ba Loaders are observable. Modules refactoring
- Now it is possible to monitor loaders and tasks they perform
- Now it is NOT possible to add  dublicate tasks and modules
2021-07-09 18:12:30 +03:00
f520d4b2c6 TaskManager bug fixes and code cleanup 2021-07-07 17:02:38 +03:00
5a06788652 Refactored modules system 2021-07-07 13:37:11 +03:00
56e9be727b Removed debug code 2021-07-05 11:32:58 +03:00
e795d76367 Update .gitignore
- added log files to ignore
2021-07-05 11:32:11 +03:00
a9724d9d4c Developed module loader algorithm 2021-06-30 19:59:30 +03:00
3859db5b27 Added TaskManager
- Working on modules system
2021-06-27 18:27:56 +03:00
513feb1093 Merge branch 'master' into moduleSystem 2021-06-12 21:05:15 +03:00
531a8c99c3 Added CONTRIBUTING.md. Discussion is welcome. 2021-04-27 23:22:48 +03:00
32851b8fb0 Added a serverside uptime tick counter 2021-04-09 20:34:47 +03:00
c49fdfa5ff Added a crude music player and fixed a lot of OpenAL stuff
- Game will now play all Vorbis files in music/ directory (at runtime)
  - Random order with random pauses (15 to 60 sec)
  - Force start by pressing M
- Added AudioRegistry for performance
- Speakers now can change data correctly
- Added ResourceReaders
  - Classpath and Filesystem for now
- Added local controls. Use with ControlTriggers.localOf(...)

NB: No actual music files included (yet). Place them in music/ directory
of the game directory (e.g. in <project>/run/music).
2021-03-26 23:51:51 +03:00
9d7f69892b Removed unused import and renamed VSYNC to VSync 2021-03-26 21:57:54 +03:00
73d24d36f4 Added support VSYNC and fixed crashreporter formatter 2021-01-31 13:29:10 +03:00
bdb3bff570 Merge branch 'master' of git@github.com:OLEGSHA/Progressia.git 2021-01-31 13:23:05 +03:00
6997bb1104 Fixed compilation issue 2021-01-31 13:12:10 +03:00
eac0a34516 Clarified RAM requirement in README.md 2021-01-30 22:59:01 +03:00
127d1c3d87 Refactored Sound Engine
- Renamed SoundEffect to Sound
- Now Music inherits Sound
- Now every sound has parameter timeLength
2021-01-29 18:35:05 +03:00
26a35f306c Fixed chunk model updates 2021-01-26 22:21:03 +03:00
52f3f653d8 Added Fullscreen mode 2021-01-26 21:23:33 +03:00
fc85eb5658 Removed accidentally committed local build.gradle 2021-01-25 10:46:02 +03:00
260562310a Refactored CROs, removed internal chunk borders. Documented CROs.
- Refactored CROs
- Renamed CROOpaqueCube to CROSurface (including relevant interfaces)
- Optimized CROS
  - CROS now takes neighbor chunks into account
- Refactored default OptimizedSurface implementations
- Documented some CRO code
2021-01-24 20:48:38 +03:00
85edc07c75 Incremented version and corrected whitespace in buildPackages.sh 2021-01-21 18:15:50 +03:00
fcf225f9c7 Updated NSIS installer
Added:
- game icon
- left side image
- desktop shortcut
- main menu shortcut
- working directory in AppData
2021-01-20 18:09:59 +03:00
942c665d73 Developing module systme
- Added Module class
- minor changes in Task class
2021-01-10 15:30:05 +03:00
5a521fc131 Refactored Task class
- Now crash reports have stacktrace
- Minor changes
2021-01-09 20:52:19 +03:00
361d795ab1 Added Task class 2021-01-09 19:36:11 +03:00
67 changed files with 2301 additions and 778 deletions

3
.gitignore vendored
View File

@ -30,8 +30,11 @@ build_packages/*
!build_packages/NSIS !build_packages/NSIS
build_packages/NSIS/* build_packages/NSIS/*
!build_packages/NSIS/ProgressiaInstaller.nsi !build_packages/NSIS/ProgressiaInstaller.nsi
!build_packages/NSIS/logo.ico
!build_packages/NSIS/left_side.bmp
# ... and except build_packages/DEB/template # ... and except build_packages/DEB/template
!build_packages/DEB !build_packages/DEB
build_packages/DEB/* build_packages/DEB/*
!build_packages/DEB/template !build_packages/DEB/template
*.log

View File

@ -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) - GNU/Linux (x64, arm32 or arm64), Windows XP or later (x64 or x86) or MacOS (x64)
- Java 8 or later - Java 8 or later
- OpenGL 2.1 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 - Less than 1 GiB of storage space
See [Build Guide](docs/building/BuildGuide.md) for compilation requirements. See [Build Guide](docs/building/BuildGuide.md) for compilation requirements.

View File

@ -111,6 +111,8 @@ switch (OperatingSystem.current()) {
} }
dependencies { dependencies {
implementation 'org.jetbrains:annotations:20.1.0'
implementation platform("org.lwjgl:lwjgl-bom:$lwjglVersion") implementation platform("org.lwjgl:lwjgl-bom:$lwjglVersion")
implementation "org.lwjgl:lwjgl" implementation "org.lwjgl:lwjgl"

View File

@ -128,6 +128,7 @@ buildWindowsInstaller() {
{ {
cp -r 'build/libs/lib' 'build_packages/NSIS/lib' && cp -r 'build/libs/lib' 'build_packages/NSIS/lib' &&
cp 'build/libs/Progressia.jar' 'build_packages/NSIS/Progressia.jar' && cp 'build/libs/Progressia.jar' 'build_packages/NSIS/Progressia.jar' &&
cp 'LICENSE' 'build_packages/NSIS/LICENSE.txt' &&
echo "------ NSIS ------" && echo "------ NSIS ------" &&
makensis "$configurationFile" && makensis "$configurationFile" &&
echo "---- NSIS END ----" && echo "---- NSIS END ----" &&
@ -144,6 +145,9 @@ buildWindowsInstaller() {
if [ -e 'build_packages/NSIS/Progressia.jar' ]; then if [ -e 'build_packages/NSIS/Progressia.jar' ]; then
rm 'build_packages/NSIS/Progressia.jar' rm 'build_packages/NSIS/Progressia.jar'
fi fi
if [ -e 'build_packages/NSIS/LICENSE.txt' ]; then
rm 'build_packages/NSIS/LICENSE.txt'
fi
echo "Cleaned up" echo "Cleaned up"
} || { } || {
echoerr "Could not clean up after building Windows installer" echoerr "Could not clean up after building Windows installer"

View File

@ -10,16 +10,32 @@
;-------------------------------- ;--------------------------------
;General ;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 and file
Name "Progressia" Name "${PROJECT_NAME}"
OutFile "ProgressiaInstaller.exe" OutFile "${PROJECT_NAME}Installer.exe"
Unicode True Unicode True
;Default installation folder ;Default installation folder
InstallDir "$PROGRAMFILES\Progressia" InstallDir "$PROGRAMFILES\${PROJECT_NAME}"
;Get installation folder from registry if available ;Get installation folder from registry if available
InstallDirRegKey HKLM "Software\Progressia" "Install_Dir" InstallDirRegKey HKLM "Software\${PROJECT_NAME}" ""
;Request application privileges for Windows Vista ;Request application privileges for Windows Vista
RequestExecutionLevel admin RequestExecutionLevel admin
@ -33,14 +49,18 @@
;Pages ;Pages
!insertmacro MUI_PAGE_WELCOME !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_COMPONENTS
!insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES !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_PAGE_FINISH
!insertmacro MUI_UNPAGE_WELCOME !insertmacro MUI_UNPAGE_WELCOME
!insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_COMPONENTS
!insertmacro MUI_UNPAGE_INSTFILES !insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_UNPAGE_FINISH !insertmacro MUI_UNPAGE_FINISH
@ -52,12 +72,15 @@
;-------------------------------- ;--------------------------------
;Installer Sections ;Installer Sections
Section "Install Progressia" SecDummy Section "Install ${PROJECT_NAME}" SEC0000
SectionIn RO ;Make it read-only
SetOutPath "$INSTDIR" SetOutPath "$INSTDIR"
SetOverwrite on
;Files ;Files
File Progressia.jar File Progressia.jar
File logo.ico
File /r lib File /r lib
;Store installation folder ;Store installation folder
@ -65,22 +88,24 @@ Section "Install Progressia" SecDummy
;Create uninstaller ;Create uninstaller
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Progressia" "DisplayName" "Progressia (remove only)" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PROJECT_NAME}" "DisplayName" "${PROJECT_NAME} (remove only)"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Progressia" "UninstallString" "$INSTDIR\Uninstall.exe" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PROJECT_NAME}" "UninstallString" "$INSTDIR\Uninstall.exe"
WriteUninstaller "$INSTDIR\Uninstall.exe" WriteUninstaller "$INSTDIR\Uninstall.exe"
SectionEnd SectionEnd
;-------------------------------- Section "Create Desktop Shortcut" SEC0001
;Descriptions SetOutPath "$APPDATA\${PROJECT_NAME}"
CreateShortCut "$DESKTOP\${PROJECT_NAME}.lnk" "$INSTDIR\${PROJECT_NAME}.jar" "" "$INSTDIR\logo.ico"
SectionEnd
;Language strings Section "Start Menu Shortcuts" SEC0002
LangString DESC_SecDummy ${LANG_ENGLISH} "A test section."
;Assign language strings to sections CreateDirectory "$SMPROGRAMS\${PROJECT_NAME}"
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN CreateShortcut "$SMPROGRAMS\${PROJECT_NAME}\Uninstall.lnk" "$INSTDIR\Uninstall.exe"
!insertmacro MUI_DESCRIPTION_TEXT ${SecDummy} $(DESC_SecDummy) CreateShortcut "$SMPROGRAMS\${PROJECT_NAME}\${PROJECT_NAME}.lnk" "$INSTDIR\${PROJECT_NAME}.jar" "" "$INSTDIR\logo.ico"
!insertmacro MUI_FUNCTION_DESCRIPTION_END
SectionEnd
;-------------------------------- ;--------------------------------
;Uninstaller Section ;Uninstaller Section
@ -92,9 +117,45 @@ Section "Uninstall"
Delete $INSTDIR\Uninstall.exe Delete $INSTDIR\Uninstall.exe
Delete $INSTDIR\Progressia.jar Delete $INSTDIR\Progressia.jar
Delete $INSTDIR\lib\*.* Delete $INSTDIR\lib\*.*
Delete $INSTDIR\logo.ico
RMDir /r "$INSTDIR" RMDir $INSTDIR\lib
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Progressia"
DeleteRegKey HKLM "Software\Progressia" 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 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

69
docs/CONTRIBUTING.md Normal file
View 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.

View File

@ -40,6 +40,7 @@ public class ProgressiaLauncher {
CrashReports.registerProvider(new OpenALContextProvider()); CrashReports.registerProvider(new OpenALContextProvider());
CrashReports.registerProvider(new ArgsContextProvider()); CrashReports.registerProvider(new ArgsContextProvider());
CrashReports.registerProvider(new LanguageContextProvider()); CrashReports.registerProvider(new LanguageContextProvider());
CrashReports.registerProvider(new ScreenContextProvider());
// Analyzers // Analyzers
CrashReports.registerAnalyzer(new OutOfMemoryAnalyzer()); CrashReports.registerAnalyzer(new OutOfMemoryAnalyzer());

View File

@ -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.texture.Atlases;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram; import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.localization.Localizer; 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.resource.ResourceManager;
import ru.windcorp.progressia.common.util.crash.CrashReports; import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.server.ServerState; import ru.windcorp.progressia.server.ServerState;
import ru.windcorp.progressia.test.TestContent; import ru.windcorp.progressia.test.TestContent;
import ru.windcorp.progressia.test.TestMusicPlayer;
public class ClientProxy implements Proxy { public class ClientProxy implements Proxy {
@ -56,9 +58,13 @@ public class ClientProxy implements Proxy {
Atlases.loadAllAtlases(); Atlases.loadAllAtlases();
AudioSystem.initialize(); AudioSystem.initialize();
TaskManager.getInstance().startLoading();
ServerState.startServer(); ServerState.startServer();
ClientState.connectToLocalServer(); ClientState.connectToLocalServer();
TestMusicPlayer.start();
} }
} }

View File

@ -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.Listener;
import ru.windcorp.progressia.client.audio.backend.SoundType; import ru.windcorp.progressia.client.audio.backend.SoundType;
import ru.windcorp.progressia.client.audio.backend.Speaker; 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.AL11.*;
import static org.lwjgl.openal.ALC10.*; import static org.lwjgl.openal.ALC10.*;
@ -40,7 +41,6 @@ public class AudioManager {
private static List<Speaker> soundSpeakers = new ArrayList<>(SOUNDS_NUM); private static List<Speaker> soundSpeakers = new ArrayList<>(SOUNDS_NUM);
private static Speaker musicSpeaker; private static Speaker musicSpeaker;
private static ArrayList<SoundType> soundsBuffer = new ArrayList<>();
public static void initAL() { public static void initAL() {
String defaultDeviceName = alcGetString( String defaultDeviceName = alcGetString(
@ -82,31 +82,19 @@ public class AudioManager {
return speaker; return speaker;
} }
private static SoundType findSoundType(String soundID) throws Exception { public static Speaker initSpeaker(SoundType st) {
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) {
Speaker speaker = getLastSpeaker(); Speaker speaker = getLastSpeaker();
try { try {
findSoundType(soundID).initSpeaker(speaker); st.initSpeaker(speaker);
} catch (Exception ex) { } catch (Exception ex) {
throw new RuntimeException(); throw new RuntimeException();
} }
return speaker; return speaker;
} }
public static Speaker initMusicSpeaker(String soundID) { public static Speaker initMusicSpeaker(SoundType st) {
try { try {
findSoundType(soundID).initSpeaker(musicSpeaker); st.initSpeaker(musicSpeaker);
} catch (Exception ex) { } catch (Exception ex) {
throw new RuntimeException(); 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) { if (format == AudioFormat.MONO) {
soundsBuffer.add(AudioReader.readAsMono(path, id)); AudioRegistry.getInstance().register(AudioReader.readAsMono(resource, id));
} else { } else {
soundsBuffer.add(AudioReader.readAsStereo(path, id)); AudioRegistry.getInstance().register(AudioReader.readAsStereo(resource, id));
} }
} }

View File

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

View File

@ -18,17 +18,33 @@
package ru.windcorp.progressia.client.audio; 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 { public class AudioSystem {
static public void initialize() { static public void initialize() {
Module audioModule = new Module("AudioModule:System");
AudioManager.initAL(); AudioManager.initAL();
Thread shutdownHook = new Thread(AudioManager::closeAL, "AL Shutdown Hook"); Thread shutdownHook = new Thread(AudioManager::closeAL, "AL Shutdown Hook");
Runtime.getRuntime().addShutdownHook(shutdownHook); Runtime.getRuntime().addShutdownHook(shutdownHook);
Task t = new Task("AudioSystem:Initialize") {
@Override
protected void perform() {
loadAudioData(); loadAudioData();
LogManager.getLogger().info("Audio data is loaded");
}
};
audioModule.addTask(t);
TaskManager.getInstance().registerModule(audioModule);
} }
static void loadAudioData() { static void loadAudioData() {
AudioManager.loadSound( AudioManager.loadSound(
"assets/sounds/block_destroy_clap.ogg", ResourceManager.getResource("assets/sounds/block_destroy_clap.ogg"),
"Progressia:BlockDestroy", "Progressia:BlockDestroy",
AudioFormat.MONO AudioFormat.MONO
); );

View File

@ -19,72 +19,37 @@
package ru.windcorp.progressia.client.audio; package ru.windcorp.progressia.client.audio;
import glm.vec._3.Vec3; 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.client.audio.backend.Speaker;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
public class Music extends Namespaced { public class Music
private Vec3 position = new Vec3(); extends Sound {
private Vec3 velocity = new Vec3();
private float pitch = 1.0f;
private float gain = 1.0f;
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) { public Music(String id) {
super(id); super(id);
} }
public Music( @Override
String id, protected Speaker initSpeaker() {
Vec3 position, return AudioManager.initMusicSpeaker(soundType);
Vec3 velocity,
float pitch,
float gain
) {
this(id);
this.position = position;
this.velocity = velocity;
this.pitch = pitch;
this.gain = gain;
} }
public void play(boolean loop) { @Override
Speaker speaker = AudioManager.initMusicSpeaker(this.getId()); public void setPosition(Vec3 position) {
speaker.setGain(gain); throw new UnsupportedOperationException();
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;
} }
} }

View File

@ -19,23 +19,30 @@
package ru.windcorp.progressia.client.audio; package ru.windcorp.progressia.client.audio;
import glm.vec._3.Vec3; 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.client.audio.backend.Speaker;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
public class SoundEffect public class Sound {
extends Namespaced {
private Vec3 position = new Vec3(); protected Vec3 position = new Vec3(0f, 0f, 0f);
private Vec3 velocity = new Vec3(); protected Vec3 velocity = new Vec3(0f, 0f, 0f);
private float pitch = 1.0f; protected float pitch = 1.0f;
private float gain = 1.0f; protected float gain = 1.0f;
protected int timeLength = 0;
public SoundEffect(String id) { protected SoundType soundType;
super(id);
public Sound(SoundType soundType) {
this.soundType = soundType;
} }
public SoundEffect( public Sound(String id) {
this(AudioRegistry.getInstance().get(id));
}
public Sound(
String id, String id,
int timeLength,
Vec3 position, Vec3 position,
Vec3 velocity, Vec3 velocity,
float pitch, float pitch,
@ -48,8 +55,27 @@ public class SoundEffect
this.gain = gain; 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) { public void play(boolean loop) {
Speaker speaker = AudioManager.initSpeaker(this.getId()); Speaker speaker = initSpeaker();
speaker.setGain(gain); speaker.setGain(gain);
speaker.setPitch(pitch); speaker.setPitch(pitch);
speaker.setPosition(position); speaker.setPosition(position);
@ -93,4 +119,9 @@ public class SoundEffect
public float getPitch() { public float getPitch() {
return pitch; return pitch;
} }
public double getDuration() {
return soundType.getDuration();
}
} }

View File

@ -33,13 +33,11 @@ public class AudioReader {
} }
// TODO fix converting from mono-stereo // 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 channelBuffer = BufferUtils.createIntBuffer(1);
IntBuffer rateBuffer = BufferUtils.createIntBuffer(1); IntBuffer rateBuffer = BufferUtils.createIntBuffer(1);
Resource res = ResourceManager.getResource(path); ShortBuffer rawAudio = decodeVorbis(resource, channelBuffer, rateBuffer);
ShortBuffer rawAudio = decodeVorbis(res, channelBuffer, rateBuffer);
return new SoundType( return new SoundType(
id, id,
@ -49,12 +47,12 @@ public class AudioReader {
); );
} }
public static SoundType readAsMono(String path, String id) { public static SoundType readAsMono(Resource resource, String id) {
return readAsSpecified(path, id, AL_FORMAT_MONO16); return readAsSpecified(resource, id, AL_FORMAT_MONO16);
} }
public static SoundType readAsStereo(String path, String id) { public static SoundType readAsStereo(Resource resource, String id) {
return readAsSpecified(path, id, AL_FORMAT_STEREO16); return readAsSpecified(resource, id, AL_FORMAT_STEREO16);
} }
private static ShortBuffer decodeVorbis( private static ShortBuffer decodeVorbis(

View File

@ -21,6 +21,9 @@ package ru.windcorp.progressia.client.audio.backend;
import ru.windcorp.progressia.common.util.namespaces.Namespaced; import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import java.nio.ShortBuffer; import java.nio.ShortBuffer;
import org.lwjgl.openal.AL10;
import static org.lwjgl.openal.AL11.*; import static org.lwjgl.openal.AL11.*;
public class SoundType extends Namespaced { public class SoundType extends Namespaced {
@ -29,6 +32,7 @@ public class SoundType extends Namespaced {
private int sampleRate; private int sampleRate;
private int format; private int format;
private int audioBuffer; private int audioBuffer;
private double duration;
public SoundType( public SoundType(
String id, String id,
@ -46,9 +50,14 @@ public class SoundType extends Namespaced {
private void createAudioBuffer() { private void createAudioBuffer() {
this.audioBuffer = alGenBuffers(); this.audioBuffer = alGenBuffers();
alBufferData(audioBuffer, format, rawAudio, sampleRate); alBufferData(audioBuffer, format, rawAudio, sampleRate);
duration = rawAudio.limit() / (double) sampleRate / (format == AL10.AL_FORMAT_STEREO16 ? 2 : 1);
} }
public void initSpeaker(Speaker speaker) { public void initSpeaker(Speaker speaker) {
speaker.setAudioData(audioBuffer); speaker.setAudioData(audioBuffer);
} }
public double getDuration() {
return duration;
}
} }

View File

@ -120,6 +120,7 @@ public class Speaker {
} }
public void setAudioData(int audioData) { public void setAudioData(int audioData) {
stop();
this.audioData = audioData; this.audioData = audioData;
alSourcei(this.sourceData, AL_BUFFER, audioData); alSourcei(this.sourceData, AL_BUFFER, audioData);
} }

View File

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

View File

@ -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( private static <I extends InputEvent> BiConsumer<InputEvent, ControlData> createCheckedDataWriter(
Class<I> inputType, Class<I> inputType,
BiConsumer<I, ControlData> dataWriter BiConsumer<I, ControlData> dataWriter
@ -150,6 +240,13 @@ public class ControlTriggers {
return (inputEvent, control) -> dataWriter.accept(inputType.cast(inputEvent), control); 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( private static <I extends InputEvent> Predicate<InputEvent> createCheckedCompoundPredicate(
Class<I> inputType, Class<I> inputType,
Predicate<I>[] predicates Predicate<I>[] predicates

View File

@ -18,11 +18,11 @@
package ru.windcorp.progressia.client.graphics.backend; package ru.windcorp.progressia.client.graphics.backend;
import static org.lwjgl.opengl.GL11.*;
import glm.vec._2.i.Vec2i; import glm.vec._2.i.Vec2i;
import org.lwjgl.glfw.GLFWVidMode;
import static org.lwjgl.glfw.GLFW.*; import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL11.*;
public class GraphicsBackend { public class GraphicsBackend {
@ -38,9 +38,30 @@ public class GraphicsBackend {
private static boolean faceCullingEnabled = false; 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() { 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() { public static void initialize() {
startRenderThread(); startRenderThread();
} }
@ -128,4 +149,47 @@ public class GraphicsBackend {
faceCullingEnabled = useFaceCulling; 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();
}
} }

View File

@ -73,4 +73,13 @@ public class GraphicsInterface {
GraphicsBackend.startNextLayer(); GraphicsBackend.startNextLayer();
} }
public static void makeFullscreen(boolean state) {
if (state) {
GraphicsBackend.setFullscreen();
} else {
GraphicsBackend.setWindowed();
}
GraphicsBackend.setVSyncEnabled(GraphicsBackend.isVSyncEnabled());
}
} }

View File

@ -50,6 +50,7 @@ class LWJGLInitializer {
private static void initializeGLFW() { private static void initializeGLFW() {
// TODO Do GLFW error handling: check glfwInit, setup error callback // TODO Do GLFW error handling: check glfwInit, setup error callback
glfwInit(); glfwInit();
GraphicsBackend.setGLFWInitialized(true);
} }
private static void createWindow() { private static void createWindow() {
@ -67,7 +68,7 @@ class LWJGLInitializer {
glfwSetInputMode(handle, GLFW_CURSOR, GLFW_CURSOR_DISABLED); glfwSetInputMode(handle, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
glfwMakeContextCurrent(handle); glfwMakeContextCurrent(handle);
glfwSwapInterval(0); glfwSwapInterval(0); // TODO: remove after config system is added
} }
private static void positionWindow() { private static void positionWindow() {
@ -87,6 +88,7 @@ class LWJGLInitializer {
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
RenderTaskQueue.schedule(OpenGLObjectTracker::deleteEnqueuedObjects); RenderTaskQueue.schedule(OpenGLObjectTracker::deleteEnqueuedObjects);
GraphicsBackend.setOpenGLInitialized(true);
} }
private static void setupWindowCallbacks() { private static void setupWindowCallbacks() {

View File

@ -38,10 +38,6 @@ public class StaticModel extends Model {
this.transforms = transforms; this.transforms = transforms;
} }
public StaticModel(Builder builder) {
this(builder.getParts(), builder.getTransforms());
}
@Override @Override
protected Mat4 getTransform(int partIndex) { protected Mat4 getTransform(int partIndex) {
return transforms[partIndex]; return transforms[partIndex];
@ -83,6 +79,10 @@ public class StaticModel extends Model {
return transforms.toArray(new Mat4[transforms.size()]); return transforms.toArray(new Mat4[transforms.size()]);
} }
public StaticModel build() {
return new StaticModel(getParts(), getTransforms());
}
} }
} }

View File

@ -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"); private static final float FRICTION_COEFF = Units.get("1e-5f kg/s");

View File

@ -18,35 +18,20 @@
package ru.windcorp.progressia.client.world; package ru.windcorp.progressia.client.world;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.WeakHashMap; 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 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.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.BlockRender;
import ru.windcorp.progressia.client.world.block.BlockRenderNone;
import ru.windcorp.progressia.client.world.block.BlockRenderRegistry; 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.TileRender;
import ru.windcorp.progressia.client.world.tile.TileRenderRegistry; import ru.windcorp.progressia.client.world.tile.TileRenderRegistry;
import ru.windcorp.progressia.client.world.tile.TileRenderStack; import ru.windcorp.progressia.client.world.tile.TileRenderStack;
import ru.windcorp.progressia.common.world.ChunkData; import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.block.BlockFace; import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.generic.GenericChunk; import ru.windcorp.progressia.common.world.generic.GenericChunk;
import ru.windcorp.progressia.common.world.tile.TileData;
import ru.windcorp.progressia.common.world.tile.TileDataStack; import ru.windcorp.progressia.common.world.tile.TileDataStack;
public class ChunkRender public class ChunkRender
@ -55,7 +40,7 @@ public class ChunkRender
private final WorldRender world; private final WorldRender world;
private final ChunkData data; private final ChunkData data;
private Model model = null; private final ChunkRenderModel model;
private final Map<TileDataStack, TileRenderStackImpl> tileRenderLists = Collections private final Map<TileDataStack, TileRenderStackImpl> tileRenderLists = Collections
.synchronizedMap(new WeakHashMap<>()); .synchronizedMap(new WeakHashMap<>());
@ -63,6 +48,7 @@ public class ChunkRender
public ChunkRender(WorldRender world, ChunkData data) { public ChunkRender(WorldRender world, ChunkData data) {
this.world = world; this.world = world;
this.data = data; this.data = data;
this.model = new ChunkRenderModel(this);
} }
@Override @Override
@ -107,165 +93,11 @@ public class ChunkRender
} }
public synchronized void render(ShapeRenderHelper renderer) { 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); model.render(renderer);
renderer.popTransform();
} }
public synchronized void update() { public synchronized void update() {
Collection<ChunkRenderOptimizer> optimizers = ChunkRenderOptimizers.getAllSuppliers().stream() model.update();
.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)
);
} }
private class TileRenderStackImpl extends TileRenderStack { private class TileRenderStackImpl extends TileRenderStack {

View File

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

View File

@ -18,8 +18,14 @@
package ru.windcorp.progressia.client.world; 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.ChunkData;
import ru.windcorp.progressia.common.world.ChunkDataListener; 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 { class ChunkUpdateListener implements ChunkDataListener {
@ -34,4 +40,60 @@ class ChunkUpdateListener implements ChunkDataListener {
world.getChunk(chunk).markForUpdate(); 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);
}
} }

View File

@ -20,7 +20,9 @@ package ru.windcorp.progressia.client.world.block;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper; import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.common.util.namespaces.Namespaced; import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.generic.GenericBlock; import ru.windcorp.progressia.common.world.generic.GenericBlock;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.model.Renderable; import ru.windcorp.progressia.client.graphics.model.Renderable;
public abstract class BlockRender extends Namespaced implements GenericBlock { 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; return null;
} }

View File

@ -18,8 +18,10 @@
package ru.windcorp.progressia.client.world.block; 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.EmptyModel;
import ru.windcorp.progressia.client.graphics.model.Renderable; import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.common.world.ChunkData;
public class BlockRenderNone extends BlockRender { public class BlockRenderNone extends BlockRender {
@ -28,7 +30,7 @@ public class BlockRenderNone extends BlockRender {
} }
@Override @Override
public Renderable createRenderable() { public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk) {
return EmptyModel.getInstance(); return EmptyModel.getInstance();
} }

View File

@ -65,9 +65,4 @@ public class BlockRenderOpaqueCube extends BlockRenderTexturedCube {
return true; return true;
} }
@Override
public boolean needsOwnRenderable() {
return false;
}
} }

View File

@ -22,17 +22,27 @@ import static ru.windcorp.progressia.common.world.block.BlockFace.*;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; 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.Renderable;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.texture.Texture; import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram; 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; import ru.windcorp.progressia.common.world.block.BlockFace;
public abstract class BlockRenderTexturedCube public abstract class BlockRenderTexturedCube
extends BlockRender extends BlockRender
implements OpaqueCube { implements BlockOptimizedSurface {
private final Map<BlockFace, Texture> textures = new HashMap<>(); private final Map<BlockFace, Texture> textures = new HashMap<>();
@ -55,22 +65,61 @@ public abstract class BlockRenderTexturedCube
textures.put(WEST, westTexture); textures.put(WEST, westTexture);
} }
@Override public Texture getTexture(BlockFace blockFace) {
public Texture getTexture(BlockFace face) { return textures.get(blockFace);
return textures.get(face); }
public Vec4 getColorMultiplier(BlockFace blockFace) {
return Colors.WHITE;
} }
@Override @Override
public Renderable createRenderable() { public final void getFaces(
return new Shapes.PppBuilder( 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(), WorldRenderProgram.getDefault(),
getTexture(TOP), getTexture(blockFace),
getTexture(BOTTOM), getColorMultiplier(blockFace),
getTexture(NORTH), offset,
getTexture(SOUTH), blockFace,
getTexture(EAST), inner
getTexture(WEST) );
).create(); }
@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;
} }
} }

View File

@ -65,9 +65,4 @@ public class BlockRenderTransparentCube extends BlockRenderTexturedCube {
return false; return false;
} }
@Override
public boolean needsOwnRenderable() {
return false;
}
} }

View File

@ -19,27 +19,111 @@
package ru.windcorp.progressia.client.world.cro; package ru.windcorp.progressia.client.world.cro;
import glm.vec._3.i.Vec3i; 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.ChunkRender;
import ru.windcorp.progressia.client.world.block.BlockRender; import ru.windcorp.progressia.client.world.block.BlockRender;
import ru.windcorp.progressia.client.world.tile.TileRender; import ru.windcorp.progressia.client.world.tile.TileRender;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import ru.windcorp.progressia.common.world.block.BlockFace; import ru.windcorp.progressia.common.world.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, * Creates a new CRO instance with the specified ID.
Vec3i posInChunk *
); * @param id the ID of this CRO
*/
public ChunkRenderOptimizer(String id) {
super(id);
}
public abstract void processTile( /**
TileRender tile, * This method is invoked before a new chunk processing cycle begins to
Vec3i posInChunk, * specify the chunk. When overriding, {@code super.setup(chunk)} must be
BlockFace face * 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();
} }

View File

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

View File

@ -18,28 +18,17 @@
package ru.windcorp.progressia.client.world.cro; 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); * @return the instance
} */
public static ChunkRenderOptimizerRegistry getInstance() {
public abstract ChunkRenderOptimizer createOptimizer(); return INSTANCE;
public static ChunkRenderOptimizerSupplier of(
String id,
Supplier<ChunkRenderOptimizer> supplier
) {
return new ChunkRenderOptimizerSupplier(id) {
@Override
public ChunkRenderOptimizer createOptimizer() {
return supplier.get();
}
};
} }
} }

View File

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

View File

@ -19,9 +19,11 @@
package ru.windcorp.progressia.client.world.tile; package ru.windcorp.progressia.client.world.tile;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper; 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.graphics.model.Renderable;
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizer; import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizer;
import ru.windcorp.progressia.common.util.namespaces.Namespaced; 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.block.BlockFace;
import ru.windcorp.progressia.common.world.generic.GenericTile; 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; return null;
} }

View File

@ -18,19 +18,10 @@
package ru.windcorp.progressia.client.world.tile; 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.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; 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 topTexture;
private final Texture sideTexture; private final Texture sideTexture;
@ -55,27 +46,4 @@ public class TileRenderGrass extends TileRender implements OpaqueSurface {
return face == BlockFace.TOP; 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;
}
} }

View File

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

View File

@ -18,19 +18,25 @@
package ru.windcorp.progressia.client.world.tile; package ru.windcorp.progressia.client.world.tile;
import java.util.function.Consumer;
import glm.vec._3.Vec3; 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.Colors;
import ru.windcorp.progressia.client.graphics.backend.Usage; 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.Faces;
import ru.windcorp.progressia.client.graphics.model.Shape; 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.model.Renderable;
import ru.windcorp.progressia.client.graphics.texture.Texture; import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram; 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; 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; private final Texture texture;
@ -39,26 +45,51 @@ public abstract class TileRenderSurface extends TileRender implements OpaqueSurf
this.texture = texture; this.texture = texture;
} }
@Override public TileRenderSurface(String id) {
public Texture getTexture(BlockFace face) { this(id, null);
}
public Texture getTexture(BlockFace blockFace) {
return texture; return texture;
} }
@Override public Vec4 getColorMultiplier(BlockFace blockFace) {
public Renderable createRenderable(BlockFace face) { return Colors.WHITE;
ShapeRenderProgram program = WorldRenderProgram.getDefault(); }
@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( return new Shape(
Usage.STATIC, Usage.STATIC,
WorldRenderProgram.getDefault(), WorldRenderProgram.getDefault(),
Faces.createBlockFace(
program, createFace(chunk, blockInChunk, blockFace, false, Vectors.ZERO_3),
getTexture(face), createFace(chunk, blockInChunk, blockFace, true, Vectors.ZERO_3)
Colors.WHITE,
new Vec3(0, 0, 0),
face,
false
)
); );
} }

View File

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

View File

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

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

View File

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

View File

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

View File

@ -0,0 +1,36 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.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;
}
}
}

View File

@ -30,19 +30,24 @@ import org.lwjgl.BufferUtils;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
import com.google.common.io.CharStreams; import com.google.common.io.CharStreams;
import ru.windcorp.progressia.Progressia;
import ru.windcorp.progressia.common.util.Named; import ru.windcorp.progressia.common.util.Named;
import ru.windcorp.progressia.common.util.crash.CrashReports; import ru.windcorp.progressia.common.util.crash.CrashReports;
public class Resource extends Named { public class Resource extends Named {
public Resource(String name) { private final ResourceReader resourceReader;
public Resource(String name, ResourceReader resourceReader) {
super(name); super(name);
this.resourceReader = resourceReader;
} }
public InputStream getInputStream() { public InputStream getInputStream() {
// TODO Do proper resource lookup return getResourceReader().read(getName());
return Progressia.class.getClassLoader().getResourceAsStream(getName()); }
public ResourceReader getResourceReader() {
return resourceReader;
} }
public Reader getReader() { public Reader getReader() {

View File

@ -20,8 +20,15 @@ package ru.windcorp.progressia.common.resource;
public class ResourceManager { 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) { 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) { public static Resource getTextureResource(String name) {

View File

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

View File

@ -203,13 +203,12 @@ public class CrashReports {
if (provider == null) if (provider == null)
continue; continue;
addSeparator(output);
try { try {
Map<String, String> buf = new HashMap<>(); Map<String, String> buf = new HashMap<>();
provider.provideContext(buf); provider.provideContext(buf);
if (!buf.isEmpty()) { if (!buf.isEmpty()) {
addSeparator(output);
output.append(StringUtil.center(provider.getName(), 80)).append("\n"); output.append(StringUtil.center(provider.getName(), 80)).append("\n");
for (Map.Entry<String, String> entry : buf.entrySet()) { for (Map.Entry<String, String> entry : buf.entrySet()) {
output.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); output.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n");

View File

@ -26,12 +26,12 @@ public class RAMContextProvider implements ContextProvider {
@Override @Override
public void provideContext(Map<String, String> output) { public void provideContext(Map<String, String> output) {
output.put("Max Memory", Long.toString(Runtime.getRuntime().maxMemory() / 1024 / 1024) + " MB"); output.put("Max Memory", Runtime.getRuntime().maxMemory() / 1024 / 1024 + " MB");
output.put("Total Memory", Long.toString(Runtime.getRuntime().totalMemory() / 1024 / 1024) + " MB"); output.put("Total Memory", Runtime.getRuntime().totalMemory() / 1024 / 1024 + " MB");
output.put("Free Memory", Long.toString(Runtime.getRuntime().freeMemory() / 1024 / 1024) + " MB"); output.put("Free Memory", Runtime.getRuntime().freeMemory() / 1024 / 1024 + " MB");
output.put( output.put(
"Used Memory", "Used Memory",
Long.toString((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1024 / 1024) (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1024 / 1024
+ " MB" + " MB"
); );
} }

View File

@ -16,38 +16,26 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package ru.windcorp.progressia.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; 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<>(); @Override
public String getName() {
static { return "Screen Context Provider";
register(
ChunkRenderOptimizerSupplier.of(
"Default:OpaqueCube",
ChunkRenderOptimizerCube::new
)
);
} }
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();
}
} }

View File

@ -159,4 +159,8 @@ public class Coordinates {
return output; return output;
} }
public static boolean isOnChunkBorder(int blockInChunk) {
return blockInChunk == 0 || blockInChunk == BLOCKS_PER_CHUNK - 1;
}
} }

View File

@ -27,10 +27,12 @@ import glm.vec._3.i.Vec3i;
public class BlockRelation { public class BlockRelation {
private final Vec3i vector = new Vec3i(); private final Vec3i vector = new Vec3i();
private final Vec3 floatVector = new Vec3();
private final Vec3 normalized = new Vec3(); private final Vec3 normalized = new Vec3();
public BlockRelation(int x, int y, int z) { public BlockRelation(int x, int y, int z) {
vector.set(x, y, z); vector.set(x, y, z);
floatVector.set(x, y, z);
normalized.set(x, y, z).normalize(); normalized.set(x, y, z).normalize();
} }
@ -42,6 +44,10 @@ public class BlockRelation {
return vector; return vector;
} }
public Vec3 getFloatVector() {
return floatVector;
}
public Vec3 getNormalized() { public Vec3 getNormalized() {
return normalized; return normalized;
} }

View File

@ -92,6 +92,72 @@ public interface GenericChunk<Self extends GenericChunk<Self, B, T, TS>, B exten
return result; 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) { default void forEachBiC(Consumer<? super Vec3i> action) {
VectorUtil.iterateCuboid( VectorUtil.iterateCuboid(
0, 0,

View File

@ -183,6 +183,18 @@ public class Server {
return this.serverThread.getTicker().getTPS(); 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 * Returns the {@link WorldAccessor} object for this server. Use the
* provided accessor to * provided accessor to

View File

@ -82,6 +82,7 @@ public class TickerCoordinator {
private boolean isTickStartSet = false; private boolean isTickStartSet = false;
private long tickStart = -1; private long tickStart = -1;
private double tickLength = 1.0 / 20; // Do something about it private double tickLength = 1.0 / 20; // Do something about it
private long ticks = 0;
private final Logger logger = LogManager.getLogger("Ticker Coordinator"); private final Logger logger = LogManager.getLogger("Ticker Coordinator");
@ -152,6 +153,10 @@ public class TickerCoordinator {
return 1 / tickLength; return 1 / tickLength;
} }
public long getUptimeTicks() {
return ticks;
}
private void onTickStart() { private void onTickStart() {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
@ -164,6 +169,10 @@ public class TickerCoordinator {
tickStart = System.currentTimeMillis(); tickStart = System.currentTimeMillis();
} }
private void onTickEnd() {
ticks++;
}
/* /*
* runOneTick & Friends * runOneTick & Friends
*/ */
@ -183,6 +192,8 @@ public class TickerCoordinator {
passes++; passes++;
} }
onTickEnd();
logger.debug("Tick complete; run {} passes", passes); logger.debug("Tick complete; run {} passes", passes);
} catch (InterruptedException e) { } catch (InterruptedException e) {
@ -191,7 +202,7 @@ public class TickerCoordinator {
// ...or almost silently // ...or almost silently
logger.debug("Tick interrupted. WTF?"); logger.debug("Tick interrupted. WTF?");
} catch (Exception e) { } catch (Exception e) {
crash(e, "Coordinator"); throw CrashReports.report(e, "Coordinator");
} }
} }

View File

@ -50,7 +50,7 @@ public class LayerAbout extends GUILayer {
new Label( new Label(
"Version", "Version",
font, font,
new MutableStringLocalized("LayerAbout.Version").format("TechDemo") new MutableStringLocalized("LayerAbout.Version").format("pre-alpha 1")
) )
); );

View File

@ -18,16 +18,12 @@
package ru.windcorp.progressia.test; 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._3.Vec3;
import glm.vec._4.Vec4; import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.Client; import ru.windcorp.progressia.client.Client;
import ru.windcorp.progressia.client.ClientState; import ru.windcorp.progressia.client.ClientState;
import ru.windcorp.progressia.client.graphics.Colors; 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.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.font.Font; import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.gui.DynamicLabel; 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.Server;
import ru.windcorp.progressia.server.ServerState; 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 class LayerTestGUI extends GUILayer {
public LayerTestGUI() { 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( panel.addChild(
new DynamicLabel( new DynamicLabel(
"FPSDisplay", "FPSDisplay",

View File

@ -33,12 +33,14 @@ import org.lwjgl.glfw.GLFW;
import glm.vec._3.Vec3; import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i; import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.ClientState; 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.comms.controls.*;
import ru.windcorp.progressia.client.graphics.input.KeyEvent; import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.graphics.input.KeyMatcher; import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
import ru.windcorp.progressia.client.graphics.world.Selection; import ru.windcorp.progressia.client.graphics.world.Selection;
import ru.windcorp.progressia.client.world.block.*; 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.entity.*;
import ru.windcorp.progressia.client.world.tile.*; import ru.windcorp.progressia.client.world.tile.*;
import ru.windcorp.progressia.common.collision.AABB; import ru.windcorp.progressia.common.collision.AABB;
@ -285,6 +287,15 @@ public class TestContent {
) )
); );
logic.register(ControlLogic.of("Test:PlaceTile", TestContent::onTilePlaceReceived)); 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) { private static void register(BlockData x) {
@ -358,7 +369,7 @@ public class TestContent {
private static void onBlockBreakTrigger(ControlData control) { private static void onBlockBreakTrigger(ControlData control) {
((ControlBreakBlockData) control).setBlockInWorld(getSelection().getBlock()); ((ControlBreakBlockData) control).setBlockInWorld(getSelection().getBlock());
SoundEffect sfx = new SoundEffect("Progressia:BlockDestroy"); Sound sfx = new Sound("Progressia:BlockDestroy");
sfx.setPosition(getSelection().getPoint()); sfx.setPosition(getSelection().getPoint());
sfx.setPitch((float) (Math.random() + 1 * 0.5)); sfx.setPitch((float) (Math.random() + 1 * 0.5));
sfx.play(false); sfx.play(false);
@ -420,6 +431,7 @@ public class TestContent {
private static void registerMisc() { private static void registerMisc() {
ChunkIO.registerCodec(new TestChunkCodec()); ChunkIO.registerCodec(new TestChunkCodec());
ChunkRenderOptimizerRegistry.getInstance().register("Core:SurfaceOptimizer", ChunkRenderOptimizerSurface::new);
} }
} }

View File

@ -200,7 +200,7 @@ public class TestEntityRenderHuman extends EntityRender {
).setOrigin(ox, oy, oz).setSize(sx, sy, sz).create() ).setOrigin(ox, oy, oz).setSize(sx, sy, sz).create()
); );
return new StaticModel(b); return b.build();
} }
@Override @Override

View File

@ -386,7 +386,7 @@ public class TestEntityRenderJavapony extends EntityRender {
).setOrigin(16, -4, 8).setSize(4, 8, 4).create() ).setOrigin(16, -4, 8).setSize(4, 8, 4).create()
); );
return new StaticModel(b); return b.build();
} }
private static Renderable createLeg( private static Renderable createLeg(
@ -427,7 +427,7 @@ public class TestEntityRenderJavapony extends EntityRender {
).setOrigin(-8, -8, -32).setSize(16, 16, 32).create() ).setOrigin(-8, -8, -32).setSize(16, 16, 32).create()
); );
return new StaticModel(b); return b.build();
} }
@Override @Override

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

View File

@ -187,6 +187,20 @@ public class TestPlayerControls {
handleEscape(); handleEscape();
break; 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: case GLFW.GLFW_KEY_F3:
if (!event.isPress()) if (!event.isPress())
return false; return false;

View File

@ -20,3 +20,5 @@ LayerTestGUI.PosDisplay.NA.Entity = Pos: entity n/a
LayerTestGUI.SelectedBlockDisplay = %s Block: %s LayerTestGUI.SelectedBlockDisplay = %s Block: %s
LayerTestGUI.SelectedTileDisplay = %s Tile: %s LayerTestGUI.SelectedTileDisplay = %s Tile: %s
LayerTestGUI.PlacementModeHint = (Blocks %s Tiles: Ctrl + Mouse Wheel) LayerTestGUI.PlacementModeHint = (Blocks %s Tiles: Ctrl + Mouse Wheel)
LayerTestGUI.IsFullscreen = Fullscreen: %5s (F11)
LayerTestGUI.IsVSync = VSync: %5s (F12)

View File

@ -20,3 +20,5 @@ LayerTestGUI.PosDisplay.NA.Entity = Поз: сущность н/д
LayerTestGUI.SelectedBlockDisplay = %s Блок: %s LayerTestGUI.SelectedBlockDisplay = %s Блок: %s
LayerTestGUI.SelectedTileDisplay = %s Плитка: %s LayerTestGUI.SelectedTileDisplay = %s Плитка: %s
LayerTestGUI.PlacementModeHint = (Блок %s плитки: Ctrl + прокрутка) LayerTestGUI.PlacementModeHint = (Блок %s плитки: Ctrl + прокрутка)
LayerTestGUI.IsFullscreen = Полный экран: %5s (F11)
LayerTestGUI.IsVSync = Верт. синхр.: %5s (F12)