Compare commits
108 Commits
v0.0.0-tec
...
save-world
Author | SHA1 | Date | |
---|---|---|---|
0c41350ae7 | |||
a633c8324e
|
|||
6b33f231b4 | |||
e2308b825d
|
|||
e0f6a08740 | |||
2820e01974 | |||
46bcb85044 | |||
c5dfe3d0b7 | |||
0100c8791d
|
|||
e967a64401 | |||
d2ffe1fe0e
|
|||
f4300558d5
|
|||
cd16334db8
|
|||
41a2909f7c
|
|||
a222ea8f67
|
|||
98250cd524 | |||
9dcb3a7748 | |||
0ccc108ddd
|
|||
c7e7d3bdac
|
|||
62729f5873
|
|||
84864f8947
|
|||
d01ef3654f
|
|||
9fc1a21191
|
|||
4f620b7261
|
|||
6f90bf345b
|
|||
2328f2ae3a
|
|||
fae09edb16 | |||
e4ced6507e | |||
b7dcbb0f30 | |||
9c26418354 | |||
15b5d367b4
|
|||
ca2014802a
|
|||
539a61e854
|
|||
a3760d7425
|
|||
d33b48578d
|
|||
a6fd81ba1e
|
|||
82872c7cf3
|
|||
54c66d28d6
|
|||
78a1c25554
|
|||
a03c783fc9
|
|||
0a45613e45
|
|||
5fb4c601ff
|
|||
020802a89c
|
|||
6891d3a095 | |||
0f909039fe
|
|||
15f741bc04
|
|||
8167c40f64 | |||
8bc23acb61 | |||
80541eafc3
|
|||
254faca0a5 | |||
0f60d45ffa
|
|||
0c66f1751e | |||
c88dea6030 | |||
6521cb5749 | |||
fbc803d6e2
|
|||
94db44e443 | |||
53f72b068a | |||
a9ca5f6b17 | |||
4ab7cb738e | |||
1ee9a55d19 | |||
a338a00f1d | |||
9a326603cd | |||
d7afe39f00
|
|||
0264e512ab
|
|||
e47fb3c4bd
|
|||
eace6733ce
|
|||
085f602427
|
|||
737b495fc4
|
|||
531a8c99c3
|
|||
6fb7e7fc04
|
|||
20dccf3d12
|
|||
32851b8fb0
|
|||
a95bdf1efe
|
|||
e0a03cad1d
|
|||
2532e80f6a
|
|||
3c3f3816df
|
|||
2d3d250f92
|
|||
c49fdfa5ff
|
|||
9d7f69892b
|
|||
7ecdfdfb4d
|
|||
4332a78221
|
|||
ef572c43c7
|
|||
f28c765e3f
|
|||
f4311fb27c
|
|||
abd8d9eebb
|
|||
a9a21ce664
|
|||
bd5a1fa04e
|
|||
2d55d4db51
|
|||
d438d2aa14
|
|||
d3c5011063
|
|||
10d271059c
|
|||
acef9d32df
|
|||
848178b343
|
|||
b1666fa4b9
|
|||
73d24d36f4 | |||
bdb3bff570 | |||
6997bb1104 | |||
eac0a34516 | |||
f9717be412
|
|||
127d1c3d87 | |||
26a35f306c
|
|||
52f3f653d8 | |||
553837f207
|
|||
8c5493f78e
|
|||
fc85eb5658
|
|||
260562310a
|
|||
85edc07c75 | |||
fcf225f9c7 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
BIN
build_packages/NSIS/left_side.bmp
Normal file
BIN
build_packages/NSIS/left_side.bmp
Normal file
Binary file not shown.
After Width: | Height: | Size: 151 KiB |
BIN
build_packages/NSIS/logo.ico
Normal file
BIN
build_packages/NSIS/logo.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 187 KiB |
69
docs/CONTRIBUTING.md
Normal file
69
docs/CONTRIBUTING.md
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# Contributing Guidelines
|
||||||
|
|
||||||
|
This document lists conventions adopted by Progressia developers.
|
||||||
|
|
||||||
|
## git
|
||||||
|
|
||||||
|
### Branches
|
||||||
|
Progressia repository contains a `master` branch and several "feature" branches.
|
||||||
|
|
||||||
|
`master` is expected to contain a version of the game suitable for demonstration and forking/branching. Do not commit directly to `master` without OLEGSHA's approval.
|
||||||
|
- `master` must always correctly build without compiler warnings (see below).
|
||||||
|
- `master` must always pass all unit tests.
|
||||||
|
- `master` must always be able to launch successfully.
|
||||||
|
- `master` must always only contain working features.
|
||||||
|
- `master` should not contain excessive debug code.
|
||||||
|
- `master` must always have its code and filenames formatted (see below).
|
||||||
|
|
||||||
|
"Feature" branches are branches dedicated to the development of a single feature. When the feature reaches completion the branch is merged into `master` and removed. Intermediate merges into `master` may occur when some fitting milestone is reached. Intermediate merges from `master` may be done as necessary. Merges between "feature" branches should generally be avoided.
|
||||||
|
|
||||||
|
When beginning work on a new feature, create a new branch based on `master` (or on another "feature" branch if absolutely necessary). Use `all-small-with-dashes` to name the branch: `add-trees` or `rebalance-plastics` are good names. Do not fix unrelated bugs or work on unrelated features in a "feature" branch - create a new one, switch to an existing one or commit directly to `master` if the changes are small enough.
|
||||||
|
|
||||||
|
"Feature" branches may not be formatted properly. Formatting is required before merging into `master` or other branches.
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
- Commits must leave the branch in a state that builds without compiler warnings (see below).
|
||||||
|
- Changes should be grouped in commits semantically. Avoid committing many small related changes in sequence; if necessary, wait and accumulate them. Avoid committing unrelated changes together; if necessary, split staged changes into several commits. This should normally result in about 1-2 commits for a day's work.
|
||||||
|
- Commit bulk changes (renaming, formatting, ...) separately. Don't ever commit whitespace changes outside formatting commits.
|
||||||
|
- Message format:
|
||||||
|
|
||||||
|
```
|
||||||
|
Short description of changes
|
||||||
|
<empty line>
|
||||||
|
- Enumeration of changes
|
||||||
|
- Nest when appropriate
|
||||||
|
- Use dashes only
|
||||||
|
- List not needed for small commits
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
Changed packages for relations, renamed Face to ShapePart
|
||||||
|
|
||||||
|
- Added BlockRelation as an abstract superclass to existing relations
|
||||||
|
- Must be given an absolute "up" direction before use
|
||||||
|
- Moved AbsFace, AbsRelation and BlockRelation to .world.rels
|
||||||
|
- Renamed Face to ShapePart to reduce confusion with AbsFace
|
||||||
|
```
|
||||||
|
|
||||||
|
- Only commit changes described in the commit message. Please double-check staged files before committing.
|
||||||
|
- Avoid merge conflicts. Pull before committing.
|
||||||
|
- Better sign commits than not. See: [git](https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work), [GitHub](https://docs.github.com/en/github/authenticating-to-github/managing-commit-signature-verification).
|
||||||
|
|
||||||
|
## Code
|
||||||
|
|
||||||
|
### Warnings
|
||||||
|
Make sure that all committed code contains no compiler warnings. This specifically includes unused imports, unused private members, missing `@Override`s and warnings related to generics.
|
||||||
|
|
||||||
|
Warnings about unknown tokens in `@SuppressWarnings` are temporarily ignored. Please disable them in your IDE.
|
||||||
|
|
||||||
|
### Code Style
|
||||||
|
Formatting code is important.
|
||||||
|
|
||||||
|
- The format is specified within the files inside `/templates_and_presets/eclipse_ide`. Import the specifications into Eclipse or IntelliJ IDEA and use the IDEs' format feature. Alternatively format the code manually in accordance with existing files.
|
||||||
|
- Only use tabs for indentation. Never indent with spaces even when wrapping lines. This is to ensure that indentation does not break when tab width is different.
|
||||||
|
- Don't use `I` prefix for interfaces (not `IDoable` but `Doable`).
|
||||||
|
- Prioritize readability over compactness. Do not hesitate to use (very) long identifiers if they aid comprehension.
|
||||||
|
- Document all mathematics unless it is trivial, especially when using math notation for variable names.
|
||||||
|
- Use proper English when writing comments. Avoid boxes in comments. Use `//` for single-line comments.
|
@ -15,21 +15,31 @@
|
|||||||
* 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;
|
package ru.windcorp.progressia;
|
||||||
|
|
||||||
|
import ru.windcorp.progressia.client.graphics.GUI;
|
||||||
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
||||||
import ru.windcorp.progressia.common.util.crash.analyzers.OutOfMemoryAnalyzer;
|
import ru.windcorp.progressia.common.util.crash.analyzers.OutOfMemoryAnalyzer;
|
||||||
import ru.windcorp.progressia.common.util.crash.providers.*;
|
import ru.windcorp.progressia.common.util.crash.providers.*;
|
||||||
|
import ru.windcorp.progressia.test.LayerTitle;
|
||||||
|
|
||||||
public class ProgressiaLauncher {
|
public class ProgressiaLauncher {
|
||||||
|
|
||||||
public static String[] arguments;
|
public static String[] arguments;
|
||||||
|
private static Proxy proxy;
|
||||||
|
|
||||||
public static void launch(String[] args, Proxy proxy) {
|
public static void launch(String[] args, Proxy proxy) {
|
||||||
arguments = args.clone();
|
arguments = args.clone();
|
||||||
setupCrashReports();
|
setupCrashReports();
|
||||||
|
|
||||||
proxy.initialize();
|
proxy.initialize();
|
||||||
|
ProgressiaLauncher.proxy = proxy;
|
||||||
|
GUI.addTopLayer(new LayerTitle("Title"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Proxy getProxy() {
|
||||||
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void setupCrashReports() {
|
private static void setupCrashReports() {
|
||||||
@ -40,6 +50,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());
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ import ru.windcorp.progressia.client.graphics.world.Camera;
|
|||||||
import ru.windcorp.progressia.client.graphics.world.EntityAnchor;
|
import ru.windcorp.progressia.client.graphics.world.EntityAnchor;
|
||||||
import ru.windcorp.progressia.client.graphics.world.LocalPlayer;
|
import ru.windcorp.progressia.client.graphics.world.LocalPlayer;
|
||||||
import ru.windcorp.progressia.client.world.WorldRender;
|
import ru.windcorp.progressia.client.world.WorldRender;
|
||||||
import ru.windcorp.progressia.common.world.WorldData;
|
import ru.windcorp.progressia.common.world.DefaultWorldData;
|
||||||
import ru.windcorp.progressia.common.world.entity.EntityData;
|
import ru.windcorp.progressia.common.world.entity.EntityData;
|
||||||
|
|
||||||
public class Client {
|
public class Client {
|
||||||
@ -36,7 +36,7 @@ public class Client {
|
|||||||
|
|
||||||
private final ServerCommsChannel comms;
|
private final ServerCommsChannel comms;
|
||||||
|
|
||||||
public Client(WorldData world, ServerCommsChannel comms) {
|
public Client(DefaultWorldData world, ServerCommsChannel comms) {
|
||||||
this.world = new WorldRender(world, this);
|
this.world = new WorldRender(world, this);
|
||||||
this.comms = comms;
|
this.comms = comms;
|
||||||
|
|
||||||
|
@ -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.client;
|
package ru.windcorp.progressia.client;
|
||||||
|
|
||||||
import ru.windcorp.progressia.Proxy;
|
import ru.windcorp.progressia.Proxy;
|
||||||
@ -30,14 +30,16 @@ import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
|
|||||||
import ru.windcorp.progressia.client.localization.Localizer;
|
import ru.windcorp.progressia.client.localization.Localizer;
|
||||||
import ru.windcorp.progressia.common.resource.ResourceManager;
|
import ru.windcorp.progressia.common.resource.ResourceManager;
|
||||||
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
||||||
import ru.windcorp.progressia.server.ServerState;
|
|
||||||
import ru.windcorp.progressia.test.TestContent;
|
import ru.windcorp.progressia.test.TestContent;
|
||||||
|
import ru.windcorp.progressia.test.TestMusicPlayer;
|
||||||
|
|
||||||
public class ClientProxy implements Proxy {
|
public class ClientProxy implements Proxy {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
|
|
||||||
GraphicsBackend.initialize();
|
GraphicsBackend.initialize();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
RenderTaskQueue.waitAndInvoke(FlatRenderProgram::init);
|
RenderTaskQueue.waitAndInvoke(FlatRenderProgram::init);
|
||||||
RenderTaskQueue.waitAndInvoke(WorldRenderProgram::init);
|
RenderTaskQueue.waitAndInvoke(WorldRenderProgram::init);
|
||||||
@ -57,8 +59,6 @@ public class ClientProxy implements Proxy {
|
|||||||
|
|
||||||
AudioSystem.initialize();
|
AudioSystem.initialize();
|
||||||
|
|
||||||
ServerState.startServer();
|
TestMusicPlayer.start();
|
||||||
ClientState.connectToLocalServer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,15 +15,18 @@
|
|||||||
* 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;
|
package ru.windcorp.progressia.client;
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.comms.localhost.LocalServerCommsChannel;
|
import ru.windcorp.progressia.client.comms.localhost.LocalServerCommsChannel;
|
||||||
import ru.windcorp.progressia.client.graphics.GUI;
|
import ru.windcorp.progressia.client.graphics.GUI;
|
||||||
|
import ru.windcorp.progressia.client.graphics.Layer;
|
||||||
import ru.windcorp.progressia.client.graphics.world.LayerWorld;
|
import ru.windcorp.progressia.client.graphics.world.LayerWorld;
|
||||||
import ru.windcorp.progressia.common.world.WorldData;
|
import ru.windcorp.progressia.common.world.DefaultWorldData;
|
||||||
|
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
|
||||||
import ru.windcorp.progressia.server.ServerState;
|
import ru.windcorp.progressia.server.ServerState;
|
||||||
import ru.windcorp.progressia.test.LayerAbout;
|
import ru.windcorp.progressia.test.LayerAbout;
|
||||||
|
import ru.windcorp.progressia.test.LayerTestText;
|
||||||
import ru.windcorp.progressia.test.LayerTestUI;
|
import ru.windcorp.progressia.test.LayerTestUI;
|
||||||
import ru.windcorp.progressia.test.TestContent;
|
import ru.windcorp.progressia.test.TestContent;
|
||||||
|
|
||||||
@ -41,7 +44,7 @@ public class ClientState {
|
|||||||
|
|
||||||
public static void connectToLocalServer() {
|
public static void connectToLocalServer() {
|
||||||
|
|
||||||
WorldData world = new WorldData();
|
DefaultWorldData world = new DefaultWorldData();
|
||||||
|
|
||||||
LocalServerCommsChannel channel = new LocalServerCommsChannel(
|
LocalServerCommsChannel channel = new LocalServerCommsChannel(
|
||||||
ServerState.getInstance()
|
ServerState.getInstance()
|
||||||
@ -52,11 +55,39 @@ public class ClientState {
|
|||||||
channel.connect(TestContent.PLAYER_LOGIN);
|
channel.connect(TestContent.PLAYER_LOGIN);
|
||||||
|
|
||||||
setInstance(client);
|
setInstance(client);
|
||||||
|
displayLoadingScreen();
|
||||||
|
|
||||||
GUI.addBottomLayer(new LayerWorld(client));
|
}
|
||||||
GUI.addTopLayer(new LayerTestUI());
|
|
||||||
GUI.addTopLayer(new LayerAbout());
|
|
||||||
|
|
||||||
|
private static void displayLoadingScreen() {
|
||||||
|
GUI.addTopLayer(new LayerTestText("Text", new MutableStringLocalized("LayerText.Load"), layer -> {
|
||||||
|
Client client = ClientState.getInstance();
|
||||||
|
|
||||||
|
// TODO refacetor and remove
|
||||||
|
if (client != null) {
|
||||||
|
client.getComms().processPackets();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client != null && client.getLocalPlayer().hasEntity()) {
|
||||||
|
GUI.removeLayer(layer);
|
||||||
|
|
||||||
|
// TODO refactor, this shouldn't be here
|
||||||
|
LayerWorld layerWorld = new LayerWorld(client);
|
||||||
|
LayerTestUI layerUI = new LayerTestUI();
|
||||||
|
LayerAbout layerAbout = new LayerAbout();
|
||||||
|
GUI.addBottomLayer(layerWorld);
|
||||||
|
GUI.addTopLayer(layerUI);
|
||||||
|
GUI.addTopLayer(layerAbout);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void disconnectFromLocalServer() {
|
||||||
|
getInstance().getComms().disconnect();
|
||||||
|
|
||||||
|
for (Layer layer : GUI.getLayers()) {
|
||||||
|
GUI.removeLayer(layer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ClientState() {
|
private ClientState() {
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package ru.windcorp.progressia.client.audio;
|
||||||
|
|
||||||
|
import ru.windcorp.progressia.client.audio.backend.SoundType;
|
||||||
|
import ru.windcorp.progressia.common.util.namespaces.NamespacedInstanceRegistry;
|
||||||
|
|
||||||
|
public class AudioRegistry extends NamespacedInstanceRegistry<SoundType> {
|
||||||
|
|
||||||
|
private static final AudioRegistry INSTANCE = new AudioRegistry();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the instance
|
||||||
|
*/
|
||||||
|
public static AudioRegistry getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -18,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
|
||||||
);
|
);
|
||||||
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -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(
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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,
|
||||||
|
@ -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.client.comms.localhost;
|
package ru.windcorp.progressia.client.comms.localhost;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -15,12 +15,13 @@
|
|||||||
* 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.comms.localhost;
|
package ru.windcorp.progressia.client.comms.localhost;
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.comms.ServerCommsChannel;
|
import ru.windcorp.progressia.client.comms.ServerCommsChannel;
|
||||||
import ru.windcorp.progressia.common.comms.packets.Packet;
|
import ru.windcorp.progressia.common.comms.packets.Packet;
|
||||||
import ru.windcorp.progressia.server.Server;
|
import ru.windcorp.progressia.server.Server;
|
||||||
|
import ru.windcorp.progressia.server.ServerState;
|
||||||
|
|
||||||
public class LocalServerCommsChannel extends ServerCommsChannel {
|
public class LocalServerCommsChannel extends ServerCommsChannel {
|
||||||
|
|
||||||
@ -54,7 +55,7 @@ public class LocalServerCommsChannel extends ServerCommsChannel {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void disconnect() {
|
public void disconnect() {
|
||||||
// Do nothing
|
ServerState.getInstance().getClientManager().disconnectClient(localClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,13 @@ public class Colors {
|
|||||||
DEBUG_BLUE = toVector(0xFF0000FF),
|
DEBUG_BLUE = toVector(0xFF0000FF),
|
||||||
DEBUG_CYAN = toVector(0xFF00FFFF),
|
DEBUG_CYAN = toVector(0xFF00FFFF),
|
||||||
DEBUG_MAGENTA = toVector(0xFFFF00FF),
|
DEBUG_MAGENTA = toVector(0xFFFF00FF),
|
||||||
DEBUG_YELLOW = toVector(0xFFFFFF00);
|
DEBUG_YELLOW = toVector(0xFFFFFF00),
|
||||||
|
|
||||||
|
LIGHT_GRAY = toVector(0xFFCBCBD0),
|
||||||
|
BLUE = toVector(0xFF37A2E6),
|
||||||
|
HOVER_BLUE = toVector(0xFFC3E4F7),
|
||||||
|
DISABLED_GRAY = toVector(0xFFE5E5E5),
|
||||||
|
DISABLED_BLUE = toVector(0xFFB2D8ED);
|
||||||
|
|
||||||
public static Vec4 toVector(int argb) {
|
public static Vec4 toVector(int argb) {
|
||||||
return toVector(argb, new Vec4());
|
return toVector(argb, new Vec4());
|
||||||
|
@ -15,15 +15,17 @@
|
|||||||
* 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.graphics;
|
package ru.windcorp.progressia.client.graphics;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import com.google.common.eventbus.Subscribe;
|
import com.google.common.eventbus.Subscribe;
|
||||||
|
|
||||||
|
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||||
import ru.windcorp.progressia.client.graphics.input.CursorEvent;
|
import ru.windcorp.progressia.client.graphics.input.CursorEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.input.FrameResizeEvent;
|
import ru.windcorp.progressia.client.graphics.input.FrameResizeEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||||
@ -57,15 +59,27 @@ public class GUI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void addBottomLayer(Layer layer) {
|
public static void addBottomLayer(Layer layer) {
|
||||||
modify(layers -> layers.add(layer));
|
Objects.requireNonNull(layer, "layer");
|
||||||
|
modify(layers -> {
|
||||||
|
layers.add(layer);
|
||||||
|
layer.onAdded();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void addTopLayer(Layer layer) {
|
public static void addTopLayer(Layer layer) {
|
||||||
modify(layers -> layers.add(0, layer));
|
Objects.requireNonNull(layer, "layer");
|
||||||
|
modify(layers -> {
|
||||||
|
layers.add(0, layer);
|
||||||
|
layer.onAdded();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void removeLayer(Layer layer) {
|
public static void removeLayer(Layer layer) {
|
||||||
modify(layers -> layers.remove(layer));
|
Objects.requireNonNull(layer, "layer");
|
||||||
|
modify(layers -> {
|
||||||
|
layers.remove(layer);
|
||||||
|
layer.onRemoved();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void modify(LayerStackModification mod) {
|
private static void modify(LayerStackModification mod) {
|
||||||
@ -78,12 +92,33 @@ public class GUI {
|
|||||||
|
|
||||||
public static void render() {
|
public static void render() {
|
||||||
synchronized (LAYERS) {
|
synchronized (LAYERS) {
|
||||||
MODIFICATION_QUEUE.forEach(action -> action.affect(LAYERS));
|
|
||||||
MODIFICATION_QUEUE.clear();
|
if (!MODIFICATION_QUEUE.isEmpty()) {
|
||||||
|
MODIFICATION_QUEUE.forEach(action -> action.affect(LAYERS));
|
||||||
|
MODIFICATION_QUEUE.clear();
|
||||||
|
|
||||||
|
boolean isMouseCurrentlyCaptured = GraphicsInterface.isMouseCaptured();
|
||||||
|
Layer.CursorPolicy policy = Layer.CursorPolicy.REQUIRE;
|
||||||
|
|
||||||
|
for (Layer layer : LAYERS) {
|
||||||
|
Layer.CursorPolicy currentPolicy = layer.getCursorPolicy();
|
||||||
|
|
||||||
|
if (currentPolicy != Layer.CursorPolicy.INDIFFERENT) {
|
||||||
|
policy = currentPolicy;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean shouldCaptureMouse = (policy == Layer.CursorPolicy.FORBID);
|
||||||
|
if (shouldCaptureMouse != isMouseCurrentlyCaptured) {
|
||||||
|
GraphicsInterface.setMouseCaptured(shouldCaptureMouse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = LAYERS.size() - 1; i >= 0; --i) {
|
for (int i = LAYERS.size() - 1; i >= 0; --i) {
|
||||||
LAYERS.get(i).render();
|
LAYERS.get(i).render();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,15 +30,52 @@ public abstract class Layer {
|
|||||||
private boolean hasInitialized = false;
|
private boolean hasInitialized = false;
|
||||||
|
|
||||||
private final AtomicBoolean isValid = new AtomicBoolean(false);
|
private final AtomicBoolean isValid = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents various requests that a {@link Layer} can make regarding the
|
||||||
|
* presence of a visible cursor. The value of the highest layer that is not
|
||||||
|
* {@link #INDIFFERENT} is used.
|
||||||
|
*/
|
||||||
|
public static enum CursorPolicy {
|
||||||
|
/**
|
||||||
|
* Require that a cursor is visible.
|
||||||
|
*/
|
||||||
|
REQUIRE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Layer} should not affect the presence or absence of a
|
||||||
|
* visible cursor; lower layers should be consulted.
|
||||||
|
*/
|
||||||
|
INDIFFERENT,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forbid a visible cursor.
|
||||||
|
*/
|
||||||
|
FORBID
|
||||||
|
}
|
||||||
|
|
||||||
|
private CursorPolicy cursorPolicy = CursorPolicy.INDIFFERENT;
|
||||||
|
|
||||||
public Layer(String name) {
|
public Layer(String name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Layer " + name;
|
return "Layer " + name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CursorPolicy getCursorPolicy() {
|
||||||
|
return cursorPolicy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCursorPolicy(CursorPolicy cursorPolicy) {
|
||||||
|
this.cursorPolicy = cursorPolicy;
|
||||||
|
}
|
||||||
|
|
||||||
void render() {
|
void render() {
|
||||||
GraphicsInterface.startNextLayer();
|
GraphicsInterface.startNextLayer();
|
||||||
@ -78,5 +115,13 @@ public abstract class Layer {
|
|||||||
protected int getHeight() {
|
protected int getHeight() {
|
||||||
return GraphicsInterface.getFrameHeight();
|
return GraphicsInterface.getFrameHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void onAdded() {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onRemoved() {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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,61 @@ 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isMouseCaptured() {
|
||||||
|
return glfwGetInputMode(windowHandle, GLFW_CURSOR) == GLFW_CURSOR_DISABLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setMouseCaptured(boolean capture) {
|
||||||
|
int mode = capture ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL;
|
||||||
|
glfwSetInputMode(windowHandle, GLFW_CURSOR, mode);
|
||||||
|
|
||||||
|
if (!capture) {
|
||||||
|
glfwSetCursorPos(windowHandle, FRAME_SIZE.x / 2.0, FRAME_SIZE.y / 2.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -73,4 +73,21 @@ public class GraphicsInterface {
|
|||||||
GraphicsBackend.startNextLayer();
|
GraphicsBackend.startNextLayer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void makeFullscreen(boolean state) {
|
||||||
|
if (state) {
|
||||||
|
GraphicsBackend.setFullscreen();
|
||||||
|
} else {
|
||||||
|
GraphicsBackend.setWindowed();
|
||||||
|
}
|
||||||
|
GraphicsBackend.setVSyncEnabled(GraphicsBackend.isVSyncEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isMouseCaptured() {
|
||||||
|
return GraphicsBackend.isMouseCaptured();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setMouseCaptured(boolean capture) {
|
||||||
|
GraphicsBackend.setMouseCaptured(capture);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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() {
|
||||||
@ -64,10 +65,8 @@ class LWJGLInitializer {
|
|||||||
|
|
||||||
GraphicsBackend.setWindowHandle(handle);
|
GraphicsBackend.setWindowHandle(handle);
|
||||||
|
|
||||||
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 +86,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() {
|
||||||
|
@ -29,8 +29,8 @@ import glm.vec._3.Vec3;
|
|||||||
import glm.vec._4.Vec4;
|
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.ShapePart;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Faces;
|
import ru.windcorp.progressia.client.graphics.model.ShapeParts;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Shape;
|
import ru.windcorp.progressia.client.graphics.model.Shape;
|
||||||
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;
|
||||||
@ -84,7 +84,7 @@ public class RenderTarget {
|
|||||||
|
|
||||||
private final Deque<TransformedMask> maskStack = new LinkedList<>();
|
private final Deque<TransformedMask> maskStack = new LinkedList<>();
|
||||||
private final Deque<Mat4> transformStack = new LinkedList<>();
|
private final Deque<Mat4> transformStack = new LinkedList<>();
|
||||||
private final List<Face> currentClipFaces = new ArrayList<>();
|
private final List<ShapePart> currentClipFaces = new ArrayList<>();
|
||||||
|
|
||||||
private int depth = 0;
|
private int depth = 0;
|
||||||
|
|
||||||
@ -94,8 +94,8 @@ public class RenderTarget {
|
|||||||
|
|
||||||
protected void assembleCurrentClipFromFaces() {
|
protected void assembleCurrentClipFromFaces() {
|
||||||
if (!currentClipFaces.isEmpty()) {
|
if (!currentClipFaces.isEmpty()) {
|
||||||
Face[] faces = currentClipFaces.toArray(
|
ShapePart[] faces = currentClipFaces.toArray(
|
||||||
new Face[currentClipFaces.size()]
|
new ShapePart[currentClipFaces.size()]
|
||||||
);
|
);
|
||||||
currentClipFaces.clear();
|
currentClipFaces.clear();
|
||||||
|
|
||||||
@ -189,16 +189,13 @@ public class RenderTarget {
|
|||||||
|
|
||||||
public void addCustomRenderer(Renderable renderable) {
|
public void addCustomRenderer(Renderable renderable) {
|
||||||
assembleCurrentClipFromFaces();
|
assembleCurrentClipFromFaces();
|
||||||
assembled.add(
|
|
||||||
new Clip(
|
float depth = this.depth--;
|
||||||
maskStack,
|
Mat4 transform = new Mat4().translate(0, 0, depth).mul(getTransform());
|
||||||
getTransform(),
|
assembled.add(new Clip(maskStack, transform, renderable));
|
||||||
renderable
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addFaceToCurrentClip(Face face) {
|
protected void addFaceToCurrentClip(ShapePart face) {
|
||||||
currentClipFaces.add(face);
|
currentClipFaces.add(face);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,7 +267,7 @@ public class RenderTarget {
|
|||||||
fill(Colors.toVector(color));
|
fill(Colors.toVector(color));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Face createRectagleFace(
|
public ShapePart createRectagleFace(
|
||||||
int x,
|
int x,
|
||||||
int y,
|
int y,
|
||||||
int width,
|
int width,
|
||||||
@ -280,7 +277,7 @@ public class RenderTarget {
|
|||||||
) {
|
) {
|
||||||
float depth = this.depth--;
|
float depth = this.depth--;
|
||||||
|
|
||||||
return Faces.createRectangle(
|
return ShapeParts.createRectangle(
|
||||||
FlatRenderProgram.getDefault(),
|
FlatRenderProgram.getDefault(),
|
||||||
texture,
|
texture,
|
||||||
color,
|
color,
|
||||||
@ -291,7 +288,7 @@ public class RenderTarget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Face createRectagleFace(
|
public ShapePart createRectagleFace(
|
||||||
int x,
|
int x,
|
||||||
int y,
|
int y,
|
||||||
int width,
|
int width,
|
||||||
|
@ -33,8 +33,8 @@ import gnu.trove.stack.TIntStack;
|
|||||||
import gnu.trove.stack.array.TIntArrayStack;
|
import gnu.trove.stack.array.TIntArrayStack;
|
||||||
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.ShapePart;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Faces;
|
import ru.windcorp.progressia.client.graphics.model.ShapeParts;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Shape;
|
import ru.windcorp.progressia.client.graphics.model.Shape;
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
|
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
|
||||||
@ -144,7 +144,7 @@ public abstract class SpriteTypeface extends Typeface {
|
|||||||
return new Shape(
|
return new Shape(
|
||||||
Usage.STATIC,
|
Usage.STATIC,
|
||||||
getProgram(),
|
getProgram(),
|
||||||
Faces.createRectangle(
|
ShapeParts.createRectangle(
|
||||||
getProgram(),
|
getProgram(),
|
||||||
getTexture(c),
|
getTexture(c),
|
||||||
Colors.WHITE,
|
Colors.WHITE,
|
||||||
@ -167,7 +167,7 @@ public abstract class SpriteTypeface extends Typeface {
|
|||||||
private final Renderable unitLine = new Shape(
|
private final Renderable unitLine = new Shape(
|
||||||
Usage.STATIC,
|
Usage.STATIC,
|
||||||
getProgram(),
|
getProgram(),
|
||||||
Faces.createRectangle(
|
ShapeParts.createRectangle(
|
||||||
getProgram(),
|
getProgram(),
|
||||||
null,
|
null,
|
||||||
Vectors.UNIT_4,
|
Vectors.UNIT_4,
|
||||||
@ -257,7 +257,7 @@ public abstract class SpriteTypeface extends Typeface {
|
|||||||
|
|
||||||
private class SDWorkspace extends SpriteTypeface.Workspace {
|
private class SDWorkspace extends SpriteTypeface.Workspace {
|
||||||
|
|
||||||
private final Collection<Face> faces = new ArrayList<>();
|
private final Collection<ShapePart> faces = new ArrayList<>();
|
||||||
|
|
||||||
private final Vec3 origin = new Vec3();
|
private final Vec3 origin = new Vec3();
|
||||||
private final Vec3 width = new Vec3();
|
private final Vec3 width = new Vec3();
|
||||||
@ -298,7 +298,7 @@ public abstract class SpriteTypeface extends Typeface {
|
|||||||
workspace.height.sub(workspace.origin);
|
workspace.height.sub(workspace.origin);
|
||||||
|
|
||||||
workspace.faces.add(
|
workspace.faces.add(
|
||||||
Faces.createRectangle(
|
ShapeParts.createRectangle(
|
||||||
getProgram(),
|
getProgram(),
|
||||||
texture,
|
texture,
|
||||||
color,
|
color,
|
||||||
@ -314,7 +314,7 @@ public abstract class SpriteTypeface extends Typeface {
|
|||||||
return new Shape(
|
return new Shape(
|
||||||
Usage.STATIC,
|
Usage.STATIC,
|
||||||
getProgram(),
|
getProgram(),
|
||||||
workspace.faces.toArray(new Face[workspace.faces.size()])
|
workspace.faces.toArray(new ShapePart[workspace.faces.size()])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,155 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ru.windcorp.progressia.client.graphics.gui;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
|
import com.google.common.eventbus.Subscribe;
|
||||||
|
|
||||||
|
import ru.windcorp.progressia.client.graphics.font.Font;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.event.ButtonEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.event.EnableEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.event.FocusEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.event.HoverEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||||
|
|
||||||
|
public abstract class BasicButton extends Component {
|
||||||
|
|
||||||
|
private final Label label;
|
||||||
|
|
||||||
|
private boolean isPressed = false;
|
||||||
|
private final Collection<Consumer<BasicButton>> actions = Collections.synchronizedCollection(new ArrayList<>());
|
||||||
|
|
||||||
|
public BasicButton(String name, Label label) {
|
||||||
|
super(name);
|
||||||
|
this.label = label;
|
||||||
|
|
||||||
|
setLayout(new LayoutAlign(10));
|
||||||
|
addChild(this.label);
|
||||||
|
|
||||||
|
setFocusable(true);
|
||||||
|
reassembleAt(ARTrigger.HOVER, ARTrigger.FOCUS, ARTrigger.ENABLE);
|
||||||
|
|
||||||
|
// Click triggers
|
||||||
|
addListener(KeyEvent.class, e -> {
|
||||||
|
if (e.isRepeat()) {
|
||||||
|
return false;
|
||||||
|
} else if (
|
||||||
|
e.isLeftMouseButton() ||
|
||||||
|
e.getKey() == GLFW.GLFW_KEY_SPACE ||
|
||||||
|
e.getKey() == GLFW.GLFW_KEY_ENTER
|
||||||
|
) {
|
||||||
|
setPressed(e.isPress());
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addListener(new Object() {
|
||||||
|
|
||||||
|
// Release when losing focus
|
||||||
|
@Subscribe
|
||||||
|
public void onFocusChange(FocusEvent e) {
|
||||||
|
if (!e.getNewState()) {
|
||||||
|
setPressed(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release when hover ends
|
||||||
|
@Subscribe
|
||||||
|
public void onHoverEnded(HoverEvent e) {
|
||||||
|
if (!e.isNowHovered()) {
|
||||||
|
setPressed(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release when disabled
|
||||||
|
@Subscribe
|
||||||
|
public void onDisabled(EnableEvent e) {
|
||||||
|
if (!e.getComponent().isEnabled()) {
|
||||||
|
setPressed(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger virtualClick when button is released
|
||||||
|
@Subscribe
|
||||||
|
public void onRelease(ButtonEvent.Release e) {
|
||||||
|
virtualClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public BasicButton(String name, String label, Font labelFont) {
|
||||||
|
this(name, new Label(name + ".Label", labelFont, label));
|
||||||
|
}
|
||||||
|
|
||||||
|
public BasicButton(String name, String label) {
|
||||||
|
this(name, label, new Font());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPressed() {
|
||||||
|
return isPressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void click() {
|
||||||
|
setPressed(true);
|
||||||
|
setPressed(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPressed(boolean isPressed) {
|
||||||
|
if (this.isPressed != isPressed) {
|
||||||
|
this.isPressed = isPressed;
|
||||||
|
|
||||||
|
if (isPressed) {
|
||||||
|
takeFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatchEvent(ButtonEvent.create(this, this.isPressed));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public BasicButton addAction(Consumer<BasicButton> action) {
|
||||||
|
this.actions.add(Objects.requireNonNull(action, "action"));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removeAction(Consumer<BasicButton> action) {
|
||||||
|
return this.actions.remove(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void virtualClick() {
|
||||||
|
this.actions.forEach(action -> {
|
||||||
|
action.accept(this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Label getLabel() {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ru.windcorp.progressia.client.graphics.gui;
|
||||||
|
|
||||||
|
import glm.vec._4.Vec4;
|
||||||
|
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
||||||
|
import ru.windcorp.progressia.client.graphics.font.Font;
|
||||||
|
import ru.windcorp.progressia.client.graphics.Colors;
|
||||||
|
|
||||||
|
public class Button extends BasicButton {
|
||||||
|
|
||||||
|
public Button(String name, String label, Font labelFont) {
|
||||||
|
super(name, label, labelFont);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Button(String name, Label label) {
|
||||||
|
super(name, label);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Button(String name, String label) {
|
||||||
|
this(name, label, new Font());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void assembleSelf(RenderTarget target) {
|
||||||
|
// Border
|
||||||
|
|
||||||
|
Vec4 borderColor;
|
||||||
|
if (isPressed() || isHovered() || isFocused()) {
|
||||||
|
borderColor = Colors.BLUE;
|
||||||
|
} else {
|
||||||
|
borderColor = Colors.LIGHT_GRAY;
|
||||||
|
}
|
||||||
|
target.fill(getX(), getY(), getWidth(), getHeight(), borderColor);
|
||||||
|
|
||||||
|
// Inside area
|
||||||
|
|
||||||
|
if (isPressed()) {
|
||||||
|
// Do nothing
|
||||||
|
} else {
|
||||||
|
Vec4 backgroundColor;
|
||||||
|
if (isHovered() && isEnabled()) {
|
||||||
|
backgroundColor = Colors.HOVER_BLUE;
|
||||||
|
} else {
|
||||||
|
backgroundColor = Colors.WHITE;
|
||||||
|
}
|
||||||
|
target.fill(getX() + 2, getY() + 2, getWidth() - 4, getHeight() - 4, backgroundColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change label font color
|
||||||
|
|
||||||
|
if (isPressed()) {
|
||||||
|
getLabel().setFont(getLabel().getFont().withColor(Colors.WHITE));
|
||||||
|
} else {
|
||||||
|
getLabel().setFont(getLabel().getFont().withColor(Colors.BLACK));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void postAssembleSelf(RenderTarget target) {
|
||||||
|
// Apply disable tint
|
||||||
|
|
||||||
|
if (!isEnabled()) {
|
||||||
|
target.fill(getX(), getY(), getWidth(), getHeight(), Colors.toVector(0x88FFFFFF));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,149 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package ru.windcorp.progressia.client.graphics.gui;
|
||||||
|
|
||||||
|
import glm.vec._2.i.Vec2i;
|
||||||
|
import glm.vec._4.Vec4;
|
||||||
|
import ru.windcorp.progressia.client.graphics.Colors;
|
||||||
|
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
||||||
|
import ru.windcorp.progressia.client.graphics.font.Font;
|
||||||
|
import ru.windcorp.progressia.client.graphics.font.Typefaces;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutHorizontal;
|
||||||
|
|
||||||
|
public class Checkbox extends BasicButton {
|
||||||
|
|
||||||
|
private class Tick extends Component {
|
||||||
|
|
||||||
|
public Tick() {
|
||||||
|
super(Checkbox.this.getName() + ".Tick");
|
||||||
|
|
||||||
|
setPreferredSize(new Vec2i(Typefaces.getDefault().getLineHeight() * 3 / 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void assembleSelf(RenderTarget target) {
|
||||||
|
|
||||||
|
int size = getPreferredSize().x;
|
||||||
|
int x = getX();
|
||||||
|
int y = getY() + (getHeight() - size) / 2;
|
||||||
|
|
||||||
|
// Border
|
||||||
|
|
||||||
|
Vec4 borderColor;
|
||||||
|
if (Checkbox.this.isPressed() || Checkbox.this.isHovered() || Checkbox.this.isFocused()) {
|
||||||
|
borderColor = Colors.BLUE;
|
||||||
|
} else {
|
||||||
|
borderColor = Colors.LIGHT_GRAY;
|
||||||
|
}
|
||||||
|
target.fill(x, y, size, size, borderColor);
|
||||||
|
|
||||||
|
// Inside area
|
||||||
|
|
||||||
|
if (Checkbox.this.isPressed()) {
|
||||||
|
// Do nothing
|
||||||
|
} else {
|
||||||
|
Vec4 backgroundColor;
|
||||||
|
if (Checkbox.this.isHovered() && Checkbox.this.isEnabled()) {
|
||||||
|
backgroundColor = Colors.HOVER_BLUE;
|
||||||
|
} else {
|
||||||
|
backgroundColor = Colors.WHITE;
|
||||||
|
}
|
||||||
|
target.fill(x + 2, y + 2, size - 4, size - 4, backgroundColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Tick"
|
||||||
|
|
||||||
|
if (Checkbox.this.isChecked()) {
|
||||||
|
target.fill(x + 4, y + 4, size - 8, size - 8, Colors.BLUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checked;
|
||||||
|
|
||||||
|
public Checkbox(String name, String label, Font labelFont, boolean check) {
|
||||||
|
super(name, label, labelFont);
|
||||||
|
this.checked = check;
|
||||||
|
|
||||||
|
assert getChildren().size() == 1 : "Checkbox expects that BasicButton contains exactly one child";
|
||||||
|
Component basicChild = getChild(0);
|
||||||
|
|
||||||
|
Group group = new Group(getName() + ".LabelAndTick", new LayoutHorizontal(0, 10));
|
||||||
|
removeChild(basicChild);
|
||||||
|
setLayout(new LayoutAlign(0, 0.5f, 10));
|
||||||
|
group.setLayoutHint(basicChild.getLayoutHint());
|
||||||
|
group.addChild(new Tick());
|
||||||
|
group.addChild(basicChild);
|
||||||
|
addChild(group);
|
||||||
|
|
||||||
|
addAction(b -> switchState());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Checkbox(String name, String label, Font labelFont) {
|
||||||
|
this(name, label, labelFont, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Checkbox(String name, String label, boolean check) {
|
||||||
|
this(name, label, new Font(), check);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Checkbox(String name, String label) {
|
||||||
|
this(name, label, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void switchState() {
|
||||||
|
setChecked(!isChecked());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the checked
|
||||||
|
*/
|
||||||
|
public boolean isChecked() {
|
||||||
|
return checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param checked the checked to set
|
||||||
|
*/
|
||||||
|
public void setChecked(boolean checked) {
|
||||||
|
this.checked = checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void assembleSelf(RenderTarget target) {
|
||||||
|
// Change label font color
|
||||||
|
|
||||||
|
if (isPressed()) {
|
||||||
|
getLabel().setFont(getLabel().getFont().withColor(Colors.BLUE));
|
||||||
|
} else {
|
||||||
|
getLabel().setFont(getLabel().getFont().withColor(Colors.BLACK));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void postAssembleSelf(RenderTarget target) {
|
||||||
|
// Apply disable tint
|
||||||
|
|
||||||
|
if (!isEnabled()) {
|
||||||
|
target.fill(getX(), getY(), getWidth(), getHeight(), Colors.toVector(0x88FFFFFF));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -19,18 +19,23 @@
|
|||||||
package ru.windcorp.progressia.client.graphics.gui;
|
package ru.windcorp.progressia.client.graphics.gui;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.EnumMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
import com.google.common.eventbus.EventBus;
|
import com.google.common.eventbus.EventBus;
|
||||||
|
import com.google.common.eventbus.Subscribe;
|
||||||
|
|
||||||
import glm.vec._2.i.Vec2i;
|
import glm.vec._2.i.Vec2i;
|
||||||
import ru.windcorp.progressia.client.graphics.backend.InputTracker;
|
import ru.windcorp.progressia.client.graphics.backend.InputTracker;
|
||||||
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
||||||
import ru.windcorp.progressia.client.graphics.gui.event.ChildAddedEvent;
|
import ru.windcorp.progressia.client.graphics.gui.event.ChildAddedEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.gui.event.ChildRemovedEvent;
|
import ru.windcorp.progressia.client.graphics.gui.event.ChildRemovedEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.event.EnableEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.gui.event.FocusEvent;
|
import ru.windcorp.progressia.client.graphics.gui.event.FocusEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.gui.event.HoverEvent;
|
import ru.windcorp.progressia.client.graphics.gui.event.HoverEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.gui.event.ParentChangedEvent;
|
import ru.windcorp.progressia.client.graphics.gui.event.ParentChangedEvent;
|
||||||
@ -61,6 +66,8 @@ public class Component extends Named {
|
|||||||
|
|
||||||
private Object layoutHint = null;
|
private Object layoutHint = null;
|
||||||
private Layout layout = null;
|
private Layout layout = null;
|
||||||
|
|
||||||
|
private boolean isEnabled = true;
|
||||||
|
|
||||||
private boolean isFocusable = false;
|
private boolean isFocusable = false;
|
||||||
private boolean isFocused = false;
|
private boolean isFocused = false;
|
||||||
@ -285,9 +292,30 @@ public class Component extends Named {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether this component is focusable. A component needs to be
|
||||||
|
* focusable to become focused. A component that is focusable may not
|
||||||
|
* necessarily be ready to gain focus (see {@link #canGainFocusNow()}).
|
||||||
|
*
|
||||||
|
* @return {@code true} iff the component is focusable
|
||||||
|
* @see #canGainFocusNow()
|
||||||
|
*/
|
||||||
public boolean isFocusable() {
|
public boolean isFocusable() {
|
||||||
return isFocusable;
|
return isFocusable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether this component can become focused at this moment.
|
||||||
|
* <p>
|
||||||
|
* The implementation of this method in {@link Component} considers the
|
||||||
|
* component a focus candidate if it is both focusable and enabled.
|
||||||
|
*
|
||||||
|
* @return {@code true} iff the component can receive focus
|
||||||
|
* @see #isFocusable()
|
||||||
|
*/
|
||||||
|
public boolean canGainFocusNow() {
|
||||||
|
return isFocusable() && isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
public Component setFocusable(boolean focusable) {
|
public Component setFocusable(boolean focusable) {
|
||||||
this.isFocusable = focusable;
|
this.isFocusable = focusable;
|
||||||
@ -337,7 +365,7 @@ public class Component extends Named {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (component.isFocusable()) {
|
if (component.canGainFocusNow()) {
|
||||||
setFocused(false);
|
setFocused(false);
|
||||||
component.setFocused(true);
|
component.setFocused(true);
|
||||||
return;
|
return;
|
||||||
@ -379,7 +407,7 @@ public class Component extends Named {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (component.isFocusable()) {
|
if (component.canGainFocusNow()) {
|
||||||
setFocused(false);
|
setFocused(false);
|
||||||
component.setFocused(true);
|
component.setFocused(true);
|
||||||
return;
|
return;
|
||||||
@ -432,13 +460,52 @@ public class Component extends Named {
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return isEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables this component. An {@link EnableEvent} is dispatched
|
||||||
|
* if the state changes.
|
||||||
|
*
|
||||||
|
* @param enabled {@code true} to enable the component, {@code false} to
|
||||||
|
* disable the component
|
||||||
|
* @see #setEnabledRecursively(boolean)
|
||||||
|
*/
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
if (this.isEnabled != enabled) {
|
||||||
|
if (isFocused() && isEnabled()) {
|
||||||
|
focusNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEnabled()) {
|
||||||
|
setHovered(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isEnabled = enabled;
|
||||||
|
dispatchEvent(new EnableEvent(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables this component and all of its children recursively.
|
||||||
|
*
|
||||||
|
* @param enabled {@code true} to enable the components, {@code false} to
|
||||||
|
* disable the components
|
||||||
|
* @see #setEnabled(boolean)
|
||||||
|
*/
|
||||||
|
public void setEnabledRecursively(boolean enabled) {
|
||||||
|
setEnabled(enabled);
|
||||||
|
getChildren().forEach(c -> c.setEnabledRecursively(enabled));
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isHovered() {
|
public boolean isHovered() {
|
||||||
return isHovered;
|
return isHovered;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setHovered(boolean isHovered) {
|
protected void setHovered(boolean isHovered) {
|
||||||
if (this.isHovered != isHovered) {
|
if (this.isHovered != isHovered && isEnabled()) {
|
||||||
this.isHovered = isHovered;
|
this.isHovered = isHovered;
|
||||||
|
|
||||||
if (!isHovered && !getChildren().isEmpty()) {
|
if (!isHovered && !getChildren().isEmpty()) {
|
||||||
@ -502,7 +569,7 @@ public class Component extends Named {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void handleInput(Input input) {
|
protected void handleInput(Input input) {
|
||||||
if (inputBus != null) {
|
if (inputBus != null && isEnabled()) {
|
||||||
inputBus.dispatch(input);
|
inputBus.dispatch(input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -598,6 +665,17 @@ public class Component extends Named {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedules the reassembly to occur.
|
||||||
|
* <p>
|
||||||
|
* This method is invoked in root components whenever a
|
||||||
|
* {@linkplain #requestReassembly() reassembly request} is made by one of
|
||||||
|
* its children. When creating the dedicated root component, override this
|
||||||
|
* method to perform any implementation-specific actions that will cause a
|
||||||
|
* reassembly as soon as possible.
|
||||||
|
* <p>
|
||||||
|
* The default implementation of this method does nothing.
|
||||||
|
*/
|
||||||
protected void handleReassemblyRequest() {
|
protected void handleReassemblyRequest() {
|
||||||
// To be overridden
|
// To be overridden
|
||||||
}
|
}
|
||||||
@ -637,6 +715,135 @@ public class Component extends Named {
|
|||||||
protected void assembleChildren(RenderTarget target) {
|
protected void assembleChildren(RenderTarget target) {
|
||||||
getChildren().forEach(child -> child.assemble(target));
|
getChildren().forEach(child -> child.assemble(target));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Automatic Reassembly
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The various kinds of changes that may be used with
|
||||||
|
* {@link Component#reassembleAt(ARTrigger...)}.
|
||||||
|
*/
|
||||||
|
protected static enum ARTrigger {
|
||||||
|
/**
|
||||||
|
* Reassemble the component whenever its hover status changes, e.g.
|
||||||
|
* whenever the pointer enters or leaves its bounds.
|
||||||
|
*/
|
||||||
|
HOVER,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reassemble the component whenever it gains or loses focus.
|
||||||
|
* <p>
|
||||||
|
* <em>Component must be focusable to be able to gain focus.</em> The
|
||||||
|
* component will not be reassembled unless
|
||||||
|
* {@link Component#setFocusable(boolean) setFocusable(true)} has been
|
||||||
|
* invoked.
|
||||||
|
*/
|
||||||
|
FOCUS,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reassemble the component whenever it is enabled or disabled.
|
||||||
|
*/
|
||||||
|
ENABLE
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All trigger objects (event listeners) that are currently registered with
|
||||||
|
* {@link #eventBus}. The field is {@code null} until the first trigger is
|
||||||
|
* installed.
|
||||||
|
*/
|
||||||
|
private Map<ARTrigger, Object> autoReassemblyTriggerObjects = null;
|
||||||
|
|
||||||
|
private Object createTriggerObject(ARTrigger type) {
|
||||||
|
switch (type) {
|
||||||
|
case HOVER:
|
||||||
|
return new Object() {
|
||||||
|
@Subscribe
|
||||||
|
public void onHoverChanged(HoverEvent e) {
|
||||||
|
requestReassembly();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
case FOCUS:
|
||||||
|
return new Object() {
|
||||||
|
@Subscribe
|
||||||
|
public void onFocusChanged(FocusEvent e) {
|
||||||
|
requestReassembly();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
case ENABLE:
|
||||||
|
return new Object() {
|
||||||
|
@Subscribe
|
||||||
|
public void onEnabled(EnableEvent e) {
|
||||||
|
requestReassembly();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
throw new NullPointerException("type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests that {@link #requestReassembly()} is invoked on this component
|
||||||
|
* whenever any of the specified changes occur. Duplicate attempts to
|
||||||
|
* register the same trigger are silently ignored.
|
||||||
|
* <p>
|
||||||
|
* {@code triggers} may be empty, which results in a no-op. It must not be
|
||||||
|
* {@code null}.
|
||||||
|
*
|
||||||
|
* @param triggers the {@linkplain ARTrigger triggers} to
|
||||||
|
* request reassembly with.
|
||||||
|
* @see #disableAutoReassemblyAt(ARTrigger...)
|
||||||
|
*/
|
||||||
|
protected synchronized void reassembleAt(ARTrigger... triggers) {
|
||||||
|
|
||||||
|
Objects.requireNonNull(triggers, "triggers");
|
||||||
|
if (triggers.length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (autoReassemblyTriggerObjects == null) {
|
||||||
|
autoReassemblyTriggerObjects = new EnumMap<>(ARTrigger.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ARTrigger trigger : triggers) {
|
||||||
|
if (!autoReassemblyTriggerObjects.containsKey(trigger)) {
|
||||||
|
Object triggerObject = createTriggerObject(trigger);
|
||||||
|
addListener(trigger);
|
||||||
|
autoReassemblyTriggerObjects.put(trigger, triggerObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests that {@link #requestReassembly()} is no longer invoked on this
|
||||||
|
* component whenever any of the specified changes occur. After a trigger is
|
||||||
|
* removed, it may be reinstalled with
|
||||||
|
* {@link #reassembleAt(ARTrigger...)}. Attempts to remove a
|
||||||
|
* nonexistant trigger are silently ignored.
|
||||||
|
* <p>
|
||||||
|
* {@code triggers} may be empty, which results in a no-op. It must not be
|
||||||
|
* {@code null}.
|
||||||
|
*
|
||||||
|
* @param triggers the {@linkplain ARTrigger triggers} to remove
|
||||||
|
* @see #reassemblyAt(ARTrigger...)
|
||||||
|
*/
|
||||||
|
protected synchronized void disableAutoReassemblyAt(ARTrigger... triggers) {
|
||||||
|
|
||||||
|
Objects.requireNonNull(triggers, "triggers");
|
||||||
|
if (triggers.length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (autoReassemblyTriggerObjects == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (ARTrigger trigger : triggers) {
|
||||||
|
Object triggerObject = autoReassemblyTriggerObjects.remove(trigger);
|
||||||
|
if (triggerObject != null) {
|
||||||
|
removeListener(trigger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// /**
|
// /**
|
||||||
// * Returns a component that displays this component in its center.
|
// * Returns a component that displays this component in its center.
|
||||||
|
@ -16,18 +16,13 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.common.world.tile;
|
package ru.windcorp.progressia.client.graphics.gui;
|
||||||
|
|
||||||
public interface TileReference {
|
public class Group extends Component {
|
||||||
|
|
||||||
TileData get();
|
public Group(String name, Layout layout) {
|
||||||
|
super(name);
|
||||||
int getIndex();
|
setLayout(layout);
|
||||||
|
|
||||||
TileDataStack getStack();
|
|
||||||
|
|
||||||
default boolean isValid() {
|
|
||||||
return get() != null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -82,6 +82,11 @@ public class Label extends Component {
|
|||||||
public Font getFont() {
|
public Font getFont() {
|
||||||
return font;
|
return font;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setFont(Font font) {
|
||||||
|
this.font = font;
|
||||||
|
requestReassembly();
|
||||||
|
}
|
||||||
|
|
||||||
public String getCurrentText() {
|
public String getCurrentText() {
|
||||||
return currentText;
|
return currentText;
|
||||||
@ -96,11 +101,7 @@ public class Label extends Component {
|
|||||||
float startX = getX() + font.getAlign() * (getWidth() - currentSize.x);
|
float startX = getX() + font.getAlign() * (getWidth() - currentSize.x);
|
||||||
|
|
||||||
target.pushTransform(
|
target.pushTransform(
|
||||||
new Mat4().identity().translate(startX, getY(), -1000) // TODO wtf
|
new Mat4().identity().translate(startX, getY(), 0).scale(2)
|
||||||
// is this
|
|
||||||
// magic
|
|
||||||
// <---
|
|
||||||
.scale(2)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
target.addCustomRenderer(font.assemble(currentText, maxWidth));
|
target.addCustomRenderer(font.assemble(currentText, maxWidth));
|
||||||
|
60
src/main/java/ru/windcorp/progressia/client/graphics/gui/Panel.java
Executable file → Normal file
60
src/main/java/ru/windcorp/progressia/client/graphics/gui/Panel.java
Executable file → Normal file
@ -15,14 +15,66 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* 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.graphics.gui;
|
package ru.windcorp.progressia.client.graphics.gui;
|
||||||
|
|
||||||
public class Panel extends Component {
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import glm.vec._4.Vec4;
|
||||||
|
import ru.windcorp.progressia.client.graphics.Colors;
|
||||||
|
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
||||||
|
|
||||||
|
public class Panel extends Group {
|
||||||
|
|
||||||
|
private Vec4 fill;
|
||||||
|
private Vec4 border;
|
||||||
|
|
||||||
|
public Panel(String name, Layout layout, Vec4 fill, Vec4 border) {
|
||||||
|
super(name, layout);
|
||||||
|
|
||||||
|
this.fill = Objects.requireNonNull(fill, "fill");
|
||||||
|
this.border = border;
|
||||||
|
}
|
||||||
|
|
||||||
public Panel(String name, Layout layout) {
|
public Panel(String name, Layout layout) {
|
||||||
super(name);
|
this(name, layout, Colors.WHITE, Colors.LIGHT_GRAY);
|
||||||
setLayout(layout);
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the fill
|
||||||
|
*/
|
||||||
|
public Vec4 getFill() {
|
||||||
|
return fill;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param fill the fill to set
|
||||||
|
*/
|
||||||
|
public void setFill(Vec4 fill) {
|
||||||
|
this.fill = Objects.requireNonNull(fill, "fill");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the border
|
||||||
|
*/
|
||||||
|
public Vec4 getBorder() {
|
||||||
|
return border;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param border the border to set
|
||||||
|
*/
|
||||||
|
public void setBorder(Vec4 border) {
|
||||||
|
this.border = border;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void assembleSelf(RenderTarget target) {
|
||||||
|
if (border == null) {
|
||||||
|
target.fill(getX(), getY(), getWidth(), getHeight(), fill);
|
||||||
|
} else {
|
||||||
|
target.fill(getX(), getY(), getWidth(), getHeight(), border);
|
||||||
|
target.fill(getX() + 2, getY() + 2, getWidth() - 4, getHeight() - 4, fill);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,208 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package ru.windcorp.progressia.client.graphics.gui;
|
||||||
|
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
|
import glm.vec._2.i.Vec2i;
|
||||||
|
import glm.vec._4.Vec4;
|
||||||
|
import ru.windcorp.progressia.client.graphics.Colors;
|
||||||
|
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
||||||
|
import ru.windcorp.progressia.client.graphics.font.Font;
|
||||||
|
import ru.windcorp.progressia.client.graphics.font.Typefaces;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutHorizontal;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||||
|
|
||||||
|
public class RadioButton extends BasicButton {
|
||||||
|
|
||||||
|
private class Tick extends Component {
|
||||||
|
|
||||||
|
public Tick() {
|
||||||
|
super(RadioButton.this.getName() + ".Tick");
|
||||||
|
|
||||||
|
setPreferredSize(new Vec2i(Typefaces.getDefault().getLineHeight() * 3 / 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cross(RenderTarget target, int x, int y, int size, Vec4 color) {
|
||||||
|
target.fill(x + 4, y, size - 8, size, color);
|
||||||
|
target.fill(x + 2, y + 2, size - 4, size - 4, color);
|
||||||
|
target.fill(x, y + 4, size, size - 8, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void assembleSelf(RenderTarget target) {
|
||||||
|
|
||||||
|
int size = getPreferredSize().x;
|
||||||
|
int x = getX();
|
||||||
|
int y = getY() + (getHeight() - size) / 2;
|
||||||
|
|
||||||
|
// Border
|
||||||
|
|
||||||
|
Vec4 borderColor;
|
||||||
|
if (RadioButton.this.isPressed() || RadioButton.this.isHovered() || RadioButton.this.isFocused()) {
|
||||||
|
borderColor = Colors.BLUE;
|
||||||
|
} else {
|
||||||
|
borderColor = Colors.LIGHT_GRAY;
|
||||||
|
}
|
||||||
|
cross(target, x, y, size, borderColor);
|
||||||
|
|
||||||
|
// Inside area
|
||||||
|
|
||||||
|
if (RadioButton.this.isPressed()) {
|
||||||
|
// Do nothing
|
||||||
|
} else {
|
||||||
|
Vec4 backgroundColor;
|
||||||
|
if (RadioButton.this.isHovered() && RadioButton.this.isEnabled()) {
|
||||||
|
backgroundColor = Colors.HOVER_BLUE;
|
||||||
|
} else {
|
||||||
|
backgroundColor = Colors.WHITE;
|
||||||
|
}
|
||||||
|
cross(target, x + 2, y + 2, size - 4, backgroundColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Tick"
|
||||||
|
|
||||||
|
if (RadioButton.this.isChecked()) {
|
||||||
|
cross(target, x + 4, y + 4, size - 8, Colors.BLUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checked;
|
||||||
|
|
||||||
|
private RadioButtonGroup group = null;
|
||||||
|
|
||||||
|
public RadioButton(String name, String label, Font labelFont, boolean check) {
|
||||||
|
super(name, label, labelFont);
|
||||||
|
this.checked = check;
|
||||||
|
|
||||||
|
assert getChildren().size() == 1 : "RadioButton expects that BasicButton contains exactly one child";
|
||||||
|
Component basicChild = getChild(0);
|
||||||
|
|
||||||
|
Group group = new Group(getName() + ".LabelAndTick", new LayoutHorizontal(0, 10));
|
||||||
|
removeChild(basicChild);
|
||||||
|
setLayout(new LayoutAlign(0, 0.5f, 10));
|
||||||
|
group.setLayoutHint(basicChild.getLayoutHint());
|
||||||
|
group.addChild(new Tick());
|
||||||
|
group.addChild(basicChild);
|
||||||
|
addChild(group);
|
||||||
|
|
||||||
|
addListener(KeyEvent.class, e -> {
|
||||||
|
if (e.isRelease())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (e.getKey() == GLFW.GLFW_KEY_LEFT || e.getKey() == GLFW.GLFW_KEY_UP) {
|
||||||
|
if (this.group != null) {
|
||||||
|
this.group.selectPrevious();
|
||||||
|
this.group.getSelected().takeFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else if (e.getKey() == GLFW.GLFW_KEY_RIGHT || e.getKey() == GLFW.GLFW_KEY_DOWN) {
|
||||||
|
if (this.group != null) {
|
||||||
|
this.group.selectNext();
|
||||||
|
this.group.getSelected().takeFocus();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
addAction(b -> setChecked(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public RadioButton(String name, String label, Font labelFont) {
|
||||||
|
this(name, label, labelFont, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RadioButton(String name, String label, boolean check) {
|
||||||
|
this(name, label, new Font(), check);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RadioButton(String name, String label) {
|
||||||
|
this(name, label, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param group the group to set
|
||||||
|
*/
|
||||||
|
public RadioButton setGroup(RadioButtonGroup group) {
|
||||||
|
|
||||||
|
if (this.group != null) {
|
||||||
|
group.selectNext();
|
||||||
|
removeAction(group.listener);
|
||||||
|
group.buttons.remove(this);
|
||||||
|
group.getSelected(); // Clear reference if this was the only button
|
||||||
|
// in the group
|
||||||
|
}
|
||||||
|
|
||||||
|
this.group = group;
|
||||||
|
|
||||||
|
if (this.group != null) {
|
||||||
|
group.buttons.add(this);
|
||||||
|
addAction(group.listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
setChecked(false);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the checked
|
||||||
|
*/
|
||||||
|
public boolean isChecked() {
|
||||||
|
return checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param checked the checked to set
|
||||||
|
*/
|
||||||
|
public void setChecked(boolean checked) {
|
||||||
|
this.checked = checked;
|
||||||
|
|
||||||
|
if (group != null) {
|
||||||
|
group.listener.accept(this); // Failsafe for manual invocations of
|
||||||
|
// setChecked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void assembleSelf(RenderTarget target) {
|
||||||
|
// Change label font color
|
||||||
|
|
||||||
|
if (isPressed()) {
|
||||||
|
getLabel().setFont(getLabel().getFont().withColor(Colors.BLUE));
|
||||||
|
} else {
|
||||||
|
getLabel().setFont(getLabel().getFont().withColor(Colors.BLACK));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void postAssembleSelf(RenderTarget target) {
|
||||||
|
// Apply disable tint
|
||||||
|
|
||||||
|
if (!isEnabled()) {
|
||||||
|
target.fill(getX(), getY(), getWidth(), getHeight(), Colors.toVector(0x88FFFFFF));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package ru.windcorp.progressia.client.graphics.gui;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public class RadioButtonGroup {
|
||||||
|
|
||||||
|
private final Collection<Consumer<RadioButtonGroup>> actions = Collections.synchronizedCollection(new ArrayList<>());
|
||||||
|
final List<RadioButton> buttons = Collections.synchronizedList(new ArrayList<>());
|
||||||
|
|
||||||
|
private RadioButton selected = null;
|
||||||
|
|
||||||
|
Consumer<BasicButton> listener = b -> {
|
||||||
|
if (b instanceof RadioButton && ((RadioButton) b).isChecked() && buttons.contains(b)) {
|
||||||
|
select((RadioButton) b);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public RadioButtonGroup addAction(Consumer<RadioButtonGroup> action) {
|
||||||
|
this.actions.add(Objects.requireNonNull(action, "action"));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removeAction(Consumer<BasicButton> action) {
|
||||||
|
return this.actions.remove(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<RadioButton> getButtons() {
|
||||||
|
return Collections.unmodifiableList(buttons);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized RadioButton getSelected() {
|
||||||
|
if (!buttons.contains(selected)) {
|
||||||
|
selected = null;
|
||||||
|
}
|
||||||
|
return selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void select(RadioButton button) {
|
||||||
|
if (button != null && !buttons.contains(button)) {
|
||||||
|
throw new IllegalArgumentException("Button " + button + " is not in the group");
|
||||||
|
}
|
||||||
|
|
||||||
|
getSelected(); // Clear if invalid
|
||||||
|
|
||||||
|
if (selected == button) {
|
||||||
|
return; // Terminate listener-setter recursion
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selected != null) {
|
||||||
|
selected.setChecked(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
selected = button;
|
||||||
|
|
||||||
|
if (selected != null) {
|
||||||
|
selected.setChecked(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
actions.forEach(action -> action.accept(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void selectNext() {
|
||||||
|
selectNeighbour(+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void selectPrevious() {
|
||||||
|
selectNeighbour(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void selectNeighbour(int direction) {
|
||||||
|
if (getSelected() == null) {
|
||||||
|
if (buttons.isEmpty()) {
|
||||||
|
throw new IllegalStateException("Cannot select neighbour button: group empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
select(buttons.get(0));
|
||||||
|
} else {
|
||||||
|
RadioButton button;
|
||||||
|
int index = buttons.indexOf(selected);
|
||||||
|
|
||||||
|
do {
|
||||||
|
index += direction;
|
||||||
|
|
||||||
|
if (index >= buttons.size()) {
|
||||||
|
index = 0;
|
||||||
|
} else if (index < 0) {
|
||||||
|
index = buttons.size() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
button = buttons.get(index);
|
||||||
|
} while (button != getSelected() && !button.isEnabled());
|
||||||
|
|
||||||
|
select(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ru.windcorp.progressia.client.graphics.gui.event;
|
||||||
|
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.BasicButton;
|
||||||
|
|
||||||
|
public class ButtonEvent extends ComponentEvent {
|
||||||
|
|
||||||
|
public static class Press extends ButtonEvent {
|
||||||
|
public Press(BasicButton button) {
|
||||||
|
super(button, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Release extends ButtonEvent {
|
||||||
|
public Release(BasicButton button) {
|
||||||
|
super(button, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final boolean isPress;
|
||||||
|
|
||||||
|
protected ButtonEvent(BasicButton button, boolean isPress) {
|
||||||
|
super(button);
|
||||||
|
this.isPress = isPress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ButtonEvent create(BasicButton button, boolean isPress) {
|
||||||
|
if (isPress) {
|
||||||
|
return new Press(button);
|
||||||
|
} else {
|
||||||
|
return new Release(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPress() {
|
||||||
|
return isPress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRelease() {
|
||||||
|
return !isPress;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package ru.windcorp.progressia.client.graphics.gui.event;
|
||||||
|
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||||
|
|
||||||
|
public class EnableEvent extends ComponentEvent {
|
||||||
|
|
||||||
|
public EnableEvent(Component component) {
|
||||||
|
super(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ru.windcorp.progressia.client.graphics.gui.layout;
|
||||||
|
|
||||||
|
import static java.lang.Math.max;
|
||||||
|
|
||||||
|
import glm.vec._2.i.Vec2i;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.Layout;
|
||||||
|
|
||||||
|
public class LayoutFill implements Layout {
|
||||||
|
|
||||||
|
private final int margin;
|
||||||
|
|
||||||
|
public LayoutFill(int margin) {
|
||||||
|
this.margin = margin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutFill() {
|
||||||
|
this(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void layout(Component c) {
|
||||||
|
c.getChildren().forEach(child -> {
|
||||||
|
|
||||||
|
int cWidth = c.getWidth() - 2 * margin;
|
||||||
|
int cHeight = c.getHeight() - 2 * margin;
|
||||||
|
|
||||||
|
child.setBounds(
|
||||||
|
c.getX() + margin,
|
||||||
|
c.getY() + margin,
|
||||||
|
cWidth,
|
||||||
|
cHeight
|
||||||
|
);
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Vec2i calculatePreferredSize(Component c) {
|
||||||
|
Vec2i result = new Vec2i(0, 0);
|
||||||
|
|
||||||
|
c.getChildren().stream()
|
||||||
|
.map(child -> child.getPreferredSize())
|
||||||
|
.forEach(size -> {
|
||||||
|
result.x = max(size.x, result.x);
|
||||||
|
result.y = max(size.y, result.y);
|
||||||
|
});
|
||||||
|
|
||||||
|
result.x += 2 * margin;
|
||||||
|
result.y += 2 * margin;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getClass().getSimpleName() + "(" + margin + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -97,16 +97,27 @@ public class LayoutGrid implements Layout {
|
|||||||
void setBounds(int column, int row, Component child, Component parent) {
|
void setBounds(int column, int row, Component child, Component parent) {
|
||||||
if (!isSummed)
|
if (!isSummed)
|
||||||
throw new IllegalStateException("Not summed yet");
|
throw new IllegalStateException("Not summed yet");
|
||||||
|
|
||||||
|
int width, height;
|
||||||
|
|
||||||
|
if (column == columns.length - 1) {
|
||||||
|
width = parent.getWidth() - margin - columns[column];
|
||||||
|
} else {
|
||||||
|
width = columns[column + 1] - columns[column] - gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row == rows.length - 1) {
|
||||||
|
height = parent.getHeight() - margin - rows[row];
|
||||||
|
} else {
|
||||||
|
height = rows[row + 1] - rows[row] - gap;
|
||||||
|
}
|
||||||
|
|
||||||
child.setBounds(
|
child.setBounds(
|
||||||
parent.getX() + columns[column],
|
parent.getX() + columns[column],
|
||||||
parent.getY() + rows[row],
|
parent.getY() + parent.getHeight() - (rows[row] + height),
|
||||||
|
|
||||||
(column != (columns.length - 1) ? (columns[column + 1] - columns[column] - gap)
|
width,
|
||||||
: (parent.getWidth() - margin - columns[column])),
|
height
|
||||||
|
|
||||||
(row != (rows.length - 1) ? (rows[row + 1] - rows[row] - gap)
|
|
||||||
: (parent.getHeight() - margin - rows[row]))
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -132,10 +143,9 @@ public class LayoutGrid implements Layout {
|
|||||||
GridDimensions grid = calculateGrid(c);
|
GridDimensions grid = calculateGrid(c);
|
||||||
grid.sum();
|
grid.sum();
|
||||||
|
|
||||||
int[] coords;
|
|
||||||
for (Component child : c.getChildren()) {
|
for (Component child : c.getChildren()) {
|
||||||
coords = (int[]) child.getLayoutHint();
|
Vec2i coords = (Vec2i) child.getLayoutHint();
|
||||||
grid.setBounds(coords[0], coords[1], child, c);
|
grid.setBounds(coords.x, coords.y, child, c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -149,11 +159,10 @@ public class LayoutGrid implements Layout {
|
|||||||
|
|
||||||
private GridDimensions calculateGrid(Component parent) {
|
private GridDimensions calculateGrid(Component parent) {
|
||||||
GridDimensions result = new GridDimensions();
|
GridDimensions result = new GridDimensions();
|
||||||
int[] coords;
|
|
||||||
|
|
||||||
for (Component child : parent.getChildren()) {
|
for (Component child : parent.getChildren()) {
|
||||||
coords = (int[]) child.getLayoutHint();
|
Vec2i coords = (Vec2i) child.getLayoutHint();
|
||||||
result.add(coords[0], coords[1], child.getPreferredSize());
|
result.add(coords.x, coords.y, child.getPreferredSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package ru.windcorp.progressia.client.graphics.gui.menu;
|
||||||
|
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
|
import glm.vec._2.i.Vec2i;
|
||||||
|
import ru.windcorp.progressia.client.graphics.Colors;
|
||||||
|
import ru.windcorp.progressia.client.graphics.GUI;
|
||||||
|
import ru.windcorp.progressia.client.graphics.font.Font;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.GUILayer;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.Label;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.Layout;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.Panel;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutFill;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
||||||
|
import ru.windcorp.progressia.client.localization.MutableString;
|
||||||
|
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
|
||||||
|
|
||||||
|
public class MenuLayer extends GUILayer {
|
||||||
|
|
||||||
|
private final Component content;
|
||||||
|
private final Component background;
|
||||||
|
|
||||||
|
private final Runnable closeAction = () -> {
|
||||||
|
GUI.removeLayer(this);
|
||||||
|
};
|
||||||
|
|
||||||
|
public MenuLayer(String name, Component content) {
|
||||||
|
super(name, new LayoutFill(0));
|
||||||
|
|
||||||
|
setCursorPolicy(CursorPolicy.REQUIRE);
|
||||||
|
|
||||||
|
this.background = new Panel(name + ".Background", new LayoutAlign(10), Colors.toVector(0x66000000), null);
|
||||||
|
this.content = content;
|
||||||
|
|
||||||
|
background.addChild(content);
|
||||||
|
getRoot().addChild(background);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MenuLayer(String name, Layout contentLayout) {
|
||||||
|
this(name, new Panel(name + ".Content", contentLayout));
|
||||||
|
}
|
||||||
|
|
||||||
|
public MenuLayer(String name) {
|
||||||
|
this(name, new LayoutVertical(20, 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Component getContent() {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Component getBackground() {
|
||||||
|
return background;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addTitle() {
|
||||||
|
String translationKey = "Layer" + getName() + ".Title";
|
||||||
|
MutableString titleText = new MutableStringLocalized(translationKey);
|
||||||
|
Font titleFont = new Font().deriveBold().withColor(Colors.BLACK).withAlign(0.5f);
|
||||||
|
|
||||||
|
Label label = new Label(getName() + ".Title", titleFont, titleText);
|
||||||
|
getContent().addChild(label);
|
||||||
|
|
||||||
|
Panel panel = new Panel(getName() + ".Title.Underscore", null, Colors.BLUE, null);
|
||||||
|
panel.setLayout(new LayoutFill() {
|
||||||
|
@Override
|
||||||
|
public Vec2i calculatePreferredSize(Component c) {
|
||||||
|
return new Vec2i(label.getPreferredSize().x + 40, 4);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
getContent().addChild(panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Runnable getCloseAction() {
|
||||||
|
return closeAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void handleInput(Input input) {
|
||||||
|
|
||||||
|
if (!input.isConsumed()) {
|
||||||
|
InputEvent event = input.getEvent();
|
||||||
|
|
||||||
|
if (event instanceof KeyEvent) {
|
||||||
|
KeyEvent keyEvent = (KeyEvent) event;
|
||||||
|
if (keyEvent.isPress() && keyEvent.getKey() == GLFW.GLFW_KEY_ESCAPE) {
|
||||||
|
getCloseAction().run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super.handleInput(input);
|
||||||
|
input.consume();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -18,23 +18,23 @@
|
|||||||
|
|
||||||
package ru.windcorp.progressia.client.graphics.model;
|
package ru.windcorp.progressia.client.graphics.model;
|
||||||
|
|
||||||
import static ru.windcorp.progressia.common.world.block.BlockFace.*;
|
import static ru.windcorp.progressia.common.world.rels.AbsFace.*;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
|
||||||
import glm.vec._3.Vec3;
|
import glm.vec._3.Vec3;
|
||||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
||||||
|
|
||||||
class BlockFaceVectors {
|
class BlockFaceVectors {
|
||||||
|
|
||||||
private static BlockFaceVectors createInner(BlockFaceVectors outer) {
|
private static BlockFaceVectors createInner(BlockFaceVectors outer) {
|
||||||
ImmutableMap.Builder<BlockFace, Vec3> originBuilder = ImmutableMap.builder();
|
ImmutableMap.Builder<AbsFace, Vec3> originBuilder = ImmutableMap.builder();
|
||||||
|
|
||||||
ImmutableMap.Builder<BlockFace, Vec3> widthBuilder = ImmutableMap.builder();
|
ImmutableMap.Builder<AbsFace, Vec3> widthBuilder = ImmutableMap.builder();
|
||||||
|
|
||||||
ImmutableMap.Builder<BlockFace, Vec3> heightBuilder = ImmutableMap.builder();
|
ImmutableMap.Builder<AbsFace, Vec3> heightBuilder = ImmutableMap.builder();
|
||||||
|
|
||||||
for (BlockFace face : getFaces()) {
|
for (AbsFace face : getFaces()) {
|
||||||
Vec3 width = outer.getWidth(face);
|
Vec3 width = outer.getWidth(face);
|
||||||
Vec3 height = outer.getHeight(face);
|
Vec3 height = outer.getHeight(face);
|
||||||
|
|
||||||
@ -59,36 +59,36 @@ class BlockFaceVectors {
|
|||||||
|
|
||||||
static {
|
static {
|
||||||
OUTER = new BlockFaceVectors(
|
OUTER = new BlockFaceVectors(
|
||||||
ImmutableMap.<BlockFace, Vec3>builder()
|
ImmutableMap.<AbsFace, Vec3>builder()
|
||||||
|
|
||||||
.put(TOP, new Vec3(-0.5f, +0.5f, +0.5f))
|
.put(POS_Z, new Vec3(-0.5f, +0.5f, +0.5f))
|
||||||
.put(BOTTOM, new Vec3(-0.5f, -0.5f, -0.5f))
|
.put(NEG_Z, new Vec3(-0.5f, -0.5f, -0.5f))
|
||||||
.put(NORTH, new Vec3(+0.5f, -0.5f, -0.5f))
|
.put(POS_X, new Vec3(+0.5f, -0.5f, -0.5f))
|
||||||
.put(SOUTH, new Vec3(-0.5f, +0.5f, -0.5f))
|
.put(NEG_X, new Vec3(-0.5f, +0.5f, -0.5f))
|
||||||
.put(WEST, new Vec3(+0.5f, +0.5f, -0.5f))
|
.put(POS_Y, new Vec3(+0.5f, +0.5f, -0.5f))
|
||||||
.put(EAST, new Vec3(-0.5f, -0.5f, -0.5f))
|
.put(NEG_Y, new Vec3(-0.5f, -0.5f, -0.5f))
|
||||||
|
|
||||||
.build(),
|
.build(),
|
||||||
|
|
||||||
ImmutableMap.<BlockFace, Vec3>builder()
|
ImmutableMap.<AbsFace, Vec3>builder()
|
||||||
|
|
||||||
.put(TOP, new Vec3(0, -1, 0))
|
.put(POS_Z, new Vec3(0, -1, 0))
|
||||||
.put(BOTTOM, new Vec3(0, +1, 0))
|
.put(NEG_Z, new Vec3(0, +1, 0))
|
||||||
.put(NORTH, new Vec3(0, +1, 0))
|
.put(POS_X, new Vec3(0, +1, 0))
|
||||||
.put(SOUTH, new Vec3(0, -1, 0))
|
.put(NEG_X, new Vec3(0, -1, 0))
|
||||||
.put(WEST, new Vec3(-1, 0, 0))
|
.put(POS_Y, new Vec3(-1, 0, 0))
|
||||||
.put(EAST, new Vec3(+1, 0, 0))
|
.put(NEG_Y, new Vec3(+1, 0, 0))
|
||||||
|
|
||||||
.build(),
|
.build(),
|
||||||
|
|
||||||
ImmutableMap.<BlockFace, Vec3>builder()
|
ImmutableMap.<AbsFace, Vec3>builder()
|
||||||
|
|
||||||
.put(TOP, new Vec3(+1, 0, 0))
|
.put(POS_Z, new Vec3(+1, 0, 0))
|
||||||
.put(BOTTOM, new Vec3(+1, 0, 0))
|
.put(NEG_Z, new Vec3(+1, 0, 0))
|
||||||
.put(NORTH, new Vec3(0, 0, +1))
|
.put(POS_X, new Vec3(0, 0, +1))
|
||||||
.put(SOUTH, new Vec3(0, 0, +1))
|
.put(NEG_X, new Vec3(0, 0, +1))
|
||||||
.put(WEST, new Vec3(0, 0, +1))
|
.put(POS_Y, new Vec3(0, 0, +1))
|
||||||
.put(EAST, new Vec3(0, 0, +1))
|
.put(NEG_Y, new Vec3(0, 0, +1))
|
||||||
|
|
||||||
.build()
|
.build()
|
||||||
);
|
);
|
||||||
@ -100,29 +100,29 @@ class BlockFaceVectors {
|
|||||||
return inner ? INNER : OUTER;
|
return inner ? INNER : OUTER;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final ImmutableMap<BlockFace, Vec3> origins;
|
private final ImmutableMap<AbsFace, Vec3> origins;
|
||||||
private final ImmutableMap<BlockFace, Vec3> widths;
|
private final ImmutableMap<AbsFace, Vec3> widths;
|
||||||
private final ImmutableMap<BlockFace, Vec3> heights;
|
private final ImmutableMap<AbsFace, Vec3> heights;
|
||||||
|
|
||||||
public BlockFaceVectors(
|
public BlockFaceVectors(
|
||||||
ImmutableMap<BlockFace, Vec3> origins,
|
ImmutableMap<AbsFace, Vec3> origins,
|
||||||
ImmutableMap<BlockFace, Vec3> widths,
|
ImmutableMap<AbsFace, Vec3> widths,
|
||||||
ImmutableMap<BlockFace, Vec3> heights
|
ImmutableMap<AbsFace, Vec3> heights
|
||||||
) {
|
) {
|
||||||
this.origins = origins;
|
this.origins = origins;
|
||||||
this.widths = widths;
|
this.widths = widths;
|
||||||
this.heights = heights;
|
this.heights = heights;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vec3 getOrigin(BlockFace face) {
|
public Vec3 getOrigin(AbsFace face) {
|
||||||
return origins.get(face);
|
return origins.get(face);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vec3 getWidth(BlockFace face) {
|
public Vec3 getWidth(AbsFace face) {
|
||||||
return widths.get(face);
|
return widths.get(face);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vec3 getHeight(BlockFace face) {
|
public Vec3 getHeight(AbsFace face) {
|
||||||
return heights.get(face);
|
return heights.get(face);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,10 +30,10 @@ import ru.windcorp.progressia.client.graphics.backend.VertexBufferObject;
|
|||||||
public class Shape implements Renderable {
|
public class Shape implements Renderable {
|
||||||
|
|
||||||
private final ShapeRenderProgram program;
|
private final ShapeRenderProgram program;
|
||||||
private final Face[] faces;
|
private final ShapePart[] parts;
|
||||||
private final Usage usage;
|
private final Usage usage;
|
||||||
|
|
||||||
private FaceGroup[] groups;
|
private ShapePartGroup[] groups;
|
||||||
|
|
||||||
private ByteBuffer vertices;
|
private ByteBuffer vertices;
|
||||||
private ShortBuffer indices;
|
private ShortBuffer indices;
|
||||||
@ -45,33 +45,33 @@ public class Shape implements Renderable {
|
|||||||
private VertexBufferObject verticesVbo;
|
private VertexBufferObject verticesVbo;
|
||||||
private VertexBufferObject indicesVbo;
|
private VertexBufferObject indicesVbo;
|
||||||
|
|
||||||
public Shape(Usage usage, ShapeRenderProgram program, Face... faces) {
|
public Shape(Usage usage, ShapeRenderProgram program, ShapePart... parts) {
|
||||||
this.program = program;
|
this.program = program;
|
||||||
this.faces = faces;
|
this.parts = parts;
|
||||||
this.usage = usage;
|
this.usage = usage;
|
||||||
|
|
||||||
configureFaces();
|
configureParts();
|
||||||
program.preprocess(this);
|
program.preprocess(this);
|
||||||
|
|
||||||
assembleBuffers();
|
assembleBuffers();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void configureFaces() {
|
private void configureParts() {
|
||||||
for (Face face : faces) {
|
for (ShapePart part : parts) {
|
||||||
face.setShape(this);
|
part.setShape(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assembleBuffers() {
|
private void assembleBuffers() {
|
||||||
// TODO optimize: only update faces that requested it
|
// TODO optimize: only update faces that requested it
|
||||||
|
|
||||||
sortFaces();
|
sortParts();
|
||||||
resizeBuffers();
|
resizeBuffers();
|
||||||
|
|
||||||
for (Face face : faces) {
|
for (ShapePart part : parts) {
|
||||||
assembleVertices(face);
|
assembleVertices(part);
|
||||||
assembleIndices(face);
|
assembleIndices(part);
|
||||||
face.resetUpdateFlags();
|
part.resetUpdateFlags();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.vertices.flip();
|
this.vertices.flip();
|
||||||
@ -85,110 +85,110 @@ public class Shape implements Renderable {
|
|||||||
|
|
||||||
private void resizeBuffers() {
|
private void resizeBuffers() {
|
||||||
int verticesRequired = 0, indicesRequired = 0;
|
int verticesRequired = 0, indicesRequired = 0;
|
||||||
for (Face face : faces) {
|
for (ShapePart part : parts) {
|
||||||
verticesRequired += face.getVertices().remaining();
|
verticesRequired += part.getVertices().remaining();
|
||||||
indicesRequired += face.getIndices().remaining();
|
indicesRequired += part.getIndices().remaining();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.vertices == null || vertices.capacity() < verticesRequired) {
|
if (vertices == null || vertices.capacity() < verticesRequired) {
|
||||||
this.vertices = BufferUtils.createByteBuffer(verticesRequired);
|
this.vertices = BufferUtils.createByteBuffer(verticesRequired);
|
||||||
} else {
|
} else {
|
||||||
this.vertices.position(0).limit(verticesRequired);
|
vertices.position(0).limit(verticesRequired);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.indices == null || this.indices.capacity() < indicesRequired) {
|
if (indices == null || indices.capacity() < indicesRequired) {
|
||||||
this.indices = BufferUtils.createShortBuffer(indicesRequired);
|
this.indices = BufferUtils.createShortBuffer(indicesRequired);
|
||||||
} else {
|
} else {
|
||||||
this.indices.position(0).limit(indicesRequired);
|
indices.position(0).limit(indicesRequired);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assembleVertices(Face face) {
|
private void assembleVertices(ShapePart part) {
|
||||||
face.locationOfVertices = this.vertices.position();
|
part.locationOfVertices = this.vertices.position();
|
||||||
|
|
||||||
insertVertices(face);
|
insertVertices(part);
|
||||||
linkVerticesWith(face);
|
linkVerticesWith(part);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void insertVertices(Face face) {
|
private void insertVertices(ShapePart part) {
|
||||||
ByteBuffer faceVertices = face.getVertices();
|
ByteBuffer partVertices = part.getVertices();
|
||||||
|
|
||||||
faceVertices.mark();
|
partVertices.mark();
|
||||||
this.vertices.put(faceVertices);
|
this.vertices.put(partVertices);
|
||||||
faceVertices.reset();
|
partVertices.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void linkVerticesWith(Face face) {
|
private void linkVerticesWith(ShapePart part) {
|
||||||
int limit = vertices.limit();
|
int limit = vertices.limit();
|
||||||
int position = vertices.position();
|
int position = vertices.position();
|
||||||
|
|
||||||
vertices.limit(position).position(face.getLocationOfVertices());
|
vertices.limit(position).position(part.getLocationOfVertices());
|
||||||
face.vertices = vertices.slice();
|
part.vertices = vertices.slice();
|
||||||
|
|
||||||
vertices.position(position).limit(limit);
|
vertices.position(position).limit(limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assembleIndices(Face face) {
|
private void assembleIndices(ShapePart part) {
|
||||||
short vertexOffset = (short) (face.getLocationOfVertices() / program.getBytesPerVertex());
|
short vertexOffset = (short) (part.getLocationOfVertices() / program.getBytesPerVertex());
|
||||||
|
|
||||||
face.locationOfIndices = indices.position();
|
part.locationOfIndices = indices.position();
|
||||||
|
|
||||||
ShortBuffer faceIndices = face.getIndices();
|
ShortBuffer partIndices = part.getIndices();
|
||||||
|
|
||||||
if (faceIndices == null) {
|
if (partIndices == null) {
|
||||||
for (int i = 0; i < face.getVertexCount(); ++i) {
|
for (int i = 0; i < part.getVertexCount(); ++i) {
|
||||||
this.indices.put((short) (vertexOffset + i));
|
this.indices.put((short) (vertexOffset + i));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (int i = faceIndices.position(); i < faceIndices.limit(); ++i) {
|
for (int i = partIndices.position(); i < partIndices.limit(); ++i) {
|
||||||
short faceIndex = faceIndices.get(i);
|
short partIndex = partIndices.get(i);
|
||||||
faceIndex += vertexOffset;
|
partIndex += vertexOffset;
|
||||||
this.indices.put(faceIndex);
|
this.indices.put(partIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sortFaces() {
|
private void sortParts() {
|
||||||
Arrays.sort(faces);
|
Arrays.sort(parts);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assembleGroups() {
|
private void assembleGroups() {
|
||||||
int unique = countUniqueFaces();
|
int unique = countUniqueParts();
|
||||||
this.groups = new FaceGroup[unique];
|
this.groups = new ShapePartGroup[unique];
|
||||||
|
|
||||||
if (faces.length == 0)
|
if (parts.length == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int previousHandle = faces[0].getSortingIndex();
|
int previousHandle = parts[0].getSortingIndex();
|
||||||
int start = 0;
|
int start = 0;
|
||||||
int groupIndex = 0;
|
int groupIndex = 0;
|
||||||
|
|
||||||
for (int i = 1; i < faces.length; ++i) {
|
for (int i = 1; i < parts.length; ++i) {
|
||||||
if (previousHandle != faces[i].getSortingIndex()) {
|
if (previousHandle != parts[i].getSortingIndex()) {
|
||||||
|
|
||||||
groups[groupIndex] = new FaceGroup(faces, start, i);
|
groups[groupIndex] = new ShapePartGroup(parts, start, i);
|
||||||
start = i;
|
start = i;
|
||||||
groupIndex++;
|
groupIndex++;
|
||||||
|
|
||||||
previousHandle = faces[i].getSortingIndex();
|
previousHandle = parts[i].getSortingIndex();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert groupIndex == groups.length - 1;
|
assert groupIndex == groups.length - 1;
|
||||||
groups[groupIndex] = new FaceGroup(faces, start, faces.length);
|
groups[groupIndex] = new ShapePartGroup(parts, start, parts.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int countUniqueFaces() {
|
private int countUniqueParts() {
|
||||||
if (faces.length == 0)
|
if (parts.length == 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
int result = 1;
|
int result = 1;
|
||||||
int previousHandle = faces[0].getSortingIndex();
|
int previousHandle = parts[0].getSortingIndex();
|
||||||
|
|
||||||
for (int i = 1; i < faces.length; ++i) {
|
for (int i = 1; i < parts.length; ++i) {
|
||||||
if (previousHandle != faces[i].getSortingIndex()) {
|
if (previousHandle != parts[i].getSortingIndex()) {
|
||||||
result++;
|
result++;
|
||||||
previousHandle = faces[i].getSortingIndex();
|
previousHandle = parts[i].getSortingIndex();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,11 +238,11 @@ public class Shape implements Renderable {
|
|||||||
return program;
|
return program;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Face[] getFaces() {
|
public ShapePart[] getParts() {
|
||||||
return faces;
|
return parts;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FaceGroup[] getGroups() {
|
public ShapePartGroup[] getGroups() {
|
||||||
return groups;
|
return groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ import java.util.Objects;
|
|||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||||
|
|
||||||
public class Face implements Comparable<Face> {
|
public class ShapePart implements Comparable<ShapePart> {
|
||||||
|
|
||||||
private static final ShortBuffer GENERATE_SUCCESSIVE_LATER = null;
|
private static final ShortBuffer GENERATE_SUCCESSIVE_LATER = null;
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ public class Face implements Comparable<Face> {
|
|||||||
private ShortBuffer userIndices;
|
private ShortBuffer userIndices;
|
||||||
private boolean userIndicesUpdated = true;
|
private boolean userIndicesUpdated = true;
|
||||||
|
|
||||||
public Face(
|
public ShapePart(
|
||||||
Texture texture,
|
Texture texture,
|
||||||
ByteBuffer vertices,
|
ByteBuffer vertices,
|
||||||
ShortBuffer indices
|
ShortBuffer indices
|
||||||
@ -50,7 +50,7 @@ public class Face implements Comparable<Face> {
|
|||||||
setIndices(indices);
|
setIndices(indices);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Face(
|
public ShapePart(
|
||||||
Texture texture,
|
Texture texture,
|
||||||
ByteBuffer vertices
|
ByteBuffer vertices
|
||||||
) {
|
) {
|
||||||
@ -155,7 +155,7 @@ public class Face implements Comparable<Face> {
|
|||||||
return vertices;
|
return vertices;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Face setVertices(ByteBuffer vertices) {
|
public ShapePart setVertices(ByteBuffer vertices) {
|
||||||
this.vertices = Objects.requireNonNull(vertices, "vertices");
|
this.vertices = Objects.requireNonNull(vertices, "vertices");
|
||||||
markForVertexUpdate();
|
markForVertexUpdate();
|
||||||
return this;
|
return this;
|
||||||
@ -202,7 +202,7 @@ public class Face implements Comparable<Face> {
|
|||||||
return userIndices.remaining();
|
return userIndices.remaining();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Face setIndices(ShortBuffer indices) {
|
public ShapePart setIndices(ShortBuffer indices) {
|
||||||
if (indices == null) {
|
if (indices == null) {
|
||||||
indices = GENERATE_SUCCESSIVE_LATER;
|
indices = GENERATE_SUCCESSIVE_LATER;
|
||||||
}
|
}
|
||||||
@ -245,7 +245,7 @@ public class Face implements Comparable<Face> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(Face o) {
|
public int compareTo(ShapePart o) {
|
||||||
return Integer.compare(getSortingIndex(), o.getSortingIndex());
|
return Integer.compare(getSortingIndex(), o.getSortingIndex());
|
||||||
}
|
}
|
||||||
|
|
@ -21,13 +21,13 @@ package ru.windcorp.progressia.client.graphics.model;
|
|||||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||||
import ru.windcorp.progressia.client.graphics.texture.TexturePrimitive;
|
import ru.windcorp.progressia.client.graphics.texture.TexturePrimitive;
|
||||||
|
|
||||||
public class FaceGroup {
|
public class ShapePartGroup {
|
||||||
|
|
||||||
private final TexturePrimitive texture;
|
private final TexturePrimitive texture;
|
||||||
private final int indexCount;
|
private final int indexCount;
|
||||||
private final int byteOffsetOfIndices;
|
private final int byteOffsetOfIndices;
|
||||||
|
|
||||||
FaceGroup(Face[] faces, int start, int end) {
|
ShapePartGroup(ShapePart[] faces, int start, int end) {
|
||||||
|
|
||||||
Texture t = faces[start].getTexture();
|
Texture t = faces[start].getTexture();
|
||||||
this.texture = t == null ? null : t.getSprite().getPrimitive();
|
this.texture = t == null ? null : t.getSprite().getPrimitive();
|
||||||
@ -36,7 +36,7 @@ public class FaceGroup {
|
|||||||
int indexCount = 0;
|
int indexCount = 0;
|
||||||
|
|
||||||
for (int i = start; i < end; ++i) {
|
for (int i = start; i < end; ++i) {
|
||||||
Face face = faces[i];
|
ShapePart face = faces[i];
|
||||||
|
|
||||||
assert this.texture == null
|
assert this.texture == null
|
||||||
? (face.getTexture() == null)
|
? (face.getTexture() == null)
|
@ -25,14 +25,14 @@ import glm.vec._3.Vec3;
|
|||||||
import glm.vec._4.Vec4;
|
import glm.vec._4.Vec4;
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram.VertexBuilder;
|
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram.VertexBuilder;
|
||||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
||||||
|
|
||||||
public class Faces {
|
public class ShapeParts {
|
||||||
|
|
||||||
private Faces() {
|
private ShapeParts() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Face createRectangle(
|
public static ShapePart createRectangle(
|
||||||
ShapeRenderProgram program,
|
ShapeRenderProgram program,
|
||||||
Texture texture,
|
Texture texture,
|
||||||
Vec4 colorMultiplier,
|
Vec4 colorMultiplier,
|
||||||
@ -82,19 +82,19 @@ public class Faces {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return new Face(
|
return new ShapePart(
|
||||||
texture,
|
texture,
|
||||||
builder.assemble(),
|
builder.assemble(),
|
||||||
buffer
|
buffer
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Face createBlockFace(
|
public static ShapePart createBlockFace(
|
||||||
ShapeRenderProgram program,
|
ShapeRenderProgram program,
|
||||||
Texture texture,
|
Texture texture,
|
||||||
Vec4 colorMultiplier,
|
Vec4 colorMultiplier,
|
||||||
Vec3 blockCenter,
|
Vec3 blockCenter,
|
||||||
BlockFace face,
|
AbsFace face,
|
||||||
boolean inner
|
boolean inner
|
||||||
) {
|
) {
|
||||||
BlockFaceVectors vectors = BlockFaceVectors.get(inner);
|
BlockFaceVectors vectors = BlockFaceVectors.get(inner);
|
@ -116,7 +116,7 @@ public class ShapeRenderProgram extends Program {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
enableAttributes();
|
enableAttributes();
|
||||||
for (FaceGroup group : shape.getGroups()) {
|
for (ShapePartGroup group : shape.getGroups()) {
|
||||||
renderFaceGroup(group);
|
renderFaceGroup(group);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@ -182,7 +182,7 @@ public class ShapeRenderProgram extends Program {
|
|||||||
indices.bind(BindTarget.ELEMENT_ARRAY);
|
indices.bind(BindTarget.ELEMENT_ARRAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void renderFaceGroup(FaceGroup group) {
|
protected void renderFaceGroup(ShapePartGroup group) {
|
||||||
TexturePrimitive texture = group.getTexture();
|
TexturePrimitive texture = group.getTexture();
|
||||||
|
|
||||||
if (texture != null) {
|
if (texture != null) {
|
||||||
@ -206,12 +206,12 @@ public class ShapeRenderProgram extends Program {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void preprocess(Shape shape) {
|
public void preprocess(Shape shape) {
|
||||||
for (Face face : shape.getFaces()) {
|
for (ShapePart face : shape.getParts()) {
|
||||||
applySprites(face);
|
applySprites(face);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applySprites(Face face) {
|
private void applySprites(ShapePart face) {
|
||||||
if (face.getTexture() == null)
|
if (face.getTexture() == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ import glm.vec._3.Vec3;
|
|||||||
import glm.vec._4.Vec4;
|
import glm.vec._4.Vec4;
|
||||||
import ru.windcorp.progressia.client.graphics.backend.Usage;
|
import ru.windcorp.progressia.client.graphics.backend.Usage;
|
||||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
||||||
|
|
||||||
public class Shapes {
|
public class Shapes {
|
||||||
|
|
||||||
@ -50,7 +50,7 @@ public class Shapes {
|
|||||||
boolean flip
|
boolean flip
|
||||||
) {
|
) {
|
||||||
|
|
||||||
Face top = Faces.createRectangle(
|
ShapePart top = ShapeParts.createRectangle(
|
||||||
program,
|
program,
|
||||||
topTexture,
|
topTexture,
|
||||||
colorMultiplier,
|
colorMultiplier,
|
||||||
@ -60,7 +60,7 @@ public class Shapes {
|
|||||||
flip
|
flip
|
||||||
);
|
);
|
||||||
|
|
||||||
Face bottom = Faces.createRectangle(
|
ShapePart bottom = ShapeParts.createRectangle(
|
||||||
program,
|
program,
|
||||||
bottomTexture,
|
bottomTexture,
|
||||||
colorMultiplier,
|
colorMultiplier,
|
||||||
@ -70,7 +70,7 @@ public class Shapes {
|
|||||||
flip
|
flip
|
||||||
);
|
);
|
||||||
|
|
||||||
Face north = Faces.createRectangle(
|
ShapePart north = ShapeParts.createRectangle(
|
||||||
program,
|
program,
|
||||||
northTexture,
|
northTexture,
|
||||||
colorMultiplier,
|
colorMultiplier,
|
||||||
@ -80,7 +80,7 @@ public class Shapes {
|
|||||||
flip
|
flip
|
||||||
);
|
);
|
||||||
|
|
||||||
Face south = Faces.createRectangle(
|
ShapePart south = ShapeParts.createRectangle(
|
||||||
program,
|
program,
|
||||||
southTexture,
|
southTexture,
|
||||||
colorMultiplier,
|
colorMultiplier,
|
||||||
@ -90,7 +90,7 @@ public class Shapes {
|
|||||||
flip
|
flip
|
||||||
);
|
);
|
||||||
|
|
||||||
Face east = Faces.createRectangle(
|
ShapePart east = ShapeParts.createRectangle(
|
||||||
program,
|
program,
|
||||||
eastTexture,
|
eastTexture,
|
||||||
colorMultiplier,
|
colorMultiplier,
|
||||||
@ -100,7 +100,7 @@ public class Shapes {
|
|||||||
flip
|
flip
|
||||||
);
|
);
|
||||||
|
|
||||||
Face west = Faces.createRectangle(
|
ShapePart west = ShapeParts.createRectangle(
|
||||||
program,
|
program,
|
||||||
westTexture,
|
westTexture,
|
||||||
colorMultiplier,
|
colorMultiplier,
|
||||||
@ -165,16 +165,16 @@ public class Shapes {
|
|||||||
|
|
||||||
public PppBuilder(
|
public PppBuilder(
|
||||||
ShapeRenderProgram program,
|
ShapeRenderProgram program,
|
||||||
Map<BlockFace, Texture> textureMap
|
Map<AbsFace, Texture> textureMap
|
||||||
) {
|
) {
|
||||||
this(
|
this(
|
||||||
program,
|
program,
|
||||||
textureMap.get(BlockFace.TOP),
|
textureMap.get(AbsFace.POS_Z),
|
||||||
textureMap.get(BlockFace.BOTTOM),
|
textureMap.get(AbsFace.NEG_Z),
|
||||||
textureMap.get(BlockFace.NORTH),
|
textureMap.get(AbsFace.POS_X),
|
||||||
textureMap.get(BlockFace.SOUTH),
|
textureMap.get(AbsFace.NEG_X),
|
||||||
textureMap.get(BlockFace.EAST),
|
textureMap.get(AbsFace.NEG_Y),
|
||||||
textureMap.get(BlockFace.WEST)
|
textureMap.get(AbsFace.POS_Y)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ package ru.windcorp.progressia.client.graphics.texture;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import glm.vec._2.Vec2;
|
import glm.vec._2.Vec2;
|
||||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
||||||
|
|
||||||
public class ComplexTexture {
|
public class ComplexTexture {
|
||||||
|
|
||||||
@ -54,14 +54,14 @@ public class ComplexTexture {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<BlockFace, Texture> getCuboidTextures(
|
public Map<AbsFace, Texture> getCuboidTextures(
|
||||||
int x,
|
int x,
|
||||||
int y,
|
int y,
|
||||||
int width,
|
int width,
|
||||||
int height,
|
int height,
|
||||||
int depth
|
int depth
|
||||||
) {
|
) {
|
||||||
return BlockFace.mapToFaces(
|
return AbsFace.mapToFaces(
|
||||||
get(
|
get(
|
||||||
x + depth + width,
|
x + depth + width,
|
||||||
y + height + depth,
|
y + height + depth,
|
||||||
@ -86,7 +86,7 @@ public class ComplexTexture {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<BlockFace, Texture> getCuboidTextures(
|
public Map<AbsFace, Texture> getCuboidTextures(
|
||||||
int x,
|
int x,
|
||||||
int y,
|
int y,
|
||||||
int size
|
int size
|
||||||
|
@ -29,6 +29,9 @@ import glm.mat._4.Mat4;
|
|||||||
import glm.vec._3.Vec3;
|
import glm.vec._3.Vec3;
|
||||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||||
import ru.windcorp.progressia.client.graphics.world.Camera.Anchor.Mode;
|
import ru.windcorp.progressia.client.graphics.world.Camera.Anchor.Mode;
|
||||||
|
import ru.windcorp.progressia.client.world.entity.NPedModel;
|
||||||
|
import ru.windcorp.progressia.common.util.Matrices;
|
||||||
|
import ru.windcorp.progressia.common.util.Vectors;
|
||||||
|
|
||||||
public class Camera {
|
public class Camera {
|
||||||
|
|
||||||
@ -60,13 +63,13 @@ public class Camera {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void getCameraPosition(Vec3 output);
|
Vec3 getCameraPosition(Vec3 output);
|
||||||
|
|
||||||
void getCameraVelocity(Vec3 output);
|
Vec3 getCameraVelocity(Vec3 output);
|
||||||
|
|
||||||
float getCameraYaw();
|
Vec3 getLookingAt(Vec3 output);
|
||||||
|
|
||||||
float getCameraPitch();
|
Vec3 getUpVector(Vec3 output);
|
||||||
|
|
||||||
Collection<Mode> getCameraModes();
|
Collection<Mode> getCameraModes();
|
||||||
|
|
||||||
@ -84,14 +87,11 @@ public class Camera {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
private final Vec3 lastAnchorPosition = new Vec3();
|
private final Vec3 lastAnchorPosition = new Vec3();
|
||||||
private float lastAnchorYaw;
|
private final Vec3 lastAnchorLookingAt = new Vec3();
|
||||||
private float lastAnchorPitch;
|
private final Vec3 lastAnchorUpVector = new Vec3();
|
||||||
|
|
||||||
private final Mat4 lastCameraMatrix = new Mat4();
|
private final Mat4 lastCameraMatrix = new Mat4();
|
||||||
|
|
||||||
private final Vec3 lastAnchorLookingAt = new Vec3();
|
|
||||||
private final Vec3 lastAnchorUp = new Vec3();
|
|
||||||
|
|
||||||
{
|
{
|
||||||
invalidateCache();
|
invalidateCache();
|
||||||
}
|
}
|
||||||
@ -108,6 +108,9 @@ public class Camera {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
public void apply(WorldRenderHelper helper) {
|
public void apply(WorldRenderHelper helper) {
|
||||||
|
if (NPedModel.flag) {
|
||||||
|
// System.out.println("Camera.apply()");
|
||||||
|
}
|
||||||
applyPerspective(helper);
|
applyPerspective(helper);
|
||||||
rotateCoordinateSystem(helper);
|
rotateCoordinateSystem(helper);
|
||||||
|
|
||||||
@ -149,26 +152,34 @@ public class Camera {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void applyDirection(WorldRenderHelper helper) {
|
private void applyDirection(WorldRenderHelper helper) {
|
||||||
float pitch = anchor.getCameraPitch();
|
anchor.getLookingAt(lastAnchorLookingAt);
|
||||||
float yaw = anchor.getCameraYaw();
|
anchor.getUpVector(lastAnchorUpVector);
|
||||||
|
|
||||||
helper.pushViewTransform()
|
lookAt(helper.pushViewTransform());
|
||||||
.rotateY(-pitch)
|
}
|
||||||
.rotateZ(-yaw);
|
|
||||||
|
|
||||||
this.lastAnchorYaw = yaw;
|
private void lookAt(Mat4 result) {
|
||||||
this.lastAnchorPitch = pitch;
|
Vec3 f = this.lastAnchorLookingAt;
|
||||||
|
Vec3 s = Vectors.grab3();
|
||||||
this.lastAnchorLookingAt.set(
|
Vec3 u = Vectors.grab3();
|
||||||
cos(pitch) * cos(yaw),
|
|
||||||
cos(pitch) * sin(yaw),
|
f.cross(this.lastAnchorUpVector, s);
|
||||||
sin(pitch)
|
s.normalize();
|
||||||
);
|
|
||||||
this.lastAnchorUp.set(
|
s.cross(f, u);
|
||||||
cos(pitch + PI_F / 2) * cos(yaw),
|
|
||||||
cos(pitch + PI_F / 2) * sin(yaw),
|
Mat4 workspace = Matrices.grab4();
|
||||||
sin(pitch + PI_F / 2)
|
workspace.set(
|
||||||
|
+f.x, -s.x, +u.x, 0,
|
||||||
|
+f.y, -s.y, +u.y, 0,
|
||||||
|
+f.z, -s.z, +u.z, 0,
|
||||||
|
0, 0, 0, 1
|
||||||
);
|
);
|
||||||
|
result.mul(workspace);
|
||||||
|
Matrices.release(workspace);
|
||||||
|
|
||||||
|
Vectors.release(s);
|
||||||
|
Vectors.release(u);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyPosition(WorldRenderHelper helper) {
|
private void applyPosition(WorldRenderHelper helper) {
|
||||||
@ -247,8 +258,6 @@ public class Camera {
|
|||||||
|
|
||||||
private void invalidateCache() {
|
private void invalidateCache() {
|
||||||
this.lastAnchorPosition.set(Float.NaN);
|
this.lastAnchorPosition.set(Float.NaN);
|
||||||
this.lastAnchorYaw = Float.NaN;
|
|
||||||
this.lastAnchorPitch = Float.NaN;
|
|
||||||
|
|
||||||
this.lastCameraMatrix.set(
|
this.lastCameraMatrix.set(
|
||||||
Float.NaN,
|
Float.NaN,
|
||||||
@ -270,7 +279,7 @@ public class Camera {
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.lastAnchorLookingAt.set(Float.NaN);
|
this.lastAnchorLookingAt.set(Float.NaN);
|
||||||
this.lastAnchorUp.set(Float.NaN);
|
this.lastAnchorUpVector.set(Float.NaN);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Anchor.Mode getMode() {
|
public Anchor.Mode getMode() {
|
||||||
@ -289,14 +298,6 @@ public class Camera {
|
|||||||
return currentModeIndex;
|
return currentModeIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getLastAnchorYaw() {
|
|
||||||
return lastAnchorYaw;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float getLastAnchorPitch() {
|
|
||||||
return lastAnchorPitch;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Vec3 getLastAnchorPosition() {
|
public Vec3 getLastAnchorPosition() {
|
||||||
return lastAnchorPosition;
|
return lastAnchorPosition;
|
||||||
}
|
}
|
||||||
@ -310,7 +311,7 @@ public class Camera {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Vec3 getLastAnchorUp() {
|
public Vec3 getLastAnchorUp() {
|
||||||
return lastAnchorUp;
|
return lastAnchorUpVector;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -59,24 +59,32 @@ public class EntityAnchor implements Anchor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void getCameraPosition(Vec3 output) {
|
public Vec3 getCameraPosition(Vec3 output) {
|
||||||
|
if (output == null) output = new Vec3();
|
||||||
model.getViewPoint(output);
|
model.getViewPoint(output);
|
||||||
output.add(entity.getPosition());
|
output.add(model.getPosition());
|
||||||
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void getCameraVelocity(Vec3 output) {
|
public Vec3 getCameraVelocity(Vec3 output) {
|
||||||
|
if (output == null) output = new Vec3();
|
||||||
output.set(entity.getVelocity());
|
output.set(entity.getVelocity());
|
||||||
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getCameraYaw() {
|
public Vec3 getLookingAt(Vec3 output) {
|
||||||
return entity.getYaw();
|
if (output == null) output = new Vec3();
|
||||||
|
model.getLookingAt(output);
|
||||||
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getCameraPitch() {
|
public Vec3 getUpVector(Vec3 output) {
|
||||||
return entity.getPitch();
|
if (output == null) output = new Vec3();
|
||||||
|
model.getUpVector(output);
|
||||||
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -41,6 +41,8 @@ import ru.windcorp.progressia.common.Units;
|
|||||||
import ru.windcorp.progressia.common.collision.Collideable;
|
import ru.windcorp.progressia.common.collision.Collideable;
|
||||||
import ru.windcorp.progressia.common.collision.colliders.Collider;
|
import ru.windcorp.progressia.common.collision.colliders.Collider;
|
||||||
import ru.windcorp.progressia.common.util.FloatMathUtil;
|
import ru.windcorp.progressia.common.util.FloatMathUtil;
|
||||||
|
import ru.windcorp.progressia.common.util.Vectors;
|
||||||
|
import ru.windcorp.progressia.common.world.GravityModel;
|
||||||
import ru.windcorp.progressia.common.world.entity.EntityData;
|
import ru.windcorp.progressia.common.world.entity.EntityData;
|
||||||
import ru.windcorp.progressia.test.CollisionModelRenderer;
|
import ru.windcorp.progressia.test.CollisionModelRenderer;
|
||||||
import ru.windcorp.progressia.test.TestPlayerControls;
|
import ru.windcorp.progressia.test.TestPlayerControls;
|
||||||
@ -57,6 +59,8 @@ public class LayerWorld extends Layer {
|
|||||||
super("World");
|
super("World");
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.inputBasedControls = new InputBasedControls(client);
|
this.inputBasedControls = new InputBasedControls(client);
|
||||||
|
|
||||||
|
setCursorPolicy(CursorPolicy.FORBID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -72,6 +76,8 @@ public class LayerWorld extends Layer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doRender() {
|
protected void doRender() {
|
||||||
|
client.getComms().processPackets();
|
||||||
|
|
||||||
Camera camera = client.getCamera();
|
Camera camera = client.getCamera();
|
||||||
if (camera.hasAnchor()) {
|
if (camera.hasAnchor()) {
|
||||||
renderWorld();
|
renderWorld();
|
||||||
@ -188,7 +194,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");
|
||||||
@ -197,16 +203,25 @@ public class LayerWorld extends Layer {
|
|||||||
entity.getVelocity().mul((float) Math.exp(-FRICTION_COEFF / entity.getCollisionMass() * tickLength));
|
entity.getVelocity().mul((float) Math.exp(-FRICTION_COEFF / entity.getCollisionMass() * tickLength));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final float MC_g = Units.get("32 m/s^2");
|
|
||||||
private static final float IRL_g = Units.get("9.8 m/s^2");
|
|
||||||
|
|
||||||
private void tmp_applyGravity(EntityData entity, float tickLength) {
|
private void tmp_applyGravity(EntityData entity, float tickLength) {
|
||||||
|
GravityModel gm = ClientState.getInstance().getWorld().getData().getGravityModel();
|
||||||
|
|
||||||
|
Vec3 upVector = Vectors.grab3();
|
||||||
|
gm.getUp(entity.getPosition(), upVector);
|
||||||
|
entity.changeUpVector(upVector);
|
||||||
|
Vectors.release(upVector);
|
||||||
|
|
||||||
if (ClientState.getInstance().getLocalPlayer().getEntity() == entity && tmp_testControls.isFlying()) {
|
if (ClientState.getInstance().getLocalPlayer().getEntity() == entity && tmp_testControls.isFlying()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final float gravitationalAcceleration = tmp_testControls.useMinecraftGravity() ? MC_g : IRL_g;
|
Vec3 gravitationalAcceleration = Vectors.grab3();
|
||||||
entity.getVelocity().add(0, 0, -gravitationalAcceleration * tickLength);
|
gm.getGravity(entity.getPosition(), gravitationalAcceleration);
|
||||||
|
|
||||||
|
gravitationalAcceleration.mul(tickLength);
|
||||||
|
entity.getVelocity().add(gravitationalAcceleration);
|
||||||
|
|
||||||
|
Vectors.release(gravitationalAcceleration);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -23,13 +23,13 @@ import glm.vec._3.Vec3;
|
|||||||
import glm.vec._3.i.Vec3i;
|
import glm.vec._3.i.Vec3i;
|
||||||
import ru.windcorp.progressia.client.world.WorldRender;
|
import ru.windcorp.progressia.client.world.WorldRender;
|
||||||
import ru.windcorp.progressia.common.world.BlockRay;
|
import ru.windcorp.progressia.common.world.BlockRay;
|
||||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
|
||||||
import ru.windcorp.progressia.common.world.entity.EntityData;
|
import ru.windcorp.progressia.common.world.entity.EntityData;
|
||||||
|
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
||||||
|
|
||||||
public class Selection {
|
public class Selection {
|
||||||
|
|
||||||
private final Vec3i block = new Vec3i();
|
private final Vec3i block = new Vec3i();
|
||||||
private BlockFace surface = null;
|
private AbsFace surface = null;
|
||||||
private final Vec2 pointOnSurface = new Vec2(0.5f, 0.5f);
|
private final Vec2 pointOnSurface = new Vec2(0.5f, 0.5f);
|
||||||
private final Vec3 point = new Vec3();
|
private final Vec3 point = new Vec3();
|
||||||
|
|
||||||
@ -38,10 +38,9 @@ public class Selection {
|
|||||||
private BlockRay ray = new BlockRay();
|
private BlockRay ray = new BlockRay();
|
||||||
|
|
||||||
public void update(WorldRender world, EntityData player) {
|
public void update(WorldRender world, EntityData player) {
|
||||||
Vec3 direction = new Vec3();
|
|
||||||
Vec3 start = new Vec3();
|
Vec3 start = new Vec3();
|
||||||
|
Vec3 direction = player.getLookingAt();
|
||||||
player.getLookingAtVector(direction);
|
|
||||||
world.getEntityRenderable(player).getViewPoint(start);
|
world.getEntityRenderable(player).getViewPoint(start);
|
||||||
start.add(player.getPosition());
|
start.add(player.getPosition());
|
||||||
|
|
||||||
@ -71,7 +70,7 @@ public class Selection {
|
|||||||
return exists ? point : null;
|
return exists ? point : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlockFace getSurface() {
|
public AbsFace getSurface() {
|
||||||
return exists ? surface : null;
|
return exists ? surface : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ import glm.vec._4.Vec4;
|
|||||||
import ru.windcorp.progressia.client.graphics.backend.VertexBufferObject;
|
import ru.windcorp.progressia.client.graphics.backend.VertexBufferObject;
|
||||||
import ru.windcorp.progressia.client.graphics.backend.shaders.attributes.*;
|
import ru.windcorp.progressia.client.graphics.backend.shaders.attributes.*;
|
||||||
import ru.windcorp.progressia.client.graphics.backend.shaders.uniforms.*;
|
import ru.windcorp.progressia.client.graphics.backend.shaders.uniforms.*;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Face;
|
import ru.windcorp.progressia.client.graphics.model.ShapePart;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Shape;
|
import ru.windcorp.progressia.client.graphics.model.Shape;
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
|
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
|
||||||
@ -138,12 +138,12 @@ public class WorldRenderProgram extends ShapeRenderProgram {
|
|||||||
public void preprocess(Shape shape) {
|
public void preprocess(Shape shape) {
|
||||||
super.preprocess(shape);
|
super.preprocess(shape);
|
||||||
|
|
||||||
for (Face face : shape.getFaces()) {
|
for (ShapePart face : shape.getParts()) {
|
||||||
computeNormals(face);
|
computeNormals(face);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void computeNormals(Face face) {
|
private void computeNormals(ShapePart face) {
|
||||||
Vec3 a = Vectors.grab3();
|
Vec3 a = Vectors.grab3();
|
||||||
Vec3 b = Vectors.grab3();
|
Vec3 b = Vectors.grab3();
|
||||||
Vec3 c = Vectors.grab3();
|
Vec3 c = Vectors.grab3();
|
||||||
@ -183,7 +183,7 @@ public class WorldRenderProgram extends ShapeRenderProgram {
|
|||||||
normal.normalize();
|
normal.normalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadVertexPosition(Face face, int index, Vec3 result) {
|
private void loadVertexPosition(ShapePart face, int index, Vec3 result) {
|
||||||
ByteBuffer vertices = face.getVertices();
|
ByteBuffer vertices = face.getVertices();
|
||||||
int offset = vertices.position() + index * getBytesPerVertex();
|
int offset = vertices.position() + index * getBytesPerVertex();
|
||||||
|
|
||||||
@ -194,7 +194,7 @@ public class WorldRenderProgram extends ShapeRenderProgram {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveVertexNormal(Face face, int index, Vec3 normal) {
|
private void saveVertexNormal(ShapePart face, int index, Vec3 normal) {
|
||||||
ByteBuffer vertices = face.getVertices();
|
ByteBuffer vertices = face.getVertices();
|
||||||
int offset = vertices.position() + index * getBytesPerVertex() + (3 * Float.BYTES +
|
int offset = vertices.position() + index * getBytesPerVertex() + (3 * Float.BYTES +
|
||||||
4 * Float.BYTES +
|
4 * Float.BYTES +
|
||||||
|
@ -18,57 +18,52 @@
|
|||||||
|
|
||||||
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.TileRenderReference;
|
||||||
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.DefaultChunkData;
|
||||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
import ru.windcorp.progressia.common.world.TileDataReference;
|
||||||
import ru.windcorp.progressia.common.world.generic.GenericChunk;
|
import ru.windcorp.progressia.common.world.TileDataStack;
|
||||||
import ru.windcorp.progressia.common.world.tile.TileData;
|
import ru.windcorp.progressia.common.world.generic.ChunkGenericRO;
|
||||||
import ru.windcorp.progressia.common.world.tile.TileDataStack;
|
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
||||||
|
import ru.windcorp.progressia.common.world.rels.BlockFace;
|
||||||
|
import ru.windcorp.progressia.common.world.rels.RelFace;
|
||||||
|
|
||||||
public class ChunkRender
|
public class ChunkRender
|
||||||
implements GenericChunk<ChunkRender, BlockRender, TileRender, TileRenderStack> {
|
implements ChunkGenericRO<BlockRender, TileRender, TileRenderStack, TileRenderReference, ChunkRender> {
|
||||||
|
|
||||||
private final WorldRender world;
|
private final WorldRender world;
|
||||||
private final ChunkData data;
|
private final DefaultChunkData 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<>());
|
||||||
|
|
||||||
public ChunkRender(WorldRender world, ChunkData data) {
|
public ChunkRender(WorldRender world, DefaultChunkData data) {
|
||||||
this.world = world;
|
this.world = world;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
|
this.model = new ChunkRenderModel(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Vec3i getPosition() {
|
public Vec3i getPosition() {
|
||||||
return getData().getPosition();
|
return getData().getPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AbsFace getUp() {
|
||||||
|
return getData().getUp();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BlockRender getBlock(Vec3i posInChunk) {
|
public BlockRender getBlock(Vec3i posInChunk) {
|
||||||
@ -98,177 +93,45 @@ public class ChunkRender
|
|||||||
return world;
|
return world;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChunkData getData() {
|
public DefaultChunkData getData() {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void markForUpdate() {
|
public void markForUpdate() {
|
||||||
getWorld().markChunkForUpdate(getPosition());
|
getWorld().markChunkForUpdate(getPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
private class TileRenderReferenceImpl implements TileRenderReference {
|
||||||
|
private final TileDataReference parent;
|
||||||
|
|
||||||
|
public TileRenderReferenceImpl(TileDataReference parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TileRender get() {
|
||||||
|
return TileRenderRegistry.getInstance().get(parent.get().getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIndex() {
|
||||||
|
return parent.getIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TileRenderStack getStack() {
|
||||||
|
return TileRenderStackImpl.this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final TileDataStack parent;
|
private final TileDataStack parent;
|
||||||
|
|
||||||
@ -287,9 +150,24 @@ public class ChunkRender
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BlockFace getFace() {
|
public RelFace getFace() {
|
||||||
return parent.getFace();
|
return parent.getFace();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TileRenderReference getReference(int index) {
|
||||||
|
return new TileRenderReferenceImpl(parent.getReference(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIndexByTag(int tag) {
|
||||||
|
return parent.getIndexByTag(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTagByIndex(int index) {
|
||||||
|
return parent.getTagByIndex(index);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TileRender get(int index) {
|
public TileRender get(int index) {
|
||||||
|
@ -0,0 +1,167 @@
|
|||||||
|
/*
|
||||||
|
* 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.DefaultChunkData;
|
||||||
|
import ru.windcorp.progressia.common.world.generic.GenericChunks;
|
||||||
|
import ru.windcorp.progressia.common.world.rels.AxisRotations;
|
||||||
|
import ru.windcorp.progressia.common.world.rels.RelFace;
|
||||||
|
|
||||||
|
public class ChunkRenderModel implements Renderable {
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
float offset = DefaultChunkData.BLOCKS_PER_CHUNK / 2 - 0.5f;
|
||||||
|
|
||||||
|
renderer.pushTransform().translate(
|
||||||
|
chunk.getX() * DefaultChunkData.BLOCKS_PER_CHUNK,
|
||||||
|
chunk.getY() * DefaultChunkData.BLOCKS_PER_CHUNK,
|
||||||
|
chunk.getZ() * DefaultChunkData.BLOCKS_PER_CHUNK
|
||||||
|
).translate(offset, offset, offset)
|
||||||
|
.mul(AxisRotations.getResolutionMatrix4(chunk.getUp()))
|
||||||
|
.translate(-offset, -offset, -offset);
|
||||||
|
|
||||||
|
model.render(renderer);
|
||||||
|
|
||||||
|
renderer.popTransform();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update() {
|
||||||
|
setupCROs();
|
||||||
|
|
||||||
|
StaticModel.Builder sink = StaticModel.builder();
|
||||||
|
|
||||||
|
optimizers.forEach(ChunkRenderOptimizer::startRender);
|
||||||
|
|
||||||
|
GenericChunks.forEachBiC(relBlockInChunk -> {
|
||||||
|
processBlockAndTiles(relBlockInChunk, 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 relBlockInChunk, Builder sink) {
|
||||||
|
processBlock(relBlockInChunk, sink);
|
||||||
|
|
||||||
|
for (RelFace face : RelFace.getFaces()) {
|
||||||
|
processTileStack(relBlockInChunk, face, sink);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processBlock(Vec3i relBlockInChunk, Builder sink) {
|
||||||
|
BlockRender block = chunk.getBlockRel(relBlockInChunk);
|
||||||
|
|
||||||
|
if (block instanceof BlockRenderNone) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (block.needsOwnRenderable()) {
|
||||||
|
sink.addPart(
|
||||||
|
block.createRenderable(chunk.getData(), relBlockInChunk),
|
||||||
|
new Mat4().identity().translate(relBlockInChunk.x, relBlockInChunk.y, relBlockInChunk.z)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
processBlockWithCROs(block, relBlockInChunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processBlockWithCROs(BlockRender block, Vec3i relBlockInChunk) {
|
||||||
|
for (ChunkRenderOptimizer optimizer : optimizers) {
|
||||||
|
optimizer.addBlock(block, relBlockInChunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processTileStack(Vec3i relBlockInChunk, RelFace face, Builder sink) {
|
||||||
|
TileRenderStack trs = chunk.getTilesOrNullRel(relBlockInChunk, face);
|
||||||
|
|
||||||
|
if (trs == null || trs.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
trs.forEach(tile -> processTile(tile, relBlockInChunk, face, sink));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processTile(TileRender tile, Vec3i relBlockInChunk, RelFace face, Builder sink) {
|
||||||
|
if (tile instanceof TileRenderNone) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tile.needsOwnRenderable()) {
|
||||||
|
sink.addPart(
|
||||||
|
tile.createRenderable(chunk.getData(), relBlockInChunk, face),
|
||||||
|
new Mat4().identity().translate(relBlockInChunk.x, relBlockInChunk.y, relBlockInChunk.z)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
processTileWithCROs(tile, relBlockInChunk, face);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processTileWithCROs(TileRender tile, Vec3i relBlockInChunk, RelFace face) {
|
||||||
|
for (ChunkRenderOptimizer optimizer : optimizers) {
|
||||||
|
optimizer.addTile(tile, relBlockInChunk, face);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -18,8 +18,15 @@
|
|||||||
|
|
||||||
package ru.windcorp.progressia.client.world;
|
package ru.windcorp.progressia.client.world;
|
||||||
|
|
||||||
import ru.windcorp.progressia.common.world.ChunkData;
|
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.DefaultChunkData;
|
||||||
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.rels.AbsFace;
|
||||||
|
import ru.windcorp.progressia.common.world.rels.RelFace;
|
||||||
|
import ru.windcorp.progressia.common.world.tile.TileData;
|
||||||
|
|
||||||
class ChunkUpdateListener implements ChunkDataListener {
|
class ChunkUpdateListener implements ChunkDataListener {
|
||||||
|
|
||||||
@ -30,8 +37,64 @@ class ChunkUpdateListener implements ChunkDataListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onChunkChanged(ChunkData chunk) {
|
public void onChunkChanged(DefaultChunkData chunk) {
|
||||||
world.getChunk(chunk).markForUpdate();
|
world.getChunk(chunk).markForUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChunkLoaded(DefaultChunkData chunk) {
|
||||||
|
Vec3i cursor = new Vec3i();
|
||||||
|
for (AbsFace face : AbsFace.getFaces()) {
|
||||||
|
cursor.set(chunk.getX(), chunk.getY(), chunk.getZ());
|
||||||
|
cursor.add(face.getVector());
|
||||||
|
world.markChunkForUpdate(cursor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChunkBlockChanged(DefaultChunkData chunk, Vec3i blockInChunk, BlockData previous, BlockData current) {
|
||||||
|
onLocationChanged(chunk, blockInChunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChunkTilesChanged(
|
||||||
|
DefaultChunkData chunk,
|
||||||
|
Vec3i blockInChunk,
|
||||||
|
RelFace face,
|
||||||
|
TileData tile,
|
||||||
|
boolean wasAdded
|
||||||
|
) {
|
||||||
|
onLocationChanged(chunk, blockInChunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onLocationChanged(DefaultChunkData chunk, Vec3i blockInChunk) {
|
||||||
|
Vec3i chunkPos = Vectors.grab3i().set(chunk.getX(), chunk.getY(), chunk.getZ());
|
||||||
|
|
||||||
|
checkCoordinate(blockInChunk, chunkPos, VectorUtil.Axis.X);
|
||||||
|
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 == DefaultChunkData.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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -34,57 +34,58 @@ import ru.windcorp.progressia.client.world.block.BlockRender;
|
|||||||
import ru.windcorp.progressia.client.world.entity.EntityRenderRegistry;
|
import ru.windcorp.progressia.client.world.entity.EntityRenderRegistry;
|
||||||
import ru.windcorp.progressia.client.world.entity.EntityRenderable;
|
import ru.windcorp.progressia.client.world.entity.EntityRenderable;
|
||||||
import ru.windcorp.progressia.client.world.tile.TileRender;
|
import ru.windcorp.progressia.client.world.tile.TileRender;
|
||||||
|
import ru.windcorp.progressia.client.world.tile.TileRenderReference;
|
||||||
import ru.windcorp.progressia.client.world.tile.TileRenderStack;
|
import ru.windcorp.progressia.client.world.tile.TileRenderStack;
|
||||||
import ru.windcorp.progressia.common.util.VectorUtil;
|
import ru.windcorp.progressia.common.util.VectorUtil;
|
||||||
import ru.windcorp.progressia.common.util.Vectors;
|
import ru.windcorp.progressia.common.util.Vectors;
|
||||||
import ru.windcorp.progressia.common.world.ChunkData;
|
import ru.windcorp.progressia.common.world.DefaultChunkData;
|
||||||
import ru.windcorp.progressia.common.world.ChunkDataListeners;
|
import ru.windcorp.progressia.common.world.ChunkDataListeners;
|
||||||
import ru.windcorp.progressia.common.world.WorldData;
|
import ru.windcorp.progressia.common.world.DefaultWorldData;
|
||||||
import ru.windcorp.progressia.common.world.WorldDataListener;
|
import ru.windcorp.progressia.common.world.WorldDataListener;
|
||||||
import ru.windcorp.progressia.common.world.entity.EntityData;
|
import ru.windcorp.progressia.common.world.entity.EntityData;
|
||||||
import ru.windcorp.progressia.common.world.generic.ChunkSet;
|
import ru.windcorp.progressia.common.world.generic.ChunkSet;
|
||||||
import ru.windcorp.progressia.common.world.generic.ChunkSets;
|
import ru.windcorp.progressia.common.world.generic.ChunkSets;
|
||||||
import ru.windcorp.progressia.common.world.generic.GenericWorld;
|
import ru.windcorp.progressia.common.world.generic.WorldGenericRO;
|
||||||
|
|
||||||
public class WorldRender
|
public class WorldRender
|
||||||
implements GenericWorld<BlockRender, TileRender, TileRenderStack, ChunkRender, EntityRenderable> {
|
implements WorldGenericRO<BlockRender, TileRender, TileRenderStack, TileRenderReference, ChunkRender, EntityRenderable> {
|
||||||
|
|
||||||
private final WorldData data;
|
private final DefaultWorldData data;
|
||||||
private final Client client;
|
private final Client client;
|
||||||
|
|
||||||
private final Map<ChunkData, ChunkRender> chunks = Collections.synchronizedMap(new HashMap<>());
|
private final Map<DefaultChunkData, ChunkRender> chunks = Collections.synchronizedMap(new HashMap<>());
|
||||||
private final Map<EntityData, EntityRenderable> entityModels = Collections.synchronizedMap(new WeakHashMap<>());
|
private final Map<EntityData, EntityRenderable> entityModels = Collections.synchronizedMap(new WeakHashMap<>());
|
||||||
|
|
||||||
private final ChunkSet chunksToUpdate = ChunkSets.newSyncHashSet();
|
private final ChunkSet chunksToUpdate = ChunkSets.newSyncHashSet();
|
||||||
|
|
||||||
public WorldRender(WorldData data, Client client) {
|
public WorldRender(DefaultWorldData data, Client client) {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
|
||||||
data.addListener(ChunkDataListeners.createAdder(new ChunkUpdateListener(this)));
|
data.addListener(ChunkDataListeners.createAdder(new ChunkUpdateListener(this)));
|
||||||
data.addListener(new WorldDataListener() {
|
data.addListener(new WorldDataListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onChunkLoaded(WorldData world, ChunkData chunk) {
|
public void onChunkLoaded(DefaultWorldData world, DefaultChunkData chunk) {
|
||||||
addChunk(chunk);
|
addChunk(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void beforeChunkUnloaded(WorldData world, ChunkData chunk) {
|
public void beforeChunkUnloaded(DefaultWorldData world, DefaultChunkData chunk) {
|
||||||
removeChunk(chunk);
|
removeChunk(chunk);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addChunk(ChunkData chunk) {
|
protected void addChunk(DefaultChunkData chunk) {
|
||||||
chunks.put(chunk, new ChunkRender(WorldRender.this, chunk));
|
chunks.put(chunk, new ChunkRender(WorldRender.this, chunk));
|
||||||
markChunkForUpdate(chunk.getPosition());
|
markChunkForUpdate(chunk.getPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void removeChunk(ChunkData chunk) {
|
protected void removeChunk(DefaultChunkData chunk) {
|
||||||
chunks.remove(chunk);
|
chunks.remove(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
public WorldData getData() {
|
public DefaultWorldData getData() {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +93,7 @@ public class WorldRender
|
|||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChunkRender getChunk(ChunkData chunkData) {
|
public ChunkRender getChunk(DefaultChunkData chunkData) {
|
||||||
return chunks.get(chunkData);
|
return chunks.get(chunkData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,6 +111,13 @@ public class WorldRender
|
|||||||
public Collection<EntityRenderable> getEntities() {
|
public Collection<EntityRenderable> getEntities() {
|
||||||
return entityModels.values();
|
return entityModels.values();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EntityRenderable getEntity(long entityId) {
|
||||||
|
EntityData entityData = getData().getEntity(entityId);
|
||||||
|
if (entityData == null) return null;
|
||||||
|
return getEntityRenderable(entityData);
|
||||||
|
}
|
||||||
|
|
||||||
public void render(ShapeRenderHelper renderer) {
|
public void render(ShapeRenderHelper renderer) {
|
||||||
updateChunks();
|
updateChunks();
|
||||||
|
@ -18,24 +18,19 @@
|
|||||||
|
|
||||||
package ru.windcorp.progressia.client.world.block;
|
package ru.windcorp.progressia.client.world.block;
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
|
||||||
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
|
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
|
||||||
import ru.windcorp.progressia.common.world.generic.GenericBlock;
|
import ru.windcorp.progressia.common.world.DefaultChunkData;
|
||||||
|
import ru.windcorp.progressia.common.world.generic.BlockGeneric;
|
||||||
|
import glm.vec._3.i.Vec3i;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||||
|
|
||||||
public abstract class BlockRender extends Namespaced implements GenericBlock {
|
public abstract class BlockRender extends Namespaced implements BlockGeneric {
|
||||||
|
|
||||||
public BlockRender(String id) {
|
public BlockRender(String id) {
|
||||||
super(id);
|
super(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void render(ShapeRenderHelper renderer) {
|
public Renderable createRenderable(DefaultChunkData chunk, Vec3i relBlockInChunk) {
|
||||||
throw new UnsupportedOperationException(
|
|
||||||
"BlockRender.render() not implemented in " + this
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Renderable createRenderable() {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.DefaultChunkData;
|
||||||
|
|
||||||
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(DefaultChunkData chunk, Vec3i blockInChunk) {
|
||||||
return EmptyModel.getInstance();
|
return EmptyModel.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
package ru.windcorp.progressia.client.world.block;
|
package ru.windcorp.progressia.client.world.block;
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
import ru.windcorp.progressia.common.world.rels.RelFace;
|
||||||
|
|
||||||
public class BlockRenderOpaqueCube extends BlockRenderTexturedCube {
|
public class BlockRenderOpaqueCube extends BlockRenderTexturedCube {
|
||||||
|
|
||||||
@ -29,8 +29,8 @@ public class BlockRenderOpaqueCube extends BlockRenderTexturedCube {
|
|||||||
Texture bottomTexture,
|
Texture bottomTexture,
|
||||||
Texture northTexture,
|
Texture northTexture,
|
||||||
Texture southTexture,
|
Texture southTexture,
|
||||||
Texture eastTexture,
|
Texture westTexture,
|
||||||
Texture westTexture
|
Texture eastTexture
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
id,
|
id,
|
||||||
@ -38,8 +38,8 @@ public class BlockRenderOpaqueCube extends BlockRenderTexturedCube {
|
|||||||
bottomTexture,
|
bottomTexture,
|
||||||
northTexture,
|
northTexture,
|
||||||
southTexture,
|
southTexture,
|
||||||
eastTexture,
|
westTexture,
|
||||||
westTexture
|
eastTexture
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,9 +54,21 @@ public class BlockRenderOpaqueCube extends BlockRenderTexturedCube {
|
|||||||
texture
|
texture
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BlockRenderOpaqueCube(String id, Texture topTexture, Texture bottomTexture, Texture sideTexture) {
|
||||||
|
this(
|
||||||
|
id,
|
||||||
|
topTexture,
|
||||||
|
bottomTexture,
|
||||||
|
sideTexture,
|
||||||
|
sideTexture,
|
||||||
|
sideTexture,
|
||||||
|
sideTexture
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isOpaque(BlockFace face) {
|
public boolean isOpaque(RelFace face) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,9 +77,4 @@ public class BlockRenderOpaqueCube extends BlockRenderTexturedCube {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean needsOwnRenderable() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,23 +18,33 @@
|
|||||||
|
|
||||||
package ru.windcorp.progressia.client.world.block;
|
package ru.windcorp.progressia.client.world.block;
|
||||||
|
|
||||||
import static ru.windcorp.progressia.common.world.block.BlockFace.*;
|
import static ru.windcorp.progressia.common.world.rels.AbsFace.*;
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
import java.util.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.ShapePart;
|
||||||
|
import ru.windcorp.progressia.client.graphics.model.ShapeParts;
|
||||||
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.world.block.BlockFace;
|
import ru.windcorp.progressia.common.util.Vectors;
|
||||||
|
import ru.windcorp.progressia.common.world.DefaultChunkData;
|
||||||
|
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
||||||
|
import ru.windcorp.progressia.common.world.rels.RelFace;
|
||||||
|
|
||||||
public abstract class BlockRenderTexturedCube
|
public abstract class BlockRenderTexturedCube
|
||||||
extends BlockRender
|
extends BlockRender
|
||||||
implements OpaqueCube {
|
implements BlockOptimizedSurface {
|
||||||
|
|
||||||
private final Map<BlockFace, Texture> textures = new HashMap<>();
|
private final Map<RelFace, Texture> textures;
|
||||||
|
|
||||||
public BlockRenderTexturedCube(
|
public BlockRenderTexturedCube(
|
||||||
String id,
|
String id,
|
||||||
@ -42,35 +52,68 @@ public abstract class BlockRenderTexturedCube
|
|||||||
Texture bottomTexture,
|
Texture bottomTexture,
|
||||||
Texture northTexture,
|
Texture northTexture,
|
||||||
Texture southTexture,
|
Texture southTexture,
|
||||||
Texture eastTexture,
|
Texture westTexture,
|
||||||
Texture westTexture
|
Texture eastTexture
|
||||||
) {
|
) {
|
||||||
super(id);
|
super(id);
|
||||||
|
this.textures = RelFace.mapToFaces(topTexture, bottomTexture, northTexture, southTexture, westTexture, eastTexture);
|
||||||
|
}
|
||||||
|
|
||||||
textures.put(TOP, topTexture);
|
public Texture getTexture(RelFace blockFace) {
|
||||||
textures.put(BOTTOM, bottomTexture);
|
return textures.get(blockFace);
|
||||||
textures.put(NORTH, northTexture);
|
}
|
||||||
textures.put(SOUTH, southTexture);
|
|
||||||
textures.put(EAST, eastTexture);
|
public Vec4 getColorMultiplier(RelFace blockFace) {
|
||||||
textures.put(WEST, westTexture);
|
return Colors.WHITE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Texture getTexture(BlockFace face) {
|
public final void getShapeParts(
|
||||||
return textures.get(face);
|
DefaultChunkData chunk, Vec3i blockInChunk, RelFace blockFace,
|
||||||
|
boolean inner,
|
||||||
|
Consumer<ShapePart> output,
|
||||||
|
Vec3 offset
|
||||||
|
) {
|
||||||
|
output.accept(createFace(chunk, blockInChunk, blockFace, inner, offset));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private ShapePart createFace(
|
||||||
public Renderable createRenderable() {
|
DefaultChunkData chunk, Vec3i blockInChunk, RelFace blockFace,
|
||||||
return new Shapes.PppBuilder(
|
boolean inner,
|
||||||
|
Vec3 offset
|
||||||
|
) {
|
||||||
|
return ShapeParts.createBlockFace(
|
||||||
WorldRenderProgram.getDefault(),
|
WorldRenderProgram.getDefault(),
|
||||||
getTexture(TOP),
|
getTexture(blockFace),
|
||||||
getTexture(BOTTOM),
|
getColorMultiplier(blockFace),
|
||||||
getTexture(NORTH),
|
offset,
|
||||||
getTexture(SOUTH),
|
blockFace.resolve(AbsFace.POS_Z),
|
||||||
getTexture(EAST),
|
inner
|
||||||
getTexture(WEST)
|
);
|
||||||
).create();
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk) {
|
||||||
|
boolean opaque = isBlockOpaque();
|
||||||
|
|
||||||
|
ShapePart[] faces = new ShapePart[BLOCK_FACE_COUNT + (opaque ? BLOCK_FACE_COUNT : 0)];
|
||||||
|
|
||||||
|
for (int i = 0; i < BLOCK_FACE_COUNT; ++i) {
|
||||||
|
faces[i] = createFace(chunk, blockInChunk, RelFace.getFaces().get(i), false, Vectors.ZERO_3);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!opaque) {
|
||||||
|
for (int i = 0; i < BLOCK_FACE_COUNT; ++i) {
|
||||||
|
faces[i + BLOCK_FACE_COUNT] = createFace(chunk, blockInChunk, RelFace.getFaces().get(i), true, Vectors.ZERO_3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Shape(Usage.STATIC, WorldRenderProgram.getDefault(), faces);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean needsOwnRenderable() {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
package ru.windcorp.progressia.client.world.block;
|
package ru.windcorp.progressia.client.world.block;
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
import ru.windcorp.progressia.common.world.rels.RelFace;
|
||||||
|
|
||||||
public class BlockRenderTransparentCube extends BlockRenderTexturedCube {
|
public class BlockRenderTransparentCube extends BlockRenderTexturedCube {
|
||||||
|
|
||||||
@ -29,8 +29,8 @@ public class BlockRenderTransparentCube extends BlockRenderTexturedCube {
|
|||||||
Texture bottomTexture,
|
Texture bottomTexture,
|
||||||
Texture northTexture,
|
Texture northTexture,
|
||||||
Texture southTexture,
|
Texture southTexture,
|
||||||
Texture eastTexture,
|
Texture westTexture,
|
||||||
Texture westTexture
|
Texture eastTexture
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
id,
|
id,
|
||||||
@ -38,8 +38,8 @@ public class BlockRenderTransparentCube extends BlockRenderTexturedCube {
|
|||||||
bottomTexture,
|
bottomTexture,
|
||||||
northTexture,
|
northTexture,
|
||||||
southTexture,
|
southTexture,
|
||||||
eastTexture,
|
westTexture,
|
||||||
westTexture
|
eastTexture
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,9 +54,21 @@ public class BlockRenderTransparentCube extends BlockRenderTexturedCube {
|
|||||||
texture
|
texture
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BlockRenderTransparentCube(String id, Texture topTexture, Texture bottomTexture, Texture sideTexture) {
|
||||||
|
this(
|
||||||
|
id,
|
||||||
|
topTexture,
|
||||||
|
bottomTexture,
|
||||||
|
sideTexture,
|
||||||
|
sideTexture,
|
||||||
|
sideTexture,
|
||||||
|
sideTexture
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isOpaque(BlockFace face) {
|
public boolean isOpaque(RelFace face) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,9 +77,4 @@ public class BlockRenderTransparentCube extends BlockRenderTexturedCube {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean needsOwnRenderable() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,31 +15,123 @@
|
|||||||
* 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.world.block.BlockFace;
|
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
|
||||||
|
import ru.windcorp.progressia.common.world.rels.RelFace;
|
||||||
|
|
||||||
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.
|
||||||
|
* <p>
|
||||||
|
* As with everything related to rendering chunks, CROs are interacted with
|
||||||
|
* using the relative local chunk coordinate system. In this coordinate system,
|
||||||
|
* the coordinates are the chunk coordinates relativized using the chunks's up
|
||||||
|
* direction. In simpler terms, coordinates are {@code [0; BLOCKS_PER_CHUNK)}
|
||||||
|
* and Z is always up.
|
||||||
|
* <h3>CRO lifecycle</h3>
|
||||||
|
* A CRO instance is created by {@link ChunkRenderOptimizerRegistry}. It may
|
||||||
|
* then be used to work on multiple chunks sequentially. Each chunk is processed
|
||||||
|
* 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, RelFace)} are invoked for each block and
|
||||||
|
* tile that this CRO should optimize. {@code addTile} specifies tiles in order
|
||||||
|
* of ascension within a tile stack.</li>
|
||||||
|
* <li>{@link #endRender()} is invoked. The CRO may perform any pending
|
||||||
|
* 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 relBlockInChunk the relative position of the block
|
||||||
|
*/
|
||||||
|
public abstract void addBlock(BlockRender block, Vec3i relBlockInChunk);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 relBlockInChunk the relative position of the block that the tile
|
||||||
|
* belongs to
|
||||||
|
* @param blockFace the face that the tile belongs to
|
||||||
|
*/
|
||||||
|
public abstract void addTile(TileRender tile, Vec3i relBlockInChunk, RelFace blockFace);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests that the CRO assembles and outputs its model. This method may
|
||||||
|
* only be invoked after {@link #startRender()}.
|
||||||
|
*
|
||||||
|
* @return the assembled {@link Renderable}.
|
||||||
|
*/
|
||||||
|
public abstract Renderable endRender();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,283 +0,0 @@
|
|||||||
/*
|
|
||||||
* Progressia
|
|
||||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ru.windcorp.progressia.client.world.cro;
|
|
||||||
|
|
||||||
import static ru.windcorp.progressia.common.world.ChunkData.BLOCKS_PER_CHUNK;
|
|
||||||
import static ru.windcorp.progressia.common.world.block.BlockFace.BLOCK_FACE_COUNT;
|
|
||||||
import static ru.windcorp.progressia.common.world.generic.GenericTileStack.TILES_PER_FACE;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
import glm.vec._3.Vec3;
|
|
||||||
import glm.vec._3.i.Vec3i;
|
|
||||||
import ru.windcorp.progressia.client.graphics.Colors;
|
|
||||||
import ru.windcorp.progressia.client.graphics.backend.Usage;
|
|
||||||
import ru.windcorp.progressia.client.graphics.model.Face;
|
|
||||||
import ru.windcorp.progressia.client.graphics.model.Faces;
|
|
||||||
import ru.windcorp.progressia.client.graphics.model.Shape;
|
|
||||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
|
||||||
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
|
|
||||||
import ru.windcorp.progressia.client.world.ChunkRender;
|
|
||||||
import ru.windcorp.progressia.client.world.block.BlockRender;
|
|
||||||
import ru.windcorp.progressia.client.world.tile.TileRender;
|
|
||||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
|
||||||
|
|
||||||
public class ChunkRenderOptimizerCube extends ChunkRenderOptimizer {
|
|
||||||
|
|
||||||
public static interface OpaqueCube {
|
|
||||||
public Texture getTexture(BlockFace face);
|
|
||||||
|
|
||||||
public boolean isOpaque(BlockFace face);
|
|
||||||
|
|
||||||
public boolean isBlockOpaque();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static interface OpaqueSurface {
|
|
||||||
public Texture getTexture(BlockFace face);
|
|
||||||
|
|
||||||
public boolean isOpaque(BlockFace face);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class BlockInfo {
|
|
||||||
OpaqueCube block;
|
|
||||||
final FaceInfo[] faces = new FaceInfo[BLOCK_FACE_COUNT];
|
|
||||||
|
|
||||||
{
|
|
||||||
for (int i = 0; i < faces.length; ++i) {
|
|
||||||
faces[i] = new FaceInfo();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class FaceInfo {
|
|
||||||
static final int NO_OPAQUE_TILES = -1;
|
|
||||||
|
|
||||||
int topOpaqueTile = NO_OPAQUE_TILES;
|
|
||||||
final OpaqueSurface[] tiles = new OpaqueSurface[TILES_PER_FACE];
|
|
||||||
int tileCount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final BlockInfo[][][] data = new BlockInfo[BLOCKS_PER_CHUNK][BLOCKS_PER_CHUNK][BLOCKS_PER_CHUNK];
|
|
||||||
|
|
||||||
{
|
|
||||||
for (int x = 0; x < BLOCKS_PER_CHUNK; ++x) {
|
|
||||||
for (int y = 0; y < BLOCKS_PER_CHUNK; ++y) {
|
|
||||||
for (int z = 0; z < BLOCKS_PER_CHUNK; ++z) {
|
|
||||||
data[x][y][z] = new BlockInfo();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void startRender(ChunkRender chunk) {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void processBlock(BlockRender block, Vec3i pos) {
|
|
||||||
if (!(block instanceof OpaqueCube))
|
|
||||||
return;
|
|
||||||
OpaqueCube opaqueCube = (OpaqueCube) block;
|
|
||||||
addBlock(pos, opaqueCube);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void processTile(TileRender tile, Vec3i pos, BlockFace face) {
|
|
||||||
if (!(tile instanceof OpaqueSurface))
|
|
||||||
return;
|
|
||||||
OpaqueSurface opaqueTile = (OpaqueSurface) tile;
|
|
||||||
addTile(pos, face, opaqueTile);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void addBlock(Vec3i pos, OpaqueCube cube) {
|
|
||||||
getBlock(pos).block = cube;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addTile(Vec3i pos, BlockFace face, OpaqueSurface opaqueTile) {
|
|
||||||
FaceInfo faceInfo = getFace(pos, face);
|
|
||||||
|
|
||||||
int index = faceInfo.tileCount;
|
|
||||||
faceInfo.tileCount++;
|
|
||||||
|
|
||||||
faceInfo.tiles[index] = opaqueTile;
|
|
||||||
|
|
||||||
if (opaqueTile.isOpaque(face)) {
|
|
||||||
faceInfo.topOpaqueTile = index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected BlockInfo getBlock(Vec3i cursor) {
|
|
||||||
return data[cursor.x][cursor.y][cursor.z];
|
|
||||||
}
|
|
||||||
|
|
||||||
protected FaceInfo getFace(Vec3i cursor, BlockFace face) {
|
|
||||||
return getBlock(cursor).faces[face.getId()];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Shape endRender() {
|
|
||||||
|
|
||||||
Collection<Face> shapeFaces = new ArrayList<>(
|
|
||||||
BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * 3
|
|
||||||
);
|
|
||||||
|
|
||||||
Vec3i cursor = new Vec3i();
|
|
||||||
|
|
||||||
for (cursor.x = 0; cursor.x < BLOCKS_PER_CHUNK; ++cursor.x) {
|
|
||||||
for (cursor.y = 0; cursor.y < BLOCKS_PER_CHUNK; ++cursor.y) {
|
|
||||||
for (cursor.z = 0; cursor.z < BLOCKS_PER_CHUNK; ++cursor.z) {
|
|
||||||
processInnerFaces(cursor, shapeFaces::add);
|
|
||||||
processOuterFaces(cursor, shapeFaces::add);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Shape(
|
|
||||||
Usage.STATIC,
|
|
||||||
WorldRenderProgram.getDefault(),
|
|
||||||
shapeFaces.toArray(new Face[shapeFaces.size()])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processOuterFaces(
|
|
||||||
Vec3i cursor,
|
|
||||||
Consumer<Face> output
|
|
||||||
) {
|
|
||||||
for (BlockFace face : BlockFace.getFaces()) {
|
|
||||||
if (!shouldRenderOuterFace(cursor, face))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
Vec3 faceOrigin = new Vec3(cursor.x, cursor.y, cursor.z);
|
|
||||||
Vec3 offset = new Vec3(face.getVector().x, face.getVector().y, face.getVector().z).mul(1f / 128);
|
|
||||||
|
|
||||||
FaceInfo info = getFace(cursor, face);
|
|
||||||
|
|
||||||
if (info.topOpaqueTile == FaceInfo.NO_OPAQUE_TILES) {
|
|
||||||
OpaqueCube block = getBlock(cursor).block;
|
|
||||||
|
|
||||||
if (block != null) {
|
|
||||||
addFace(
|
|
||||||
faceOrigin,
|
|
||||||
face,
|
|
||||||
getBlock(cursor).block.getTexture(face),
|
|
||||||
output
|
|
||||||
);
|
|
||||||
|
|
||||||
faceOrigin.add(offset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int startLayer = info.topOpaqueTile;
|
|
||||||
if (startLayer == FaceInfo.NO_OPAQUE_TILES) {
|
|
||||||
startLayer = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int layer = startLayer; layer < info.tileCount; ++layer) {
|
|
||||||
addFace(
|
|
||||||
faceOrigin,
|
|
||||||
face,
|
|
||||||
info.tiles[layer].getTexture(face),
|
|
||||||
output
|
|
||||||
);
|
|
||||||
|
|
||||||
faceOrigin.add(offset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addFace(
|
|
||||||
Vec3 cursor,
|
|
||||||
BlockFace face,
|
|
||||||
Texture texture,
|
|
||||||
Consumer<Face> output
|
|
||||||
) {
|
|
||||||
if (texture == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
output.accept(
|
|
||||||
Faces.createBlockFace(
|
|
||||||
WorldRenderProgram.getDefault(),
|
|
||||||
texture,
|
|
||||||
Colors.WHITE,
|
|
||||||
new Vec3(cursor),
|
|
||||||
face,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean shouldRenderOuterFace(Vec3i cursor, BlockFace face) {
|
|
||||||
cursor.add(face.getVector());
|
|
||||||
try {
|
|
||||||
|
|
||||||
// TODO handle neighboring chunks properly
|
|
||||||
if (!isInBounds(cursor))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
OpaqueCube adjacent = getBlock(cursor).block;
|
|
||||||
|
|
||||||
if (adjacent == null)
|
|
||||||
return true;
|
|
||||||
if (adjacent.isOpaque(face))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
cursor.sub(face.getVector());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processInnerFaces(
|
|
||||||
Vec3i cursor,
|
|
||||||
Consumer<Face> output
|
|
||||||
) {
|
|
||||||
// if (block.isBlockOpaque()) return;
|
|
||||||
//
|
|
||||||
// for (BlockFace face : BlockFace.getFaces()) {
|
|
||||||
//
|
|
||||||
// Texture texture = block.getTexture(face);
|
|
||||||
// if (texture == null) continue;
|
|
||||||
//
|
|
||||||
// output.accept(Faces.createBlockFace(
|
|
||||||
// WorldRenderProgram.getDefault(),
|
|
||||||
// texture,
|
|
||||||
// COLOR_MULTIPLIER,
|
|
||||||
// new Vec3(cursor.x, cursor.y, cursor.z),
|
|
||||||
// face,
|
|
||||||
// true
|
|
||||||
// ));
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isInBounds(Vec3i cursor) {
|
|
||||||
return isInBounds(cursor.x) &&
|
|
||||||
isInBounds(cursor.y) &&
|
|
||||||
isInBounds(cursor.z);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isInBounds(int c) {
|
|
||||||
return c >= 0 && c < BLOCKS_PER_CHUNK;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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.world.cro;
|
||||||
|
|
||||||
|
import ru.windcorp.progressia.common.util.namespaces.NamespacedFactoryRegistry;
|
||||||
|
|
||||||
|
public class ChunkRenderOptimizerRegistry extends NamespacedFactoryRegistry<ChunkRenderOptimizer> {
|
||||||
|
|
||||||
|
private static final ChunkRenderOptimizerRegistry INSTANCE = new ChunkRenderOptimizerRegistry();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the instance
|
||||||
|
*/
|
||||||
|
public static ChunkRenderOptimizerRegistry getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package ru.windcorp.progressia.client.world.cro;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import glm.vec._3.i.Vec3i;
|
||||||
|
import ru.windcorp.progressia.client.graphics.backend.Usage;
|
||||||
|
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||||
|
import ru.windcorp.progressia.client.graphics.model.Shape;
|
||||||
|
import ru.windcorp.progressia.client.graphics.model.ShapePart;
|
||||||
|
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
|
||||||
|
import ru.windcorp.progressia.client.world.block.BlockRender;
|
||||||
|
import ru.windcorp.progressia.client.world.tile.TileRender;
|
||||||
|
import ru.windcorp.progressia.common.world.DefaultChunkData;
|
||||||
|
import ru.windcorp.progressia.common.world.rels.RelFace;
|
||||||
|
|
||||||
|
public class ChunkRenderOptimizerSimple extends ChunkRenderOptimizer {
|
||||||
|
|
||||||
|
public interface BlockOptimizedSimple {
|
||||||
|
|
||||||
|
void getShapeParts(
|
||||||
|
DefaultChunkData chunk,
|
||||||
|
Vec3i relBlockInChunk,
|
||||||
|
Consumer<ShapePart> output
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface TileOptimizedCustom {
|
||||||
|
|
||||||
|
void getShapeParts(
|
||||||
|
DefaultChunkData chunk,
|
||||||
|
Vec3i relBlockInChunk,
|
||||||
|
RelFace blockFace,
|
||||||
|
Consumer<ShapePart> output
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Collection<ShapePart> parts = new ArrayList<>();
|
||||||
|
private final Consumer<ShapePart> partAdder = parts::add;
|
||||||
|
|
||||||
|
public ChunkRenderOptimizerSimple(String id) {
|
||||||
|
super(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startRender() {
|
||||||
|
parts.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addBlock(BlockRender block, Vec3i relBlockInChunk) {
|
||||||
|
if (block instanceof BlockOptimizedSimple) {
|
||||||
|
((BlockOptimizedSimple) block).getShapeParts(chunk.getData(), relBlockInChunk, partAdder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addTile(TileRender tile, Vec3i relBlockInChunk, RelFace blockFace) {
|
||||||
|
if (tile instanceof TileOptimizedCustom) {
|
||||||
|
((TileOptimizedCustom) tile).getShapeParts(chunk.getData(), relBlockInChunk, blockFace, partAdder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Renderable endRender() {
|
||||||
|
|
||||||
|
if (parts.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Shape(
|
||||||
|
Usage.STATIC,
|
||||||
|
WorldRenderProgram.getDefault(),
|
||||||
|
parts.toArray(new ShapePart[parts.size()])
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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.DefaultChunkData.BLOCKS_PER_CHUNK;
|
||||||
|
import static ru.windcorp.progressia.common.world.generic.TileGenericStackRO.TILES_PER_FACE;
|
||||||
|
import static ru.windcorp.progressia.common.world.rels.AbsFace.BLOCK_FACE_COUNT;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
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.ShapePart;
|
||||||
|
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||||
|
import ru.windcorp.progressia.client.graphics.model.Shape;
|
||||||
|
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
|
||||||
|
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.DefaultChunkData;
|
||||||
|
import ru.windcorp.progressia.common.world.generic.GenericChunks;
|
||||||
|
import ru.windcorp.progressia.common.world.rels.RelFace;
|
||||||
|
|
||||||
|
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 shape parts that correspond to this
|
||||||
|
* surface. The coordinates of the face vertices must be in chunk
|
||||||
|
* coordinate system.
|
||||||
|
*
|
||||||
|
* @param chunk the chunk that contains the requested face
|
||||||
|
* @param relBlockInChunk the relative 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 shape parts must
|
||||||
|
* be
|
||||||
|
* given to
|
||||||
|
* @param offset an additional offset that must be applied to
|
||||||
|
* all
|
||||||
|
* vertices
|
||||||
|
*/
|
||||||
|
void getShapeParts(
|
||||||
|
DefaultChunkData chunk,
|
||||||
|
Vec3i relBlockInChunk,
|
||||||
|
RelFace blockFace,
|
||||||
|
boolean inner,
|
||||||
|
Consumer<ShapePart> output,
|
||||||
|
Vec3 offset /* kostyl 156% */
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the opacity of the surface identified by the provided
|
||||||
|
* {@link RelFace}.
|
||||||
|
* Opaque surfaces prevent surfaces behind them from being included in
|
||||||
|
* chunk models.
|
||||||
|
*
|
||||||
|
* @param blockFace the face to query
|
||||||
|
* @return {@code true} iff the surface is opaque.
|
||||||
|
*/
|
||||||
|
boolean isOpaque(RelFace 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 relBlockInChunk) {
|
||||||
|
if (!(block instanceof BlockOptimizedSurface))
|
||||||
|
return;
|
||||||
|
|
||||||
|
BlockOptimizedSurface bos = (BlockOptimizedSurface) block;
|
||||||
|
addBlock(relBlockInChunk, bos);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addTile(TileRender tile, Vec3i relBlockInChunk, RelFace face) {
|
||||||
|
if (!(tile instanceof TileOptimizedSurface))
|
||||||
|
return;
|
||||||
|
|
||||||
|
TileOptimizedSurface tos = (TileOptimizedSurface) tile;
|
||||||
|
addTile(relBlockInChunk, face, tos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addBlock(Vec3i relBlockInChunk, BlockOptimizedSurface block) {
|
||||||
|
getBlock(relBlockInChunk).block = block;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addTile(Vec3i relBlockInChunk, RelFace face, TileOptimizedSurface tile) {
|
||||||
|
FaceInfo faceInfo = getFace(relBlockInChunk, 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 relBlockInChunk) {
|
||||||
|
return data[relBlockInChunk.x][relBlockInChunk.y][relBlockInChunk.z];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected FaceInfo getFace(Vec3i relBlockInChunk, RelFace face) {
|
||||||
|
return getBlock(relBlockInChunk).faces[face.getId()];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Renderable endRender() {
|
||||||
|
Collection<ShapePart> shapeParts = new ArrayList<>(
|
||||||
|
BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * 3
|
||||||
|
);
|
||||||
|
|
||||||
|
Consumer<ShapePart> consumer = shapeParts::add;
|
||||||
|
|
||||||
|
GenericChunks.forEachBiC(relBlockInChunk -> {
|
||||||
|
processInnerFaces(relBlockInChunk, consumer);
|
||||||
|
processOuterFaces(relBlockInChunk, consumer);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (shapeParts.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Shape(
|
||||||
|
Usage.STATIC,
|
||||||
|
WorldRenderProgram.getDefault(),
|
||||||
|
shapeParts.toArray(new ShapePart[shapeParts.size()])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processOuterFaces(
|
||||||
|
Vec3i relBlockInChunk,
|
||||||
|
Consumer<ShapePart> output
|
||||||
|
) {
|
||||||
|
for (RelFace blockFace : RelFace.getFaces()) {
|
||||||
|
processOuterFace(relBlockInChunk, blockFace, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processOuterFace(Vec3i relBlockInChunk, RelFace blockFace, Consumer<ShapePart> output) {
|
||||||
|
if (!shouldRenderOuterFace(relBlockInChunk, blockFace))
|
||||||
|
return;
|
||||||
|
|
||||||
|
FaceInfo info = getFace(relBlockInChunk, blockFace);
|
||||||
|
|
||||||
|
if (info.tileCount == 0 && info.block.block == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Vec3 faceOrigin = new Vec3(relBlockInChunk.x, relBlockInChunk.y, relBlockInChunk.z);
|
||||||
|
Vec3 offset = new Vec3(blockFace.getRelFloatVector()).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.getShapeParts(chunk.getData(), relBlockInChunk, blockFace, false, output, faceOrigin);
|
||||||
|
|
||||||
|
faceOrigin.add(offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processInnerFaces(Vec3i relBlockInChunk, Consumer<ShapePart> output) {
|
||||||
|
for (RelFace blockFace : RelFace.getFaces()) {
|
||||||
|
processInnerFace(relBlockInChunk, blockFace, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processInnerFace(Vec3i relBlockInChunk, RelFace blockFace, Consumer<ShapePart> output) {
|
||||||
|
if (!shouldRenderInnerFace(relBlockInChunk, blockFace))
|
||||||
|
return;
|
||||||
|
|
||||||
|
FaceInfo info = getFace(relBlockInChunk, blockFace);
|
||||||
|
|
||||||
|
if (info.tileCount == 0 && info.block.block == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Vec3 faceOrigin = new Vec3(relBlockInChunk.x, relBlockInChunk.y, relBlockInChunk.z);
|
||||||
|
Vec3 offset = new Vec3(blockFace.getRelFloatVector()).mul(OVERLAY_OFFSET);
|
||||||
|
|
||||||
|
for (
|
||||||
|
int layer = FaceInfo.BLOCK_LAYER;
|
||||||
|
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.getShapeParts(chunk.getData(), relBlockInChunk, blockFace, true, output, faceOrigin);
|
||||||
|
|
||||||
|
faceOrigin.add(offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldRenderOuterFace(Vec3i relBlockInChunk, RelFace face) {
|
||||||
|
relBlockInChunk.add(face.getRelVector());
|
||||||
|
try {
|
||||||
|
return shouldRenderWhenFacing(relBlockInChunk, face);
|
||||||
|
} finally {
|
||||||
|
relBlockInChunk.sub(face.getRelVector());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldRenderInnerFace(Vec3i relBlockInChunk, RelFace face) {
|
||||||
|
return shouldRenderWhenFacing(relBlockInChunk, face);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldRenderWhenFacing(Vec3i relBlockInChunk, RelFace face) {
|
||||||
|
if (GenericChunks.containsBiC(relBlockInChunk)) {
|
||||||
|
return shouldRenderWhenFacingLocal(relBlockInChunk, face);
|
||||||
|
} else {
|
||||||
|
return shouldRenderWhenFacingNeighbor(relBlockInChunk, face);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldRenderWhenFacingLocal(Vec3i relBlockInChunk, RelFace face) {
|
||||||
|
BlockOptimizedSurface block = getBlock(relBlockInChunk).block;
|
||||||
|
|
||||||
|
if (block == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (block.isOpaque(face)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldRenderWhenFacingNeighbor(Vec3i relBlockInLocalChunk, RelFace face) {
|
||||||
|
Vec3i blockInChunk = Vectors.grab3i();
|
||||||
|
chunk.resolve(relBlockInLocalChunk, blockInChunk);
|
||||||
|
Vec3i chunkPos = Vectors.grab3i().set(chunk.getX(), chunk.getY(), chunk.getZ());
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Determine blockInChunk and chunkPos
|
||||||
|
if (blockInChunk.x == -1) {
|
||||||
|
blockInChunk.x = BLOCKS_PER_CHUNK - 1;
|
||||||
|
chunkPos.x -= 1;
|
||||||
|
} else if (blockInChunk.x == BLOCKS_PER_CHUNK) {
|
||||||
|
blockInChunk.x = 0;
|
||||||
|
chunkPos.x += 1;
|
||||||
|
} else if (blockInChunk.y == -1) {
|
||||||
|
blockInChunk.y = BLOCKS_PER_CHUNK - 1;
|
||||||
|
chunkPos.y -= 1;
|
||||||
|
} else if (blockInChunk.y == BLOCKS_PER_CHUNK) {
|
||||||
|
blockInChunk.y = 0;
|
||||||
|
chunkPos.y += 1;
|
||||||
|
} else if (blockInChunk.z == -1) {
|
||||||
|
blockInChunk.z = BLOCKS_PER_CHUNK - 1;
|
||||||
|
chunkPos.z -= 1;
|
||||||
|
} else if (blockInChunk.z == BLOCKS_PER_CHUNK) {
|
||||||
|
blockInChunk.z = 0;
|
||||||
|
chunkPos.z += 1;
|
||||||
|
} else {
|
||||||
|
throw new AssertionError(
|
||||||
|
"Requested incorrent neighbor ("
|
||||||
|
+ relBlockInLocalChunk.x + "; "
|
||||||
|
+ relBlockInLocalChunk.y + "; "
|
||||||
|
+ relBlockInLocalChunk.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;
|
||||||
|
RelFace rotatedFace = face.rotate(this.chunk.getUp(), chunk.getUp());
|
||||||
|
|
||||||
|
if (!bos.isOpaque(rotatedFace)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
Vectors.release(blockInChunk);
|
||||||
|
Vectors.release(chunkPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -15,22 +15,49 @@
|
|||||||
* 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.entity;
|
package ru.windcorp.progressia.client.world.entity;
|
||||||
|
|
||||||
import glm.vec._3.Vec3;
|
import glm.vec._3.Vec3;
|
||||||
|
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||||
|
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
||||||
import ru.windcorp.progressia.common.world.entity.EntityData;
|
import ru.windcorp.progressia.common.world.entity.EntityData;
|
||||||
import ru.windcorp.progressia.common.world.generic.GenericEntity;
|
import ru.windcorp.progressia.common.world.generic.EntityGeneric;
|
||||||
|
|
||||||
public abstract class EntityRenderable implements Renderable, GenericEntity {
|
public abstract class EntityRenderable implements Renderable, EntityGeneric {
|
||||||
|
|
||||||
private final EntityData data;
|
private final EntityData data;
|
||||||
|
|
||||||
|
private long stateComputedForFrame = -1;
|
||||||
|
|
||||||
public EntityRenderable(EntityData data) {
|
public EntityRenderable(EntityData data) {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the state of this model. This method is invoked exactly once per
|
||||||
|
* renderable per frame before this entity is queried for the first time.
|
||||||
|
*/
|
||||||
|
protected void update() {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateIfNecessary() {
|
||||||
|
if (stateComputedForFrame != GraphicsInterface.getFramesRendered()) {
|
||||||
|
update();
|
||||||
|
stateComputedForFrame = GraphicsInterface.getFramesRendered();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void render(ShapeRenderHelper renderer) {
|
||||||
|
updateIfNecessary();
|
||||||
|
doRender(renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void doRender(ShapeRenderHelper renderer);
|
||||||
|
|
||||||
public EntityData getData() {
|
public EntityData getData() {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@ -44,8 +71,42 @@ public abstract class EntityRenderable implements Renderable, GenericEntity {
|
|||||||
public String getId() {
|
public String getId() {
|
||||||
return getData().getId();
|
return getData().getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getEntityId() {
|
||||||
|
return getData().getEntityId();
|
||||||
|
}
|
||||||
|
|
||||||
public void getViewPoint(Vec3 output) {
|
public final Vec3 getLookingAt(Vec3 output) {
|
||||||
|
if (output == null) output = new Vec3();
|
||||||
|
updateIfNecessary();
|
||||||
|
doGetLookingAt(output);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doGetLookingAt(Vec3 output) {
|
||||||
|
output.set(getData().getLookingAt());
|
||||||
|
}
|
||||||
|
|
||||||
|
public final Vec3 getUpVector(Vec3 output) {
|
||||||
|
if (output == null) output = new Vec3();
|
||||||
|
updateIfNecessary();
|
||||||
|
doGetUpVector(output);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doGetUpVector(Vec3 output) {
|
||||||
|
output.set(getData().getUpVector());
|
||||||
|
}
|
||||||
|
|
||||||
|
public final Vec3 getViewPoint(Vec3 output) {
|
||||||
|
if (output == null) output = new Vec3();
|
||||||
|
updateIfNecessary();
|
||||||
|
doGetViewPoint(output);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doGetViewPoint(Vec3 output) {
|
||||||
output.set(0, 0, 0);
|
output.set(0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,6 +43,7 @@ public class HumanoidModel extends NPedModel {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void applyTransform(Mat4 mat, NPedModel model) {
|
protected void applyTransform(Mat4 mat, NPedModel model) {
|
||||||
|
super.applyTransform(mat, model);
|
||||||
float phase = model.getWalkingFrequency() * model.getWalkingParameter() + animationOffset;
|
float phase = model.getWalkingFrequency() * model.getWalkingParameter() + animationOffset;
|
||||||
float value = sin(phase);
|
float value = sin(phase);
|
||||||
float amplitude = getSwingAmplitude((HumanoidModel) model) * model.getVelocityParameter();
|
float amplitude = getSwingAmplitude((HumanoidModel) model) * model.getVelocityParameter();
|
||||||
|
@ -18,11 +18,8 @@
|
|||||||
|
|
||||||
package ru.windcorp.progressia.client.world.entity;
|
package ru.windcorp.progressia.client.world.entity;
|
||||||
|
|
||||||
import static java.lang.Math.atan2;
|
|
||||||
import static java.lang.Math.min;
|
|
||||||
import static java.lang.Math.pow;
|
import static java.lang.Math.pow;
|
||||||
import static java.lang.Math.toRadians;
|
import static java.lang.Math.toRadians;
|
||||||
import static ru.windcorp.progressia.common.util.FloatMathUtil.normalizeAngle;
|
|
||||||
|
|
||||||
import glm.Glm;
|
import glm.Glm;
|
||||||
import glm.mat._4.Mat4;
|
import glm.mat._4.Mat4;
|
||||||
@ -32,6 +29,9 @@ import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
|||||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
||||||
import ru.windcorp.progressia.common.Units;
|
import ru.windcorp.progressia.common.Units;
|
||||||
|
import ru.windcorp.progressia.common.util.FloatMathUtil;
|
||||||
|
import ru.windcorp.progressia.common.util.VectorUtil;
|
||||||
|
import ru.windcorp.progressia.common.util.Vectors;
|
||||||
import ru.windcorp.progressia.common.world.entity.EntityData;
|
import ru.windcorp.progressia.common.world.entity.EntityData;
|
||||||
|
|
||||||
public abstract class NPedModel extends EntityRenderable {
|
public abstract class NPedModel extends EntityRenderable {
|
||||||
@ -51,29 +51,30 @@ public abstract class NPedModel extends EntityRenderable {
|
|||||||
ShapeRenderHelper renderer,
|
ShapeRenderHelper renderer,
|
||||||
NPedModel model
|
NPedModel model
|
||||||
) {
|
) {
|
||||||
renderer.pushTransform().translate(translation);
|
|
||||||
applyTransform(renderer.pushTransform(), model);
|
applyTransform(renderer.pushTransform(), model);
|
||||||
renderable.render(renderer);
|
renderable.render(renderer);
|
||||||
renderer.popTransform();
|
renderer.popTransform();
|
||||||
renderer.popTransform();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void applyTransform(Mat4 mat, NPedModel model);
|
protected void applyTransform(Mat4 mat, NPedModel model) {
|
||||||
|
mat.translate(getTranslation());
|
||||||
|
}
|
||||||
|
|
||||||
public Vec3 getTranslation() {
|
public Vec3 getTranslation() {
|
||||||
return translation;
|
return translation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Mat4 getTransform(Mat4 output, NPedModel model) {
|
||||||
|
if (output == null) output = new Mat4().identity();
|
||||||
|
applyTransform(output, model);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Body extends BodyPart {
|
public static class Body extends BodyPart {
|
||||||
public Body(Renderable renderable) {
|
public Body(Renderable renderable) {
|
||||||
super(renderable, null);
|
super(renderable, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void applyTransform(Mat4 mat, NPedModel model) {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Head extends BodyPart {
|
public static class Head extends BodyPart {
|
||||||
@ -97,7 +98,8 @@ public abstract class NPedModel extends EntityRenderable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void applyTransform(Mat4 mat, NPedModel model) {
|
protected void applyTransform(Mat4 mat, NPedModel model) {
|
||||||
mat.rotateZ(model.getHeadYaw()).rotateY(model.getHeadPitch());
|
super.applyTransform(mat, model);
|
||||||
|
mat.rotateZ(-model.getHeadYaw()).rotateY(-model.getHeadPitch());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vec3 getViewPoint() {
|
public Vec3 getViewPoint() {
|
||||||
@ -105,6 +107,8 @@ public abstract class NPedModel extends EntityRenderable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean flag;
|
||||||
|
|
||||||
protected final Body body;
|
protected final Body body;
|
||||||
protected final Head head;
|
protected final Head head;
|
||||||
|
|
||||||
@ -128,7 +132,9 @@ public abstract class NPedModel extends EntityRenderable {
|
|||||||
|
|
||||||
private float walkingFrequency;
|
private float walkingFrequency;
|
||||||
|
|
||||||
private float bodyYaw = Float.NaN;
|
private final Vec3 bodyLookingAt = new Vec3().set(0);
|
||||||
|
private final Mat4 bodyTransform = new Mat4();
|
||||||
|
|
||||||
private float headYaw;
|
private float headYaw;
|
||||||
private float headPitch;
|
private float headPitch;
|
||||||
|
|
||||||
@ -138,68 +144,121 @@ public abstract class NPedModel extends EntityRenderable {
|
|||||||
this.head = head;
|
this.head = head;
|
||||||
this.scale = scale;
|
this.scale = scale;
|
||||||
|
|
||||||
evaluateAngles();
|
computeRotations();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(ShapeRenderHelper renderer) {
|
protected void doRender(ShapeRenderHelper renderer) {
|
||||||
renderer.pushTransform().scale(scale).rotateZ(bodyYaw);
|
renderer.pushTransform().scale(scale).mul(bodyTransform);
|
||||||
renderBodyParts(renderer);
|
renderBodyParts(renderer);
|
||||||
renderer.popTransform();
|
renderer.popTransform();
|
||||||
|
|
||||||
accountForVelocity();
|
|
||||||
evaluateAngles();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void renderBodyParts(ShapeRenderHelper renderer) {
|
protected void renderBodyParts(ShapeRenderHelper renderer) {
|
||||||
body.render(renderer, this);
|
body.render(renderer, this);
|
||||||
head.render(renderer, this);
|
head.render(renderer, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void evaluateAngles() {
|
@Override
|
||||||
float globalYaw = normalizeAngle(getData().getYaw());
|
protected void update() {
|
||||||
|
advanceTime();
|
||||||
if (Float.isNaN(bodyYaw)) {
|
computeRotations();
|
||||||
bodyYaw = globalYaw;
|
|
||||||
headYaw = 0;
|
|
||||||
} else {
|
|
||||||
headYaw = normalizeAngle(globalYaw - bodyYaw);
|
|
||||||
|
|
||||||
if (headYaw > +head.maxYaw) {
|
|
||||||
bodyYaw += headYaw - +head.maxYaw;
|
|
||||||
headYaw = +head.maxYaw;
|
|
||||||
} else if (headYaw < -head.maxYaw) {
|
|
||||||
bodyYaw += headYaw - -head.maxYaw;
|
|
||||||
headYaw = -head.maxYaw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bodyYaw = normalizeAngle(bodyYaw);
|
|
||||||
|
|
||||||
headPitch = Glm.clamp(
|
|
||||||
getData().getPitch(),
|
|
||||||
-head.maxPitch,
|
|
||||||
head.maxPitch
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void accountForVelocity() {
|
private void computeRotations() {
|
||||||
Vec3 horizontal = new Vec3(getData().getVelocity());
|
if (!bodyLookingAt.any()) {
|
||||||
horizontal.z = 0;
|
getData().getForwardVector(bodyLookingAt);
|
||||||
|
headYaw = 0;
|
||||||
|
} else {
|
||||||
|
ensureBodyLookingAtIsPerpendicularToUpVector();
|
||||||
|
computeDesiredHeadYaw();
|
||||||
|
clampHeadYawAndChangeBodyLookingAt();
|
||||||
|
}
|
||||||
|
|
||||||
|
recomputeBodyTransform();
|
||||||
|
|
||||||
|
setHeadPitch();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureBodyLookingAtIsPerpendicularToUpVector() {
|
||||||
|
Vec3 up = getData().getUpVector();
|
||||||
|
if (up.dot(bodyLookingAt) > 1 - 1e-4) return;
|
||||||
|
|
||||||
|
Vec3 tmp = Vectors.grab3();
|
||||||
|
|
||||||
|
tmp.set(up).mul(-up.dot(bodyLookingAt)).add(bodyLookingAt);
|
||||||
|
|
||||||
|
float tmpLength = tmp.length();
|
||||||
|
if (tmpLength > 1e-4) {
|
||||||
|
bodyLookingAt.set(tmp).div(tmpLength);
|
||||||
|
} else {
|
||||||
|
// bodyLookingAt is suddenly parallel to up vector -- PANIC! ENTERING RESCUE MODE!
|
||||||
|
getData().getForwardVector(bodyLookingAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vectors.release(tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void computeDesiredHeadYaw() {
|
||||||
|
Vec3 newDirection = getData().getForwardVector(null);
|
||||||
|
Vec3 oldDirection = bodyLookingAt;
|
||||||
|
Vec3 up = getData().getUpVector();
|
||||||
|
|
||||||
|
headYaw = (float) VectorUtil.getAngle(oldDirection, newDirection, up);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clampHeadYawAndChangeBodyLookingAt() {
|
||||||
|
float bodyYawChange = 0;
|
||||||
|
|
||||||
|
if (headYaw > +head.maxYaw) {
|
||||||
|
bodyYawChange = headYaw - +head.maxYaw;
|
||||||
|
headYaw = +head.maxYaw;
|
||||||
|
} else if (headYaw < -head.maxYaw) {
|
||||||
|
bodyYawChange = headYaw - -head.maxYaw;
|
||||||
|
headYaw = -head.maxYaw;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bodyYawChange != 0) {
|
||||||
|
VectorUtil.rotate(bodyLookingAt, getData().getUpVector(), bodyYawChange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recomputeBodyTransform() {
|
||||||
|
Vec3 u = getData().getUpVector();
|
||||||
|
Vec3 f = bodyLookingAt;
|
||||||
|
Vec3 s = Vectors.grab3();
|
||||||
|
|
||||||
|
s.set(u).cross(f);
|
||||||
|
|
||||||
|
bodyTransform.identity().set(
|
||||||
|
+f.x, +f.y, +f.z, 0,
|
||||||
|
-s.x, -s.y, -s.z, 0,
|
||||||
|
+u.x, +u.y, +u.z, 0,
|
||||||
|
0, 0, 0, 1
|
||||||
|
);
|
||||||
|
|
||||||
|
Vectors.release(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setHeadPitch() {
|
||||||
|
headPitch = Glm.clamp((float) getData().getPitch(), -head.maxPitch, +head.maxPitch);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void advanceTime() {
|
||||||
|
Vec3 horizontal = getData().getUpVector()
|
||||||
|
.mul_(-getData().getUpVector().dot(getData().getVelocity()))
|
||||||
|
.add(getData().getVelocity());
|
||||||
|
|
||||||
velocity = horizontal.length();
|
velocity = horizontal.length();
|
||||||
|
|
||||||
evaluateVelocityCoeff();
|
computeVelocityParameter();
|
||||||
|
|
||||||
// TODO switch to world time
|
|
||||||
walkingParameter += velocity * GraphicsInterface.getFrameLength() * 1000;
|
walkingParameter += velocity * GraphicsInterface.getFrameLength() * 1000;
|
||||||
|
|
||||||
bodyYaw += velocityParameter * normalizeAngle(
|
rotateBodyWithMovement(horizontal);
|
||||||
(float) (atan2(horizontal.y, horizontal.x) - bodyYaw)
|
|
||||||
) * min(1, GraphicsInterface.getFrameLength() * 10);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void evaluateVelocityCoeff() {
|
private void computeVelocityParameter() {
|
||||||
if (velocity > maxEffectiveVelocity) {
|
if (velocity > maxEffectiveVelocity) {
|
||||||
velocityParameter = 1;
|
velocityParameter = 1;
|
||||||
} else {
|
} else {
|
||||||
@ -207,17 +266,39 @@ public abstract class NPedModel extends EntityRenderable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void rotateBodyWithMovement(Vec3 target) {
|
||||||
|
if (velocityParameter == 0 || !target.any() || Glm.equals(target, bodyLookingAt)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3 axis = getData().getUpVector();
|
||||||
|
|
||||||
|
float yawDifference = FloatMathUtil.normalizeAngle(
|
||||||
|
(float) VectorUtil.getAngle(
|
||||||
|
bodyLookingAt,
|
||||||
|
target.normalize_(),
|
||||||
|
axis
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
float bodyYawChange =
|
||||||
|
velocityParameter *
|
||||||
|
yawDifference *
|
||||||
|
(float) Math.expm1(GraphicsInterface.getFrameLength() * 10);
|
||||||
|
|
||||||
|
VectorUtil.rotate(bodyLookingAt, axis, bodyYawChange);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void getViewPoint(Vec3 output) {
|
protected void doGetViewPoint(Vec3 output) {
|
||||||
Mat4 m = new Mat4();
|
Mat4 m = new Mat4();
|
||||||
Vec4 v = new Vec4();
|
Vec4 v = new Vec4();
|
||||||
|
|
||||||
m.identity()
|
m.identity()
|
||||||
.scale(scale)
|
.scale(scale)
|
||||||
.rotateZ(bodyYaw)
|
.mul(bodyTransform);
|
||||||
.translate(head.getTranslation())
|
|
||||||
.rotateZ(headYaw)
|
head.getTransform(m, this);
|
||||||
.rotateY(headPitch);
|
|
||||||
|
|
||||||
v.set(head.getViewPoint(), 1);
|
v.set(head.getViewPoint(), 1);
|
||||||
m.mul(v);
|
m.mul(v);
|
||||||
@ -232,9 +313,9 @@ public abstract class NPedModel extends EntityRenderable {
|
|||||||
public Head getHead() {
|
public Head getHead() {
|
||||||
return head;
|
return head;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getBodyYaw() {
|
public Vec3 getBodyLookingAt() {
|
||||||
return bodyYaw;
|
return bodyLookingAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getHeadYaw() {
|
public float getHeadYaw() {
|
||||||
|
@ -44,6 +44,7 @@ public class QuadripedModel extends NPedModel {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void applyTransform(Mat4 mat, NPedModel model) {
|
protected void applyTransform(Mat4 mat, NPedModel model) {
|
||||||
|
super.applyTransform(mat, model);
|
||||||
float phase = model.getWalkingFrequency() * model.getWalkingParameter() + animationOffset;
|
float phase = model.getWalkingFrequency() * model.getWalkingParameter() + animationOffset;
|
||||||
float value = sin(phase);
|
float value = sin(phase);
|
||||||
float amplitude = ((QuadripedModel) model).getWalkingSwing() * model.getVelocityParameter();
|
float amplitude = ((QuadripedModel) model).getWalkingSwing() * model.getVelocityParameter();
|
||||||
|
@ -18,26 +18,21 @@
|
|||||||
|
|
||||||
package ru.windcorp.progressia.client.world.tile;
|
package ru.windcorp.progressia.client.world.tile;
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
import glm.vec._3.i.Vec3i;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
import ru.windcorp.progressia.client.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.block.BlockFace;
|
import ru.windcorp.progressia.common.world.DefaultChunkData;
|
||||||
import ru.windcorp.progressia.common.world.generic.GenericTile;
|
import ru.windcorp.progressia.common.world.generic.TileGeneric;
|
||||||
|
import ru.windcorp.progressia.common.world.rels.RelFace;
|
||||||
|
|
||||||
public class TileRender extends Namespaced implements GenericTile {
|
public class TileRender extends Namespaced implements TileGeneric {
|
||||||
|
|
||||||
public TileRender(String id) {
|
public TileRender(String id) {
|
||||||
super(id);
|
super(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void render(ShapeRenderHelper renderer, BlockFace face) {
|
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk, RelFace face) {
|
||||||
throw new UnsupportedOperationException(
|
|
||||||
"TileRender.render() not implemented in " + this
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Renderable createRenderable(BlockFace face) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,170 @@
|
|||||||
|
/*
|
||||||
|
* 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 java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import glm.mat._3.Mat3;
|
||||||
|
import glm.mat._4.Mat4;
|
||||||
|
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.Renderable;
|
||||||
|
import ru.windcorp.progressia.client.graphics.model.Shape;
|
||||||
|
import ru.windcorp.progressia.client.graphics.model.ShapePart;
|
||||||
|
import ru.windcorp.progressia.client.graphics.model.ShapeParts;
|
||||||
|
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||||
|
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
|
||||||
|
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSimple.TileOptimizedCustom;
|
||||||
|
import ru.windcorp.progressia.common.util.Matrices;
|
||||||
|
import ru.windcorp.progressia.common.util.VectorUtil;
|
||||||
|
import ru.windcorp.progressia.common.util.Vectors;
|
||||||
|
import ru.windcorp.progressia.common.world.DefaultChunkData;
|
||||||
|
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
||||||
|
import ru.windcorp.progressia.common.world.rels.AxisRotations;
|
||||||
|
import ru.windcorp.progressia.common.world.rels.RelFace;
|
||||||
|
|
||||||
|
public class TileRenderCross extends TileRender implements TileOptimizedCustom {
|
||||||
|
|
||||||
|
private static final float SQRT_2_OVER_2 = (float) Math.sqrt(2) / 2;
|
||||||
|
private static final float[] ONE_AND_NEGATIVE_ONE = new float[] { 1, -1 };
|
||||||
|
|
||||||
|
private final Texture texture;
|
||||||
|
private final float width;
|
||||||
|
|
||||||
|
public TileRenderCross(String id, Texture texture, boolean allowStretching) {
|
||||||
|
super(id);
|
||||||
|
this.texture = texture;
|
||||||
|
this.width = allowStretching ? 1 : SQRT_2_OVER_2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Texture getTexture(RelFace blockFace) {
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vec4 getColorMultiplier(RelFace blockFace) {
|
||||||
|
return Colors.WHITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getShapeParts(
|
||||||
|
DefaultChunkData chunk,
|
||||||
|
Vec3i bic,
|
||||||
|
RelFace blockFace,
|
||||||
|
Consumer<ShapePart> output
|
||||||
|
) {
|
||||||
|
Mat4 transform = Matrices.grab4();
|
||||||
|
Vec3 origin = Vectors.grab3();
|
||||||
|
Vec3 width = Vectors.grab3();
|
||||||
|
Vec3 height = Vectors.grab3();
|
||||||
|
|
||||||
|
Mat3 resolutionMatrix = AxisRotations.getResolutionMatrix3(blockFace.resolve(AbsFace.POS_Z));
|
||||||
|
|
||||||
|
Vec4 color = getColorMultiplier(blockFace);
|
||||||
|
Texture texture = getTexture(blockFace);
|
||||||
|
float originOffset = (1 - this.width) / 2;
|
||||||
|
|
||||||
|
WorldRenderProgram program = WorldRenderProgram.getDefault();
|
||||||
|
|
||||||
|
for (int i = 0; getTransform(chunk, bic, blockFace, i, transform); i++) {
|
||||||
|
|
||||||
|
for (float flip : ONE_AND_NEGATIVE_ONE) {
|
||||||
|
origin.set(flip * (originOffset - 0.5f), originOffset - 0.5f, 0);
|
||||||
|
width.set(flip * this.width, this.width, 0);
|
||||||
|
height.set(0, 0, 1);
|
||||||
|
|
||||||
|
VectorUtil.applyMat4(origin, transform);
|
||||||
|
VectorUtil.rotateOnly(width, transform);
|
||||||
|
VectorUtil.rotateOnly(height, transform);
|
||||||
|
|
||||||
|
origin.z += 1 - 0.5f;
|
||||||
|
|
||||||
|
if (blockFace != RelFace.UP) {
|
||||||
|
resolutionMatrix.mul(origin);
|
||||||
|
resolutionMatrix.mul(width);
|
||||||
|
resolutionMatrix.mul(height);
|
||||||
|
}
|
||||||
|
|
||||||
|
origin.add(bic.x, bic.y, bic.z);
|
||||||
|
|
||||||
|
output.accept(
|
||||||
|
ShapeParts.createRectangle(
|
||||||
|
program,
|
||||||
|
texture,
|
||||||
|
color,
|
||||||
|
origin,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
);
|
||||||
|
output.accept(
|
||||||
|
ShapeParts.createRectangle(
|
||||||
|
program,
|
||||||
|
texture,
|
||||||
|
color,
|
||||||
|
origin,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrices.release(transform);
|
||||||
|
Vectors.release(origin);
|
||||||
|
Vectors.release(width);
|
||||||
|
Vectors.release(height);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean getTransform(
|
||||||
|
DefaultChunkData chunk,
|
||||||
|
Vec3i relBlockInChunk,
|
||||||
|
RelFace blockFace,
|
||||||
|
int count,
|
||||||
|
Mat4 output
|
||||||
|
) {
|
||||||
|
output.identity();
|
||||||
|
return count == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk, RelFace blockFace) {
|
||||||
|
Collection<ShapePart> parts = new ArrayList<>(4);
|
||||||
|
|
||||||
|
getShapeParts(chunk, blockInChunk, blockFace, parts::add);
|
||||||
|
|
||||||
|
return new Shape(
|
||||||
|
Usage.STATIC,
|
||||||
|
WorldRenderProgram.getDefault(),
|
||||||
|
parts.toArray(new ShapePart[parts.size()])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean needsOwnRenderable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,81 +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.tile;
|
|
||||||
|
|
||||||
import glm.vec._3.Vec3;
|
|
||||||
import ru.windcorp.progressia.client.graphics.Colors;
|
|
||||||
import ru.windcorp.progressia.client.graphics.backend.Usage;
|
|
||||||
import ru.windcorp.progressia.client.graphics.model.Faces;
|
|
||||||
import ru.windcorp.progressia.client.graphics.model.Shape;
|
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
|
|
||||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
|
||||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
|
||||||
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
|
|
||||||
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerCube.OpaqueSurface;
|
|
||||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
|
||||||
|
|
||||||
public class TileRenderGrass extends TileRender implements OpaqueSurface {
|
|
||||||
|
|
||||||
private final Texture topTexture;
|
|
||||||
private final Texture sideTexture;
|
|
||||||
|
|
||||||
public TileRenderGrass(
|
|
||||||
String id,
|
|
||||||
Texture top,
|
|
||||||
Texture side
|
|
||||||
) {
|
|
||||||
super(id);
|
|
||||||
this.topTexture = top;
|
|
||||||
this.sideTexture = side;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Texture getTexture(BlockFace face) {
|
|
||||||
return (face == BlockFace.TOP) ? topTexture : sideTexture;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isOpaque(BlockFace face) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -15,31 +15,28 @@
|
|||||||
* 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.tile;
|
||||||
package ru.windcorp.progressia.client.world.cro;
|
|
||||||
|
|
||||||
import com.google.common.base.Supplier;
|
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.DefaultChunkData;
|
||||||
|
import ru.windcorp.progressia.common.world.rels.RelFace;
|
||||||
|
|
||||||
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
|
public class TileRenderNone extends TileRender {
|
||||||
|
|
||||||
public abstract class ChunkRenderOptimizerSupplier extends Namespaced {
|
public TileRenderNone(String id) {
|
||||||
|
|
||||||
public ChunkRenderOptimizerSupplier(String id) {
|
|
||||||
super(id);
|
super(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract ChunkRenderOptimizer createOptimizer();
|
@Override
|
||||||
|
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk, RelFace face) {
|
||||||
public static ChunkRenderOptimizerSupplier of(
|
return EmptyModel.getInstance();
|
||||||
String id,
|
}
|
||||||
Supplier<ChunkRenderOptimizer> supplier
|
|
||||||
) {
|
@Override
|
||||||
return new ChunkRenderOptimizerSupplier(id) {
|
public boolean needsOwnRenderable() {
|
||||||
@Override
|
return false;
|
||||||
public ChunkRenderOptimizer createOptimizer() {
|
|
||||||
return supplier.get();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -19,7 +19,7 @@
|
|||||||
package ru.windcorp.progressia.client.world.tile;
|
package ru.windcorp.progressia.client.world.tile;
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
import ru.windcorp.progressia.common.world.rels.RelFace;
|
||||||
|
|
||||||
public class TileRenderOpaqueSurface extends TileRenderSurface {
|
public class TileRenderOpaqueSurface extends TileRenderSurface {
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ public class TileRenderOpaqueSurface extends TileRenderSurface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isOpaque(BlockFace face) {
|
public boolean isOpaque(RelFace face) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* 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 ru.windcorp.progressia.client.world.ChunkRender;
|
||||||
|
import ru.windcorp.progressia.client.world.block.BlockRender;
|
||||||
|
import ru.windcorp.progressia.common.world.generic.TileGenericReferenceRO;
|
||||||
|
|
||||||
|
public interface TileRenderReference
|
||||||
|
extends TileGenericReferenceRO<BlockRender, TileRender, TileRenderStack, TileRenderReference, ChunkRender> {
|
||||||
|
|
||||||
|
}
|
@ -18,12 +18,15 @@
|
|||||||
|
|
||||||
package ru.windcorp.progressia.client.world.tile;
|
package ru.windcorp.progressia.client.world.tile;
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.world.ChunkRender;
|
import java.util.AbstractList;
|
||||||
import ru.windcorp.progressia.common.world.generic.GenericTileStack;
|
|
||||||
import ru.windcorp.progressia.common.world.tile.TileDataStack;
|
|
||||||
|
|
||||||
|
import ru.windcorp.progressia.client.world.ChunkRender;
|
||||||
|
import ru.windcorp.progressia.client.world.block.BlockRender;
|
||||||
|
import ru.windcorp.progressia.common.world.TileDataStack;
|
||||||
|
import ru.windcorp.progressia.common.world.generic.TileGenericStackRO;
|
||||||
public abstract class TileRenderStack
|
public abstract class TileRenderStack
|
||||||
extends GenericTileStack<TileRenderStack, TileRender, ChunkRender> {
|
extends AbstractList<TileRender>
|
||||||
|
implements TileGenericStackRO<BlockRender, TileRender, TileRenderStack, TileRenderReference, ChunkRender> {
|
||||||
|
|
||||||
public abstract TileDataStack getData();
|
public abstract TileDataStack getData();
|
||||||
|
|
||||||
|
@ -18,19 +18,26 @@
|
|||||||
|
|
||||||
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.Faces;
|
import ru.windcorp.progressia.client.graphics.model.ShapePart;
|
||||||
|
import ru.windcorp.progressia.client.graphics.model.ShapeParts;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Shape;
|
import ru.windcorp.progressia.client.graphics.model.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.world.block.BlockFace;
|
import ru.windcorp.progressia.common.util.Vectors;
|
||||||
|
import ru.windcorp.progressia.common.world.DefaultChunkData;
|
||||||
|
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
||||||
|
import ru.windcorp.progressia.common.world.rels.RelFace;
|
||||||
|
|
||||||
public abstract class TileRenderSurface extends TileRender implements OpaqueSurface {
|
public abstract class TileRenderSurface extends TileRender implements TileOptimizedSurface {
|
||||||
|
|
||||||
private final Texture texture;
|
private final Texture texture;
|
||||||
|
|
||||||
@ -38,27 +45,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(RelFace blockFace) {
|
||||||
public Texture getTexture(BlockFace face) {
|
|
||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Vec4 getColorMultiplier(RelFace blockFace) {
|
||||||
|
return Colors.WHITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void getShapeParts(
|
||||||
|
DefaultChunkData chunk, Vec3i relBlockInChunk, RelFace blockFace,
|
||||||
|
boolean inner,
|
||||||
|
Consumer<ShapePart> output,
|
||||||
|
Vec3 offset
|
||||||
|
) {
|
||||||
|
output.accept(createFace(chunk, relBlockInChunk, blockFace, inner, offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ShapePart createFace(
|
||||||
|
DefaultChunkData chunk, Vec3i blockInChunk, RelFace blockFace,
|
||||||
|
boolean inner,
|
||||||
|
Vec3 offset
|
||||||
|
) {
|
||||||
|
return ShapeParts.createBlockFace(
|
||||||
|
WorldRenderProgram.getDefault(),
|
||||||
|
getTexture(blockFace),
|
||||||
|
getColorMultiplier(blockFace),
|
||||||
|
offset,
|
||||||
|
blockFace.resolve(AbsFace.POS_Z),
|
||||||
|
inner
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Renderable createRenderable(BlockFace face) {
|
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk, RelFace 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
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
package ru.windcorp.progressia.client.world.tile;
|
package ru.windcorp.progressia.client.world.tile;
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
import ru.windcorp.progressia.common.world.rels.RelFace;
|
||||||
|
|
||||||
public class TileRenderTransparentSurface extends TileRenderSurface {
|
public class TileRenderTransparentSurface extends TileRenderSurface {
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ public class TileRenderTransparentSurface extends TileRenderSurface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isOpaque(BlockFace face) {
|
public boolean isOpaque(RelFace face) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package ru.windcorp.progressia.common.collision;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
||||||
|
import glm.vec._3.Vec3;
|
||||||
|
import ru.windcorp.progressia.common.util.Vectors;
|
||||||
|
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
||||||
|
import ru.windcorp.progressia.common.world.rels.AxisRotations;
|
||||||
|
|
||||||
|
public class AABBRotator implements AABBoid {
|
||||||
|
|
||||||
|
private class AABBRotatorWall implements Wall {
|
||||||
|
|
||||||
|
private final int id;
|
||||||
|
|
||||||
|
public AABBRotatorWall(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getOrigin(Vec3 output) {
|
||||||
|
parent.getWall(id).getOrigin(output);
|
||||||
|
AxisRotations.resolve(output, upSupplier.get(), output);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getWidth(Vec3 output) {
|
||||||
|
parent.getWall(id).getWidth(output);
|
||||||
|
AxisRotations.resolve(output, upSupplier.get(), output);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getHeight(Vec3 output) {
|
||||||
|
parent.getWall(id).getHeight(output);
|
||||||
|
AxisRotations.resolve(output, upSupplier.get(), output);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Supplier<AbsFace> upSupplier;
|
||||||
|
private final Supplier<Vec3> hingeSupplier;
|
||||||
|
private final AABBoid parent;
|
||||||
|
|
||||||
|
private final AABBRotatorWall[] walls = new AABBRotatorWall[AbsFace.BLOCK_FACE_COUNT];
|
||||||
|
|
||||||
|
{
|
||||||
|
for (int id = 0; id < walls.length; ++id) {
|
||||||
|
walls[id] = new AABBRotatorWall(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AABBRotator(Supplier<AbsFace> upSupplier, Supplier<Vec3> hingeSupplier, AABBoid parent) {
|
||||||
|
this.upSupplier = upSupplier;
|
||||||
|
this.hingeSupplier = hingeSupplier;
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOrigin(Vec3 origin) {
|
||||||
|
Vec3 relativeOrigin = Vectors.grab3();
|
||||||
|
Vec3 hinge = hingeSupplier.get();
|
||||||
|
|
||||||
|
origin.sub(hinge, relativeOrigin);
|
||||||
|
AxisRotations.relativize(relativeOrigin, upSupplier.get(), relativeOrigin);
|
||||||
|
relativeOrigin.add(hinge);
|
||||||
|
|
||||||
|
parent.setOrigin(relativeOrigin);
|
||||||
|
|
||||||
|
Vectors.release(relativeOrigin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void moveOrigin(Vec3 displacement) {
|
||||||
|
parent.moveOrigin(displacement);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getOrigin(Vec3 output) {
|
||||||
|
parent.getOrigin(output);
|
||||||
|
Vec3 hinge = hingeSupplier.get();
|
||||||
|
|
||||||
|
output.sub(hinge);
|
||||||
|
AxisRotations.resolve(output, upSupplier.get(), output);
|
||||||
|
output.add(hinge);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getSize(Vec3 output) {
|
||||||
|
parent.getSize(output);
|
||||||
|
AxisRotations.resolve(output, upSupplier.get(), output);
|
||||||
|
output.abs();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Wall getWall(int faceId) {
|
||||||
|
return walls[faceId];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CollisionModel rotate(Supplier<AbsFace> upSupplier, Supplier<Vec3> hingeSupplier, CollisionModel parent) {
|
||||||
|
if (parent instanceof AABBoid) {
|
||||||
|
return new AABBRotator(upSupplier, hingeSupplier, (AABBoid) parent);
|
||||||
|
} else if (parent instanceof CompoundCollisionModel) {
|
||||||
|
ImmutableList.Builder<CollisionModel> models = ImmutableList.builder();
|
||||||
|
|
||||||
|
for (CollisionModel original : ((CompoundCollisionModel) parent).getModels()) {
|
||||||
|
models.add(rotate(upSupplier, hingeSupplier, original));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CompoundCollisionModel(models.build());
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -19,7 +19,7 @@
|
|||||||
package ru.windcorp.progressia.common.collision;
|
package ru.windcorp.progressia.common.collision;
|
||||||
|
|
||||||
import glm.vec._3.Vec3;
|
import glm.vec._3.Vec3;
|
||||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
||||||
|
|
||||||
public interface AABBoid extends CollisionModel {
|
public interface AABBoid extends CollisionModel {
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ public interface AABBoid extends CollisionModel {
|
|||||||
|
|
||||||
void getSize(Vec3 output);
|
void getSize(Vec3 output);
|
||||||
|
|
||||||
default Wall getWall(BlockFace face) {
|
default Wall getWall(AbsFace face) {
|
||||||
return getWall(face.getId());
|
return getWall(face.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ package ru.windcorp.progressia.common.collision;
|
|||||||
|
|
||||||
import glm.vec._3.Vec3;
|
import glm.vec._3.Vec3;
|
||||||
import ru.windcorp.progressia.common.util.Vectors;
|
import ru.windcorp.progressia.common.util.Vectors;
|
||||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
||||||
|
|
||||||
public class TranslatedAABB implements AABBoid {
|
public class TranslatedAABB implements AABBoid {
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ public class TranslatedAABB implements AABBoid {
|
|||||||
private AABBoid parent;
|
private AABBoid parent;
|
||||||
private final Vec3 translation = new Vec3();
|
private final Vec3 translation = new Vec3();
|
||||||
|
|
||||||
private final TranslatedAABBWall[] walls = new TranslatedAABBWall[BlockFace.BLOCK_FACE_COUNT];
|
private final TranslatedAABBWall[] walls = new TranslatedAABBWall[AbsFace.BLOCK_FACE_COUNT];
|
||||||
|
|
||||||
{
|
{
|
||||||
for (int id = 0; id < walls.length; ++id) {
|
for (int id = 0; id < walls.length; ++id) {
|
||||||
|
@ -24,7 +24,7 @@ import java.util.Collection;
|
|||||||
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.common.util.LowOverheadCache;
|
import ru.windcorp.progressia.common.util.LowOverheadCache;
|
||||||
import ru.windcorp.progressia.common.world.WorldData;
|
import ru.windcorp.progressia.common.world.DefaultWorldData;
|
||||||
|
|
||||||
public class WorldCollisionHelper {
|
public class WorldCollisionHelper {
|
||||||
|
|
||||||
@ -79,7 +79,7 @@ public class WorldCollisionHelper {
|
|||||||
* checked against
|
* checked against
|
||||||
* @param maxTime maximum collision time
|
* @param maxTime maximum collision time
|
||||||
*/
|
*/
|
||||||
public void tuneToCollideable(WorldData world, Collideable collideable, float maxTime) {
|
public void tuneToCollideable(DefaultWorldData world, Collideable collideable, float maxTime) {
|
||||||
activeBlockModels.forEach(blockModelCache::release);
|
activeBlockModels.forEach(blockModelCache::release);
|
||||||
activeBlockModels.clear();
|
activeBlockModels.clear();
|
||||||
CollisionPathComputer.forEveryBlockInCollisionPath(
|
CollisionPathComputer.forEveryBlockInCollisionPath(
|
||||||
|
@ -25,7 +25,7 @@ import ru.windcorp.progressia.common.collision.colliders.Collider.ColliderWorksp
|
|||||||
import ru.windcorp.progressia.common.collision.colliders.Collider.Collision;
|
import ru.windcorp.progressia.common.collision.colliders.Collider.Collision;
|
||||||
import ru.windcorp.progressia.common.util.Matrices;
|
import ru.windcorp.progressia.common.util.Matrices;
|
||||||
import ru.windcorp.progressia.common.util.Vectors;
|
import ru.windcorp.progressia.common.util.Vectors;
|
||||||
import ru.windcorp.progressia.common.world.block.BlockFace;
|
import ru.windcorp.progressia.common.world.rels.AbsFace;
|
||||||
|
|
||||||
class AABBoidCollider {
|
class AABBoidCollider {
|
||||||
|
|
||||||
@ -50,7 +50,7 @@ class AABBoidCollider {
|
|||||||
computeCollisionVelocity(collisionVelocity, obstacleBody, colliderBody);
|
computeCollisionVelocity(collisionVelocity, obstacleBody, colliderBody);
|
||||||
|
|
||||||
// For every wall of collision space
|
// For every wall of collision space
|
||||||
for (int i = 0; i < BlockFace.BLOCK_FACE_COUNT; ++i) {
|
for (int i = 0; i < AbsFace.BLOCK_FACE_COUNT; ++i) {
|
||||||
Wall wall = originCollisionSpace.getWall(i);
|
Wall wall = originCollisionSpace.getWall(i);
|
||||||
|
|
||||||
Collision collision = computeWallCollision(
|
Collision collision = computeWallCollision(
|
||||||
@ -122,46 +122,51 @@ class AABBoidCollider {
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @formatter:off
|
||||||
/*
|
/*
|
||||||
* Here we determine whether a collision has actually happened, and if it
|
* Here we determine whether a collision has actually happened, and if it did, at what moment.
|
||||||
* did, at what moment.
|
*
|
||||||
* The basic idea is to compute the moment of collision and impact
|
* The basic idea is to compute the moment of collision and impact coordinates in wall coordinate space.
|
||||||
* coordinates in wall coordinate space.
|
* Then, we can check impact coordinates to determine if we actually hit the wall or flew by and then
|
||||||
* Then, we can check impact coordinates to determine if we actually hit the
|
* check time to make sure the collision is not too far in the future and not in the past.
|
||||||
* wall or flew by and then
|
*
|
||||||
* check time to make sure the collision is not too far in the future and
|
|
||||||
* not in the past.
|
|
||||||
* DETAILED EXPLANATION:
|
* DETAILED EXPLANATION:
|
||||||
* Consider a surface defined by an origin r_wall and two noncollinear
|
*
|
||||||
* nonzero vectors w and h.
|
* Consider a surface defined by an origin r_wall and two noncollinear nonzero vectors w and h.
|
||||||
* Consider a line defined by an origin r_line and a nonzero vector v.
|
* Consider a line defined by an origin r_line and a nonzero vector v.
|
||||||
|
*
|
||||||
* Then, a collision occurs if there exist x, y and t such that
|
* Then, a collision occurs if there exist x, y and t such that
|
||||||
* ______ _
|
* ______ _
|
||||||
* r_line + v * t
|
* r_line + v * t
|
||||||
|
*
|
||||||
* and
|
* and
|
||||||
* ______ _ _
|
* ______ _ _
|
||||||
* r_wall + w * x + h * y
|
* r_wall + w * x + h * y
|
||||||
* describe the same location (indeed, this corresponds to a collision at
|
*
|
||||||
* moment t0 + t
|
* describe the same location (indeed, this corresponds to a collision at moment t0 + t
|
||||||
* with a point on the wall with coordinates (x; y) in (w; h) coordinate
|
* with a point on the wall with coordinates (x; y) in (w; h) coordinate system).
|
||||||
* system).
|
*
|
||||||
* Therefore,
|
* Therefore,
|
||||||
* ______ _ ______ _ _
|
* ______ _ ______ _ _
|
||||||
* r_line + v*t = r_wall + w*x + h*y;
|
* r_line + v*t = r_wall + w*x + h*y;
|
||||||
* _ ⎡w_x h_x -v_x⎤ ⎡x⎤ _ ______ ______
|
*
|
||||||
* r = ⎢w_y h_y -v_y⎥ * ⎢y⎥, where r = r_line - r_wall;
|
* _ ⎡w_x h_x -v_x⎤ ⎡x⎤ _ ______ ______
|
||||||
* ⎣w_z h_z -v_z⎦ ⎣t⎦
|
* r = ⎢w_y h_y -v_y⎥ * ⎢y⎥, where r = r_line - r_wall;
|
||||||
* ⎡x⎤ ⎡w_x h_x -v_x⎤ -1 _
|
* ⎣w_z h_z -v_z⎦ ⎣t⎦
|
||||||
* ⎢y⎥ = ⎢w_y h_y -v_y⎥ * r, if the matrix is invertible.
|
*
|
||||||
* ⎣t⎦ ⎣w_z h_z -v_z⎦
|
* ⎡x⎤ ⎡w_x h_x -v_x⎤ -1 _
|
||||||
|
* ⎢y⎥ = ⎢w_y h_y -v_y⎥ * r, if the matrix is invertible.
|
||||||
|
* ⎣t⎦ ⎣w_z h_z -v_z⎦
|
||||||
|
*
|
||||||
* Then, one only needs to ensure that:
|
* Then, one only needs to ensure that:
|
||||||
* 0 < x < 1,
|
* 0 < x < 1,
|
||||||
* 0 < y < 1, and
|
* 0 < y < 1, and
|
||||||
* 0 < t < T, where T is remaining tick time.
|
* 0 < t < T, where T is remaining tick time.
|
||||||
* If the matrix is not invertible or any of the conditions are not met, no
|
*
|
||||||
* collision happened.
|
* If the matrix is not invertible or any of the conditions are not met, no collision happened.
|
||||||
* If all conditions are satisfied, then the moment of impact is t0 + t.
|
* If all conditions are satisfied, then the moment of impact is t0 + t.
|
||||||
*/
|
*/
|
||||||
|
// @formatter:on
|
||||||
private static Collision computeWallCollision(
|
private static Collision computeWallCollision(
|
||||||
Wall obstacleWall,
|
Wall obstacleWall,
|
||||||
AABBoid colliderModel,
|
AABBoid colliderModel,
|
||||||
|
@ -27,7 +27,7 @@ import glm.vec._3.Vec3;
|
|||||||
import ru.windcorp.progressia.common.collision.*;
|
import ru.windcorp.progressia.common.collision.*;
|
||||||
import ru.windcorp.progressia.common.util.LowOverheadCache;
|
import ru.windcorp.progressia.common.util.LowOverheadCache;
|
||||||
import ru.windcorp.progressia.common.util.Vectors;
|
import ru.windcorp.progressia.common.util.Vectors;
|
||||||
import ru.windcorp.progressia.common.world.WorldData;
|
import ru.windcorp.progressia.common.world.DefaultWorldData;
|
||||||
|
|
||||||
public class Collider {
|
public class Collider {
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ public class Collider {
|
|||||||
/**
|
/**
|
||||||
* Dear Princess Celestia,
|
* Dear Princess Celestia,
|
||||||
* <p>
|
* <p>
|
||||||
* When {@linkplain #advanceTime(Collection, Collision, WorldData, float)
|
* When {@linkplain #advanceTime(Collection, Collision, DefaultWorldData, float)
|
||||||
* advancing time},
|
* advancing time},
|
||||||
* time step for all entities <em>except</em> currently colliding bodies is
|
* time step for all entities <em>except</em> currently colliding bodies is
|
||||||
* the current
|
* the current
|
||||||
@ -61,7 +61,7 @@ public class Collider {
|
|||||||
|
|
||||||
public static void performCollisions(
|
public static void performCollisions(
|
||||||
List<? extends Collideable> colls,
|
List<? extends Collideable> colls,
|
||||||
WorldData world,
|
DefaultWorldData world,
|
||||||
float tickLength,
|
float tickLength,
|
||||||
ColliderWorkspace workspace
|
ColliderWorkspace workspace
|
||||||
) {
|
) {
|
||||||
@ -96,7 +96,7 @@ public class Collider {
|
|||||||
private static Collision getFirstCollision(
|
private static Collision getFirstCollision(
|
||||||
List<? extends Collideable> colls,
|
List<? extends Collideable> colls,
|
||||||
float tickLength,
|
float tickLength,
|
||||||
WorldData world,
|
DefaultWorldData world,
|
||||||
ColliderWorkspace workspace
|
ColliderWorkspace workspace
|
||||||
) {
|
) {
|
||||||
Collision result = null;
|
Collision result = null;
|
||||||
@ -126,7 +126,7 @@ public class Collider {
|
|||||||
private static void tuneWorldCollisionHelper(
|
private static void tuneWorldCollisionHelper(
|
||||||
Collideable coll,
|
Collideable coll,
|
||||||
float tickLength,
|
float tickLength,
|
||||||
WorldData world,
|
DefaultWorldData world,
|
||||||
ColliderWorkspace workspace
|
ColliderWorkspace workspace
|
||||||
) {
|
) {
|
||||||
WorldCollisionHelper wch = workspace.worldCollisionHelper;
|
WorldCollisionHelper wch = workspace.worldCollisionHelper;
|
||||||
@ -194,7 +194,7 @@ public class Collider {
|
|||||||
Collision collision,
|
Collision collision,
|
||||||
|
|
||||||
Collection<? extends Collideable> colls,
|
Collection<? extends Collideable> colls,
|
||||||
WorldData world,
|
DefaultWorldData world,
|
||||||
float tickLength,
|
float tickLength,
|
||||||
ColliderWorkspace workspace
|
ColliderWorkspace workspace
|
||||||
) {
|
) {
|
||||||
@ -212,66 +212,72 @@ public class Collider {
|
|||||||
handlePhysics(collision);
|
handlePhysics(collision);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @formatter:off
|
||||||
/*
|
/*
|
||||||
* Here we compute the change in body velocities due to a collision.
|
* Here we compute the change in body velocities due to a collision.
|
||||||
|
*
|
||||||
* We make the following simplifications:
|
* We make the following simplifications:
|
||||||
* 1) The bodies are perfectly rigid;
|
* 1) The bodies are perfectly rigid;
|
||||||
* 2) The collision is perfectly inelastic
|
* 2) The collision is perfectly inelastic
|
||||||
* (no bouncing);
|
* (no bouncing);
|
||||||
* 3) The bodies are spherical;
|
* 3) The bodies are spherical;
|
||||||
* 4) No tangential friction exists
|
* 4) No tangential friction exists
|
||||||
* (bodies do not experience friction when sliding against each other);
|
* (bodies do not experience friction when sliding against each other);
|
||||||
* 5) Velocities are not relativistic.
|
* 5) Velocities are not relativistic.
|
||||||
|
*
|
||||||
* Angular momentum is ignored per 3) and 4),
|
* Angular momentum is ignored per 3) and 4),
|
||||||
* e.g. when something pushes an end of a long stick, the stick does not
|
* e.g. when something pushes an end of a long stick, the stick does not rotate.
|
||||||
* rotate.
|
*
|
||||||
* DETAILED EXPLANATION:
|
* DETAILED EXPLANATION:
|
||||||
* Two spherical (sic) bodies, a and b, experience a perfectly inelastic
|
*
|
||||||
* collision
|
* Two spherical (sic) bodies, a and b, experience a perfectly inelastic collision
|
||||||
* along a unit vector
|
* along a unit vector
|
||||||
* _ _ _ _ _
|
* _ _ _ _ _
|
||||||
* n = (w ⨯ h) / (|w ⨯ h|),
|
* n = (w ⨯ h) / (|w ⨯ h|),
|
||||||
* _ _
|
* _ _
|
||||||
* where w and h are two noncollinear nonzero vectors on the dividing plane.
|
* where w and h are two noncollinear nonzero vectors on the dividing plane.
|
||||||
* ___ ___
|
* ___ ___
|
||||||
* Body masses and velocities are M_a, M_b and v_a, v_b, respectively.
|
* Body masses and velocities are M_a, M_b and v_a, v_b, respectively.
|
||||||
* ___ ___
|
* ___ ___
|
||||||
* After the collision desired velocities are u_a and u_b, respectively.
|
* After the collision desired velocities are u_a and u_b, respectively.
|
||||||
* _
|
* _
|
||||||
* (Notation convention: suffix 'n' denotes a vector projection onto vector
|
* (Notation convention: suffix 'n' denotes a vector projection onto vector n,
|
||||||
* n,
|
|
||||||
* and suffix 't' denotes a vector projection onto the dividing plane.)
|
* and suffix 't' denotes a vector projection onto the dividing plane.)
|
||||||
* Consider the law of conservation of momentum for axis n and the dividing
|
*
|
||||||
* plane:
|
* Consider the law of conservation of momentum for axis n and the dividing plane:
|
||||||
* ____________ ____________ ________________
|
* ____________ ____________ ________________
|
||||||
* n: ⎧ p_a_before_n + p_b_before_n = p_common_after_n;
|
* n: ⎧ p_a_before_n + p_b_before_n = p_common_after_n;
|
||||||
* ⎨ ___________ ____________
|
* ⎨ ___________ ____________
|
||||||
* t: ⎩ p_i_after_t = p_i_before_t for any i in {a, b}.
|
* t: ⎩ p_i_after_t = p_i_before_t for any i in {a, b}.
|
||||||
|
*
|
||||||
* Expressing all p_* in given terms:
|
* Expressing all p_* in given terms:
|
||||||
* ___ _ ___ _ ___ ___ ____ ____
|
* ___ _ ___ _ ___ ___ ____ ____
|
||||||
* n: ⎧ M_a * (v_a ⋅ n) + M_b * (v_b ⋅ n) = (M_a + M_b) * u_n, where u_n ≡
|
* n: ⎧ M_a * (v_a ⋅ n) + M_b * (v_b ⋅ n) = (M_a + M_b) * u_n, where u_n ≡ u_an = u_bn;
|
||||||
* u_an = u_bn;
|
* ⎨ ____ ___ _ ___ _
|
||||||
* ⎨ ____ ___ _ ___ _
|
* t: ⎩ u_it = v_i - n * (v_i ⋅ n) for any i in {a, b}.
|
||||||
* t: ⎩ u_it = v_i - n * (v_i ⋅ n) for any i in {a, b}.
|
*
|
||||||
* Therefore:
|
* Therefore:
|
||||||
* ___ _ ___ _ ___ _
|
* ___ _ ___ _ ___ _
|
||||||
* u_n = n * ( M_a/(M_a + M_b) * v_a ⋅ n + M_b/(M_a + M_b) * v_b ⋅ n );
|
* u_n = n * ( M_a/(M_a + M_b) * v_a ⋅ n + M_b/(M_a + M_b) * v_b ⋅ n );
|
||||||
|
*
|
||||||
* or, equivalently,
|
* or, equivalently,
|
||||||
* ___ _ ___ _ ___ _
|
* ___ _ ___ _ ___ _
|
||||||
* u_n = n * ( m_a * v_a ⋅ n + m_b * v_b ⋅ n ),
|
* u_n = n * ( m_a * v_a ⋅ n + m_b * v_b ⋅ n ),
|
||||||
|
*
|
||||||
* where m_a and m_b are relative masses (see below).
|
* where m_a and m_b are relative masses (see below).
|
||||||
|
*
|
||||||
* Finally,
|
* Finally,
|
||||||
* ___ ____ ___
|
* ___ ____ ___
|
||||||
* u_i = u_it + u_n for any i in {a, b}.
|
* u_i = u_it + u_n for any i in {a, b}.
|
||||||
* The usage of relative masses m_i permits a convenient generalization of
|
*
|
||||||
* the algorithm
|
* The usage of relative masses m_i permits a convenient generalization of the algorithm
|
||||||
* for infinite masses, signifying masses "significantly greater" than
|
* for infinite masses, signifying masses "significantly greater" than finite masses:
|
||||||
* finite masses:
|
*
|
||||||
* 1) If both M_a and M_b are finite, let m_i = M_i / (M_a + M_b) for any i
|
* 1) If both M_a and M_b are finite, let m_i = M_i / (M_a + M_b) for any i in {a, b}.
|
||||||
* in {a, b}.
|
* 2) If M_i is finite but M_j is infinite, let m_i = 0 and m_j = 1.
|
||||||
* 2) If M_i is finite but M_j is infinite, let m_i = 0 and m_j = 1.
|
* 3) If both M_a and M_b are infinite, let m_i = 1/2 for any i in {a, b}.
|
||||||
* 3) If both M_a and M_b are infinite, let m_i = 1/2 for any i in {a, b}.
|
|
||||||
*/
|
*/
|
||||||
|
// @formatter:on
|
||||||
private static void handlePhysics(Collision collision) {
|
private static void handlePhysics(Collision collision) {
|
||||||
// Fuck JGLM
|
// Fuck JGLM
|
||||||
Vec3 n = Vectors.grab3();
|
Vec3 n = Vectors.grab3();
|
||||||
@ -355,7 +361,7 @@ public class Collider {
|
|||||||
private static void advanceTime(
|
private static void advanceTime(
|
||||||
Collection<? extends Collideable> colls,
|
Collection<? extends Collideable> colls,
|
||||||
Collision exceptions,
|
Collision exceptions,
|
||||||
WorldData world,
|
DefaultWorldData world,
|
||||||
float step
|
float step
|
||||||
) {
|
) {
|
||||||
world.advanceTime(step);
|
world.advanceTime(step);
|
||||||
|
@ -22,6 +22,7 @@ import java.io.IOException;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import ru.windcorp.progressia.common.comms.packets.Packet;
|
import ru.windcorp.progressia.common.comms.packets.Packet;
|
||||||
|
|
||||||
@ -53,6 +54,8 @@ public abstract class CommsChannel {
|
|||||||
private State state = State.CONNECTING;
|
private State state = State.CONNECTING;
|
||||||
|
|
||||||
private final Collection<CommsListener> listeners = Collections.synchronizedCollection(new ArrayList<>());
|
private final Collection<CommsListener> listeners = Collections.synchronizedCollection(new ArrayList<>());
|
||||||
|
|
||||||
|
private final List<Packet> pendingPackets = Collections.synchronizedList(new ArrayList<>());
|
||||||
|
|
||||||
protected abstract void doSendPacket(Packet packet) throws IOException;
|
protected abstract void doSendPacket(Packet packet) throws IOException;
|
||||||
|
|
||||||
@ -101,8 +104,19 @@ public abstract class CommsChannel {
|
|||||||
public abstract void disconnect();
|
public abstract void disconnect();
|
||||||
|
|
||||||
protected void onPacketReceived(Packet packet) {
|
protected void onPacketReceived(Packet packet) {
|
||||||
|
pendingPackets.add(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void forwardPacketToListeners(Packet packet) {
|
||||||
listeners.forEach(l -> l.onPacketReceived(packet));
|
listeners.forEach(l -> l.onPacketReceived(packet));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void processPackets() {
|
||||||
|
synchronized (pendingPackets) {
|
||||||
|
pendingPackets.forEach(this::forwardPacketToListeners);
|
||||||
|
pendingPackets.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void addListener(CommsListener listener) {
|
public void addListener(CommsListener listener) {
|
||||||
listeners.add(listener);
|
listeners.add(listener);
|
||||||
|
@ -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.common.hacks;
|
package ru.windcorp.progressia.common.hacks;
|
||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
@ -29,15 +29,14 @@ import com.google.common.util.concurrent.MoreExecutors;
|
|||||||
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class had to be written because there is not legal way to instantiate a
|
* This class had to be written because there is no legal way to instantiate a
|
||||||
* non-async
|
* non-async {@link EventBus} with both a custom identifier and a custom
|
||||||
* {@link EventBus} with both a custom identifier and a custom exception
|
* exception handler. Which is a shame. Guava maintainers know about the issue
|
||||||
* handler. Which
|
* but have rejected solutions multiple times <em>without a clearly stated
|
||||||
* is a shame. Guava maintainers know about the issue but have rejected
|
* reason</em>; looks like some dirty reflection will have to do.
|
||||||
* solutions multiple
|
* <p>
|
||||||
* times <em>without a clearly stated reason</em>; looks like some dirty
|
* When explicitly referencing this class, please mention its usage in
|
||||||
* reflection will
|
* implementation notes because it is unreliable long-term.
|
||||||
* have to do.
|
|
||||||
*
|
*
|
||||||
* @author javapony
|
* @author javapony
|
||||||
*/
|
*/
|
||||||
|
@ -15,16 +15,17 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
package ru.windcorp.progressia.common.resource;
|
||||||
package ru.windcorp.progressia.server.world.tile;
|
|
||||||
|
|
||||||
import ru.windcorp.progressia.common.world.generic.GenericTileStack;
|
import java.io.InputStream;
|
||||||
import ru.windcorp.progressia.common.world.tile.TileDataStack;
|
|
||||||
import ru.windcorp.progressia.server.world.ChunkLogic;
|
|
||||||
|
|
||||||
public abstract class TileLogicStack
|
import ru.windcorp.progressia.Progressia;
|
||||||
extends GenericTileStack<TileLogicStack, TileLogic, ChunkLogic> {
|
|
||||||
|
|
||||||
public abstract TileDataStack getData();
|
public class ClasspathResourceReader implements ResourceReader {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream read(String name) {
|
||||||
|
return Progressia.class.getClassLoader().getResourceAsStream(name);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package ru.windcorp.progressia.common.resource;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
public class FilesystemResourceReader implements ResourceReader {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream read(String name) {
|
||||||
|
try {
|
||||||
|
return Files.newInputStream(Paths.get(name));
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -30,19 +30,24 @@ import org.lwjgl.BufferUtils;
|
|||||||
import com.google.common.io.ByteStreams;
|
import com.google.common.io.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() {
|
||||||
|
@ -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) {
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
}
|
@ -16,7 +16,7 @@
|
|||||||
* 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.world.tasks;
|
package ru.windcorp.progressia.common.state;
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface StateChange<T> {
|
public interface StateChange<T> {
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user