29 Commits

Author SHA1 Message Date
a984333c3a Radio Buttons
Created RadioButton and RadioManager.
They work together well I guess.
😄
2021-04-18 19:44:50 -04:00
4a1f1b7545 It actually works now
It threw errors before
2021-04-18 15:09:29 -04:00
af7b39d8e9 Better Interactables
Created Interactable class
Introduced dependency of Button and Checkbox on Interactable
Created Checkbox class
2021-04-18 14:58:53 -04:00
a0acd16008 Now actually works
Only captures events that it needs
2021-04-17 11:03:23 -04:00
330ed1ab9b Buttony things
Centered button text
Label object
New dynamic text color
2021-04-17 11:01:03 -04:00
7e852ff05f Attempting to add focus
it doesnt work but still
2021-04-17 09:34:10 -04:00
6e1b0e3f69 Buttons work
Clicking and disabling now works
2021-04-17 09:12:09 -04:00
411780b120 Made onClick
It should work but idk for sure
2021-04-17 08:21:29 -04:00
5359b93738 Bettering the Button
Not really
TPS is 20 now
2021-04-17 08:07:01 -04:00
f9aae94bbc Return TPS to 20
yeah
2021-04-16 21:26:57 -04:00
5c57a57e9a Button things
Moved button to LayerTestUI
Saved all_buttons.png in resources
Idk it still wont render the button
2021-04-16 21:26:28 -04:00
468b6dc327 Create Button.java
Created Button class
Trying to give good functionality
2021-04-16 19:25:26 -04:00
69942ad17b Reset Default TPS
TPS of 47 to TPS of 20
2021-04-12 20:38:21 -04:00
3c74a808f3 Added Queue
+Added Queue class
*Replaced TPS Averager with a Queue
2021-04-12 20:35:29 -04:00
8327fcfd19 Counter Class
+Added Counter class
*Replaced current TPS meter with a Counter
2021-04-12 19:49:51 -04: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
67 changed files with 2369 additions and 775 deletions

2
.gitignore vendored
View File

@ -30,6 +30,8 @@ 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

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

@ -3,17 +3,17 @@
# #
# Progressia # Progressia
# Copyright (C) 2020-2021 Wind Corporation and contributors # Copyright (C) 2020-2021 Wind Corporation and contributors
# #
# This program is free software: you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or # the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version. # (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
# #
@ -24,24 +24,24 @@ buildDebianPackage() {
# Commands that must be available to execute this action # Commands that must be available to execute this action
requiredCommands='dpkg-deb fakeroot' requiredCommands='dpkg-deb fakeroot'
# Package name. Sync with control file manually! # Package name. Sync with control file manually!
name='progressia-techdemo' name='progressia-techdemo'
# Version that the package will receive. Sync with control file manually! # Version that the package will receive. Sync with control file manually!
version='1.0_all' version='1.0_all'
# This directory will be copied into $tmpDir # This directory will be copied into $tmpDir
templateDirectory="build_packages/DEB/template" templateDirectory="build_packages/DEB/template"
# Files that must be present # Files that must be present
requiredFiles="$templateDirectory/DEBIAN/control" requiredFiles="$templateDirectory/DEBIAN/control"
nameAndVersion="$name-$version" nameAndVersion="$name-$version"
tmpDir="build_packages/DEB/$nameAndVersion" tmpDir="build_packages/DEB/$nameAndVersion"
outputFile="build_packages/DEB/$nameAndVersion.deb" outputFile="build_packages/DEB/$nameAndVersion.deb"
echo "Checking environment to build Debian package" echo "Checking environment to build Debian package"
for item in $requiredCommands; do for item in $requiredCommands; do
if command -v "$item" &> /dev/null; then if command -v "$item" &> /dev/null; then
echo "- $item found" echo "- $item found"
@ -50,7 +50,7 @@ buildDebianPackage() {
exit 100 exit 100
fi fi
done done
for file in $requiredFiles; do for file in $requiredFiles; do
if ! [ -r "$file" ]; then if ! [ -r "$file" ]; then
echoerr "$file is missing or not readable, cannot package" echoerr "$file is missing or not readable, cannot package"
@ -59,13 +59,13 @@ buildDebianPackage() {
echo "- $file is present and readable" echo "- $file is present and readable"
fi fi
done done
echo "Environment OK; packaging Debian package" echo "Environment OK; packaging Debian package"
exitCode=0 exitCode=0
{ {
shareDir="$tmpDir/usr/share/progressia" shareDir="$tmpDir/usr/share/progressia"
mkdir -p "$tmpDir" && mkdir -p "$tmpDir" &&
mkdir -p "$shareDir" && mkdir -p "$shareDir" &&
cp -r "$templateDirectory"/* "$tmpDir" && cp -r "$templateDirectory"/* "$tmpDir" &&
@ -79,7 +79,7 @@ buildDebianPackage() {
echoerr "Could not create Debian package" echoerr "Could not create Debian package"
exitCode=1 exitCode=1
} }
{ {
if [ -d "$tmpDir" ]; then if [ -d "$tmpDir" ]; then
rm -r "$tmpDir" rm -r "$tmpDir"
@ -89,7 +89,7 @@ buildDebianPackage() {
echoerr "Could not clean up after packaging Debian package" echoerr "Could not clean up after packaging Debian package"
exitCode=2 exitCode=2
} }
exit "$exitCode" exit "$exitCode"
} }
@ -97,15 +97,15 @@ buildWindowsInstaller() {
# Commands that must be available to execute this action # Commands that must be available to execute this action
requiredCommands='makensis' requiredCommands='makensis'
# NSIS configuration file that must be present # NSIS configuration file that must be present
configurationFile='build_packages/NSIS/ProgressiaInstaller.nsi' configurationFile='build_packages/NSIS/ProgressiaInstaller.nsi'
# File that will be output # File that will be output
outputFile='build_packages/NSIS/ProgressiaInstaller.exe' outputFile='build_packages/NSIS/ProgressiaInstaller.exe'
echo "Checking environment to build Windows installer" echo "Checking environment to build Windows installer"
for item in $requiredCommands; do for item in $requiredCommands; do
if command -v "$item" &> /dev/null; then if command -v "$item" &> /dev/null; then
echo "- $item found" echo "- $item found"
@ -114,20 +114,21 @@ buildWindowsInstaller() {
exit 100 exit 100
fi fi
done done
if ! [ -r "$configurationFile" ]; then if ! [ -r "$configurationFile" ]; then
echoerr "$configurationFile is missing or not readable, cannot build" echoerr "$configurationFile is missing or not readable, cannot build"
exit 101 exit 101
else else
echo "- $configurationFile is present and readable" echo "- $configurationFile is present and readable"
fi fi
echo "Environment OK; building Windows installer" echo "Environment OK; building Windows installer"
exitCode=0 exitCode=0
{ {
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 ----" &&
@ -136,7 +137,7 @@ buildWindowsInstaller() {
echoerr "Could not build Windows installer" echoerr "Could not build Windows installer"
exitCode=1 exitCode=1
} }
{ {
if [ -d 'build_packages/NSIS/lib' ]; then if [ -d 'build_packages/NSIS/lib' ]; then
rm -r 'build_packages/NSIS/lib' rm -r 'build_packages/NSIS/lib'
@ -144,12 +145,15 @@ 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"
exitCode=2 exitCode=2
} }
exit "$exitCode" exit "$exitCode"
} }

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,35 +72,40 @@
;-------------------------------- ;--------------------------------
;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
WriteRegStr HKLM SOFTWARE\Progressia "Install_Dir" "$INSTDIR" WriteRegStr HKLM SOFTWARE\Progressia "Install_Dir" "$INSTDIR"
;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

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

@ -32,6 +32,7 @@ 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 {
@ -59,6 +60,8 @@ public class ClientProxy implements Proxy {
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,6 +18,8 @@
package ru.windcorp.progressia.client.audio; package ru.windcorp.progressia.client.audio;
import ru.windcorp.progressia.common.resource.ResourceManager;
public class AudioSystem { public class AudioSystem {
static public void initialize() { static public void initialize() {
AudioManager.initAL(); AudioManager.initAL();
@ -28,7 +30,7 @@ public class AudioSystem {
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) {
super(id); protected SoundType soundType;
public Sound(SoundType soundType) {
this.soundType = soundType;
} }
public SoundEffect( public Sound(String id) {
this(AudioRegistry.getInstance().get(id));
}
public Sound(
String id, String id,
int timeLength,
Vec3 position, Vec3 position,
Vec3 velocity, Vec3 velocity,
float pitch, float pitch,
@ -47,9 +54,28 @@ public class SoundEffect
this.pitch = pitch; this.pitch = pitch;
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

@ -142,6 +142,96 @@ public class ControlTriggers {
predicates predicates
); );
} }
//
//
///
///
//
//
//
//
//
//
//
//
//
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,
@ -149,6 +239,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,

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

@ -0,0 +1,83 @@
package ru.windcorp.progressia.client.graphics.gui;
import java.util.function.Consumer;
import java.util.function.Supplier;
import com.google.common.eventbus.Subscribe;
import glm.mat._4.Mat4;
import glm.vec._2.i.Vec2i;
import org.lwjgl.glfw.GLFW;
import ru.windcorp.progressia.client.graphics.backend.InputTracker;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.gui.event.FocusEvent;
import ru.windcorp.progressia.client.graphics.gui.event.HoverEvent;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
import ru.windcorp.progressia.client.graphics.input.bus.InputListener;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
public class Button extends Interactable {
public <T extends InputEvent> Button(String name, Label textLabel, Consumer<Button> onClick) {//, InputListener<T> onClick, Class<? extends T> onClickClass) {
super(name, textLabel);
setPreferredSize(107,34);
//Button inButton = (Button) setFocusable(true);
addListener((Class<KeyEvent>) KeyEvent.class, (InputListener<KeyEvent>) e -> {if ((e.isLeftMouseButton() && containsCursor()) || (e.getKey()==GLFW.GLFW_KEY_ENTER && isFocused()) )
{
isClicked = e.isPress();
if (!isDisabled())
{
onClick.accept(this);
takeFocus();
}
requestReassembly();
return true;
}
return false;});
}
@Override
protected void assembleSelf(RenderTarget target) {
//Border
if (isDisabled())
{
target.fill(getX(), getY(), getWidth(), getHeight(), 0xFFE5E5E5);
}
else if (isClicked() || isHovered() || isFocused())
{
target.fill(getX(), getY(), getWidth(), getHeight(), 0xFF37A2E6);
}
else
{
target.fill(getX(), getY(), getWidth(), getHeight(), 0xFFCBCBD0);
}
//Inside area
if (!isClicked() && isHovered() && !isDisabled())
{
target.fill(getX()+2, getY()+2, getWidth()-4, getHeight()-4, 0xFFC3E4F7);
}
else if (!isClicked() || isDisabled())
{
target.fill(getX()+2, getY()+2, getWidth()-4, getHeight()-4, Colors.WHITE);
}
Font tempFont = new Font().withColor(Colors.BLACK);
if (isDisabled())
{
tempFont = tempFont.withColor(Colors.GRAY_A);
}
else if (isClicked())
{
tempFont = tempFont.withColor(Colors.WHITE);
}
target.pushTransform(new Mat4().identity().translate( getX()+.5f*getWidth()-.5f*label.getPreferredSize().x, getY(), 0));
label = new Label(label.getName(), tempFont, label.getContentSupplier());
label.assembleSelf(target);
target.popTransform();
}
}

View File

@ -0,0 +1,117 @@
package ru.windcorp.progressia.client.graphics.gui;
import java.util.function.Consumer;
import java.util.function.Supplier;
import com.google.common.eventbus.Subscribe;
import glm.mat._4.Mat4;
import glm.vec._2.i.Vec2i;
import org.lwjgl.glfw.GLFW;
import ru.windcorp.progressia.client.graphics.backend.InputTracker;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.gui.event.FocusEvent;
import ru.windcorp.progressia.client.graphics.gui.event.HoverEvent;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
import ru.windcorp.progressia.client.graphics.input.bus.InputListener;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
public class Checkbox extends Interactable {
private boolean isActive;
public <T extends InputEvent> Checkbox(String name, Label textLabel, Consumer<Checkbox> onSet, Consumer<Checkbox> onReset) {//, InputListener<T> onClick, Class<? extends T> onClickClass) {
super(name, textLabel);
setPreferredSize(44 + textLabel.getPreferredSize().x,textLabel.getPreferredSize().y);
//Checkbox inCheck = (Checkbox) setFocusable(true);
addListener((Class<KeyEvent>) KeyEvent.class, (InputListener<KeyEvent>) e -> {if (e.isLeftMouseButton() && containsCursor()|| (e.getKey()==GLFW.GLFW_KEY_ENTER && isFocused()))
{
isClicked = e.isPress();
if (!isDisabled())
{
if (!isClicked && !isActive)
{
onSet.accept(this);
isActive = !isActive;
}
else if (!isClicked && isActive)
{
onReset.accept(this);
isActive = !isActive;
}
else if (isClicked)
{
takeFocus();
}
}
requestReassembly();
return true;
}
return false;});
}
public boolean isActive()
{
return isActive;
}
@Override
protected void assembleSelf(RenderTarget target) {
//Border
if (isDisabled())
{
target.fill(getX()+label.getPreferredSize().x, getY(), getWidth()-label.getPreferredSize().x, getHeight(), 0xFFE5E5E5);
}
else if (isClicked() || isHovered() || isFocused())
{
target.fill(getX()+label.getPreferredSize().x, getY(), getWidth()-label.getPreferredSize().x, getHeight(), 0xFF37A2E6); // blue
}
else
{
target.fill(getX()+label.getPreferredSize().x, getY(), getWidth()-label.getPreferredSize().x, getHeight(), 0xFFCBCBD0);
}
//Inside area
if (!isClicked() && isHovered() && !isDisabled())
{
target.fill(getX()+2+label.getPreferredSize().x, getY()+2, getWidth()-label.getPreferredSize().x-4, getHeight()-4, 0xFFC3E4F7); // light blue
}
else if (!isClicked() || isDisabled())
{
target.fill(getX()+2+label.getPreferredSize().x, getY()+2, getWidth()-label.getPreferredSize().x-4, getHeight()-4, Colors.WHITE);
}
if (isActive() && !isClicked())
{
if (isDisabled())
{
target.fill(getX()+getWidth()-getHeight()+4, getY()+4, getHeight()-8, getHeight()-8, 0xFFB3D7EF);
}
else
{
target.fill(getX()+getWidth()-getHeight()+4, getY()+4, getHeight()-8, getHeight()-8, 0xFF37A2E6); // blue
}
}
else if (!isClicked())
{
if (isDisabled())
{
target.fill(getX()+label.getPreferredSize().x+4, getY()+4, getHeight()-8, getHeight()-8, 0xFFE5E5E5);
}
else if (isFocused() || isHovered())
{
target.fill(getX()+label.getPreferredSize().x+4, getY()+4, getHeight()-8, getHeight()-8, 0xFF37A2E6); // blue
}
else
{
target.fill(getX()+label.getPreferredSize().x+4, getY()+4, getHeight()-8, getHeight()-8, 0xFFCBCBD0);
}
target.fill(getX()+label.getPreferredSize().x+6, getY()+6, getHeight()-12, getHeight()-12, Colors.WHITE);
}
target.pushTransform(new Mat4().identity().translate( getX(), getY(), 0));
label.assembleSelf(target);
target.popTransform();
}
}

View File

@ -0,0 +1,62 @@
package ru.windcorp.progressia.client.graphics.gui;
import com.google.common.eventbus.Subscribe;
import ru.windcorp.progressia.client.graphics.gui.event.FocusEvent;
import ru.windcorp.progressia.client.graphics.gui.event.HoverEvent;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
import glm.vec._2.i.Vec2i;
public class Interactable extends Component {
private Vec2i currentSize;
protected boolean isDisabled;
protected boolean isClicked;
protected Label label;
public Interactable(String name, Label textLabel)
{
super(name);
label = textLabel;
addChild(textLabel);
addListener(new Object() {
@Subscribe
public void onHoverChanged(HoverEvent e) {
requestReassembly();
}
});
addListener(new Object() {
@Subscribe
public void onFocusChanged(FocusEvent e) {
//inButton.setText(new Label("dummy",new Font().withColor(Colors.BLACK),e.getNewState() ? "Is Focused" : "Isn't focused"));
requestReassembly();
}
});
}
public boolean isClicked()
{
return isClicked;
}
public void setDisable(boolean isDisabled)
{
this.isDisabled = isDisabled;
setFocusable(isDisabled);
}
public boolean isDisabled()
{
return isDisabled;
}
public void setText(Label newText)
{
removeChild(label);
label = newText;
addChild(label);
requestReassembly();
}
}

View File

@ -0,0 +1,87 @@
package ru.windcorp.progressia.client.graphics.gui;
import java.util.function.Consumer;
import org.lwjgl.glfw.GLFW;
import glm.mat._4.Mat4;
import glm.vec._2.i.Vec2i;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.graphics.input.bus.InputListener;
public class RadioButton extends Interactable {
private RadioManager manager;
private boolean isSelected;
public RadioButton(String name, Label textLabel, Consumer<RadioButton> onSelect, RadioManager myManager)
{
super(name, textLabel);
setPreferredSize(textLabel.getPreferredSize().x+23,textLabel.getPreferredSize().y);
manager = myManager;
manager.addOption(this);
addListener((Class<KeyEvent>) KeyEvent.class, (InputListener<KeyEvent>) e -> {if ((e.isLeftMouseButton() && containsCursor()) || (e.getKey()==GLFW.GLFW_KEY_ENTER && isFocused()) )
{
isClicked = e.isPress();
if (!isDisabled() && !isClicked)
{
onSelect.accept(this);
manager.selectSelf(this);
}
requestReassembly();
return true;
}
return false;});
}
public boolean isSelected()
{
return isSelected;
}
public void setSelected(boolean selected)
{
isSelected = selected;
}
protected void assembleSelf(RenderTarget target) {
if (isDisabled())
{
target.fill(getX()+getWidth()-getHeight(), getY(), getHeight(), getHeight(), 0xFFE5E5E5);
}
else if (isClicked() || isHovered() || isFocused())
{
target.fill(getX()+getWidth()-getHeight(), getY(), getHeight(), getHeight(), 0xFF37A2E6);
}
else
{
target.fill(getX()+getWidth()-getHeight(), getY(), getHeight(), getHeight(), 0xFFCBCBD0);
}
//Inside area
if (!isClicked() && isHovered() && !isDisabled())
{
target.fill(getX()+getWidth()-getHeight()+2, getY()+2, getHeight()-4, getHeight()-4, 0xFFC3E4F7);
}
else if (!isClicked() || isDisabled())
{
target.fill(getX()+getWidth()-getHeight()+2, getY()+2, getHeight()-4, getHeight()-4, Colors.WHITE);
}
if (isSelected())
{
if (!isDisabled())
{
target.fill(getX()+getWidth()-getHeight()+4, getY()+4, getHeight()-8, getHeight()-8, 0xFF37A2E6);
}
else
{
target.fill(getX()+getWidth()-getHeight()+4, getY()+4, getHeight()-8, getHeight()-8, 0xFFC3E4F7);
}
}
target.pushTransform(new Mat4().identity().translate( getX(), getY(), 0));
label.assembleSelf(target);
target.popTransform();
}
}

View File

@ -0,0 +1,37 @@
package ru.windcorp.progressia.client.graphics.gui;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class RadioManager {
private List<RadioButton> options;
private int selectedOption;
public RadioManager()
{
options = Collections.synchronizedList(new CopyOnWriteArrayList<>());
}
public void addOption(RadioButton option)
{
options.add(option);
}
public int getSelected()
{
return selectedOption;
}
public void selectSelf(RadioButton option)
{
if (!options.contains(option))
{
return;
}
options.get(selectedOption).setSelected(false);
selectedOption = options.indexOf(option);
option.takeFocus();
option.setSelected(true);
}
}

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];
@ -82,6 +78,10 @@ public class StaticModel extends Model {
private Mat4[] getTransforms() { private Mat4[] getTransforms() {
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 {
@ -33,5 +39,61 @@ class ChunkUpdateListener implements ChunkDataListener {
public void onChunkChanged(ChunkData chunk) { public void onChunkChanged(ChunkData chunk) {
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

@ -15,31 +15,115 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package ru.windcorp.progressia.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;
@ -38,27 +44,52 @@ public abstract class TileRenderSurface extends TileRender implements OpaqueSurf
super(id); super(id);
this.texture = texture; this.texture = texture;
} }
public TileRenderSurface(String id) {
this(id, null);
}
@Override public Texture getTexture(BlockFace blockFace) {
public Texture getTexture(BlockFace face) {
return texture; return texture;
} }
public Vec4 getColorMultiplier(BlockFace blockFace) {
return Colors.WHITE;
}
@Override
public final void getFaces(
ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace,
boolean inner,
Consumer<Face> output,
Vec3 offset
) {
output.accept(createFace(chunk, blockInChunk, blockFace, inner, offset));
}
private Face createFace(
ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace,
boolean inner,
Vec3 offset
) {
return Faces.createBlockFace(
WorldRenderProgram.getDefault(),
getTexture(blockFace),
getColorMultiplier(blockFace),
offset,
blockFace,
inner
);
}
@Override @Override
public Renderable createRenderable(BlockFace face) { public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace) {
ShapeRenderProgram program = WorldRenderProgram.getDefault();
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,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 {
private final ResourceReader resourceReader;
public Resource(String name) { 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

@ -19,9 +19,16 @@
package ru.windcorp.progressia.common.resource; 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

@ -15,39 +15,27 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package ru.windcorp.progressia.client.world.cro;
import java.util.Collection; package ru.windcorp.progressia.common.util.crash.providers;
import java.util.HashMap;
import ru.windcorp.progressia.client.graphics.backend.GraphicsBackend;
import ru.windcorp.progressia.common.util.crash.ContextProvider;
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

@ -158,5 +158,9 @@ 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();
} }
@ -41,6 +43,10 @@ public class BlockRelation {
public Vec3i getVector() { public Vec3i getVector() {
return vector; return vector;
} }
public Vec3 getFloatVector() {
return floatVector;
}
public Vec3 getNormalized() { public Vec3 getNormalized() {
return normalized; return normalized;

View File

@ -91,6 +91,72 @@ public interface GenericChunk<Self extends GenericChunk<Self, B, T, TS>, B exten
Vectors.release(v); Vectors.release(v);
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(

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package ru.windcorp.progressia.server; package ru.windcorp.progressia.server;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -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");
@ -151,6 +152,10 @@ public class TickerCoordinator {
public double getTPS() { public double getTPS() {
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();
@ -163,6 +168,10 @@ public class TickerCoordinator {
tickStart = System.currentTimeMillis(); tickStart = System.currentTimeMillis();
} }
private void onTickEnd() {
ticks++;
}
/* /*
* runOneTick & Friends * runOneTick & Friends
@ -182,6 +191,8 @@ public class TickerCoordinator {
logger.debug("Pass complete"); logger.debug("Pass complete");
passes++; passes++;
} }
onTickEnd();
logger.debug("Tick complete; run {} passes", passes); logger.debug("Tick complete; run {} passes", passes);
@ -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,22 +18,23 @@
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.Button;
import ru.windcorp.progressia.client.graphics.gui.Checkbox;
import ru.windcorp.progressia.client.graphics.gui.DynamicLabel; import ru.windcorp.progressia.client.graphics.gui.DynamicLabel;
import ru.windcorp.progressia.client.graphics.gui.GUILayer; import ru.windcorp.progressia.client.graphics.gui.GUILayer;
import ru.windcorp.progressia.client.graphics.gui.Button;
import ru.windcorp.progressia.client.graphics.gui.Label; import ru.windcorp.progressia.client.graphics.gui.Label;
import ru.windcorp.progressia.client.graphics.gui.Panel; import ru.windcorp.progressia.client.graphics.gui.Panel;
import ru.windcorp.progressia.client.graphics.gui.RadioButton;
import ru.windcorp.progressia.client.graphics.gui.RadioManager;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign; import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical; import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
import ru.windcorp.progressia.client.localization.Localizer; import ru.windcorp.progressia.client.localization.Localizer;
@ -44,6 +45,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() {
@ -55,6 +61,54 @@ public class LayerTestGUI extends GUILayer {
Font font = new Font().withColor(color).deriveOutlined(); Font font = new Font().withColor(color).deriveOutlined();
TestPlayerControls tpc = TestPlayerControls.getInstance(); TestPlayerControls tpc = TestPlayerControls.getInstance();
Button disableButton = new Button("TestButton",
new Label("TestButtonLabel", new Font().withColor(Colors.BLACK), "I'm in TestGUI"),
b -> {b.setDisable(!b.isDisabled());}
);
panel.addChild(disableButton);
panel.addChild(
new Button(
"TestButton2",
new Label("TestButtonLabel2", new Font().withColor(Colors.BLACK), "I enable the above button"),
b -> {disableButton.setDisable(false);}
)
);
panel.addChild(
new Checkbox(
"Checkbox1",
new Label("CheckboxLabel", font,"Reset"),
c -> {c.setText(
new Label("CheckboxLabel", font, "Set")
);},
c -> {c.setText(
new Label("CheckboxLabel", font, "Reset")
);}
)
);
RadioManager manager = new RadioManager();
panel.addChild(
new RadioButton(
"Radio1,1",
new Label("RadioLabel1,1",font,"Option 1"),
rb -> {},
manager
)
);
panel.addChild(
new RadioButton(
"Radio1,2",
new Label("RadioLabel1,2",font,"Option 2"),
rb -> {},
manager
)
);
panel.addChild( panel.addChild(
new Label( new Label(
@ -110,6 +164,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",
@ -243,13 +313,127 @@ public class LayerTestGUI extends GUILayer {
} }
} }
private static class Counter {
private int DISPLAY_INERTIA = 200;
private long AVERAGE_TIME = 10000;
private long first_time;
private final long[] values;
private int size;
private int head;
private long lastUpdate;
public Counter(long averageTime, int maxTPS)
{
DISPLAY_INERTIA = (int) averageTime*maxTPS/1000;
AVERAGE_TIME = averageTime;
first_time = -1;
values = new long[DISPLAY_INERTIA];
}
public void add(long value) {
if (first_time==-1)
{
first_time=System.currentTimeMillis();
}
if (size == values.length) {
values[head] = value;
head++;
if (head == values.length)
head = 0;
} else {
values[size] = value;
size++;
}
}
public double average() {
double count=0;
long ctime = System.currentTimeMillis();
for (int i=0;i<size;i++)
{
if ((ctime-values[i])<AVERAGE_TIME)
{
count++;
}
}
if ((ctime-first_time)<AVERAGE_TIME)
{
if ((ctime-first_time)<10)
{
return 20.0;
}
return count/(ctime-first_time)*1000;
}
return count/AVERAGE_TIME*1000;
}
public double update() {
long now = (long) (GraphicsInterface.getTime() / .05);
if (lastUpdate != now) {
lastUpdate = now;
add(System.currentTimeMillis());
}
return average();
}
}
private static class Queue {
private static final int DISPLAY_INERTIA = 32;
private static final double UPDATE_INTERVAL = Units.get(50.0, "ms");
private final double[] values = new double[DISPLAY_INERTIA];
private int size;
private int head;
private long lastUpdate;
public void add(double value) {
if (size == values.length) {
values[head] = value;
head++;
if (head == values.length)
head = 0;
} else {
values[size] = value;
size++;
}
}
public double average() {
if (size == values.length && head!=0) {
return (values[head-1]-values[head])/DISPLAY_INERTIA*20;
} else if (head==0) {
return (values[size-1]-values[0])/DISPLAY_INERTIA*20;
} else {
return values[head-1]/size*20;
}
}
public double update(double value) {
long now = (long) (GraphicsInterface.getTime() / UPDATE_INTERVAL);
if (lastUpdate != now) {
lastUpdate = now;
add(value);
}
return average();
}
}
private static final Averager FPS_RECORD = new Averager(); private static final Averager FPS_RECORD = new Averager();
private static final Averager TPS_RECORD = new Averager(); private static final Queue TPS_RECORD = new Queue();
private static final Supplier<CharSequence> TPS_STRING = DynamicStrings.builder() private static final Supplier<CharSequence> TPS_STRING = DynamicStrings.builder()
.addDyn(new MutableStringLocalized("LayerTestGUI.TPSDisplay")) .addDyn(new MutableStringLocalized("LayerTestGUI.TPSDisplay"))
.addDyn(() -> TPS_RECORD.update(ServerState.getInstance().getTPS()), 5, 1) .addDyn(() -> TPS_RECORD.update(ServerState.getInstance().getUptimeTicks()), 5, 1)
.buildSupplier(); .buildSupplier();
private static final Supplier<CharSequence> POS_STRING = DynamicStrings.builder() private static final Supplier<CharSequence> POS_STRING = DynamicStrings.builder()

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

@ -19,4 +19,6 @@ LayerTestGUI.PosDisplay.NA.Client = Pos: client n/a
LayerTestGUI.PosDisplay.NA.Entity = Pos: entity n/a 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

@ -19,4 +19,6 @@ LayerTestGUI.PosDisplay.NA.Client = Поз: клиент н/д
LayerTestGUI.PosDisplay.NA.Entity = Поз: сущность н/д 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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB