15 Commits

Author SHA1 Message Date
8c7c37c9c0 Code cleanup 2021-07-14 18:23:10 +03:00
3b24ee0531 Documentation for module system and minor changes. 2021-07-10 21:46:15 +03:00
953614a7d8 Now you cannot run tasks second time or if requirements are not met 2021-07-10 20:19:57 +03:00
7f57510ac8 Licensed module system. Minor changes
- Added license commetary to each file of module system
- Added default meta info generation for Module
2021-07-10 08:29:09 +03:00
c7842935ba Loaders are observable. Modules refactoring
- Now it is possible to monitor loaders and tasks they perform
- Now it is NOT possible to add  dublicate tasks and modules
2021-07-09 18:12:30 +03:00
f520d4b2c6 TaskManager bug fixes and code cleanup 2021-07-07 17:02:38 +03:00
5a06788652 Refactored modules system 2021-07-07 13:37:11 +03:00
56e9be727b Removed debug code 2021-07-05 11:32:58 +03:00
e795d76367 Update .gitignore
- added log files to ignore
2021-07-05 11:32:11 +03:00
a9724d9d4c Developed module loader algorithm 2021-06-30 19:59:30 +03:00
3859db5b27 Added TaskManager
- Working on modules system
2021-06-27 18:27:56 +03:00
513feb1093 Merge branch 'master' into moduleSystem 2021-06-12 21:05:15 +03:00
942c665d73 Developing module systme
- Added Module class
- minor changes in Task class
2021-01-10 15:30:05 +03:00
5a521fc131 Refactored Task class
- Now crash reports have stacktrace
- Minor changes
2021-01-09 20:52:19 +03:00
361d795ab1 Added Task class 2021-01-09 19:36:11 +03:00
504 changed files with 7345 additions and 24935 deletions

2
.gitattributes vendored
View File

@ -6,4 +6,4 @@
* text=auto eol=lf
*.bat text eol=crlf
*.nsi text eol=crlf

1
.gitignore vendored
View File

@ -37,3 +37,4 @@ build_packages/NSIS/*
!build_packages/DEB
build_packages/DEB/*
!build_packages/DEB/template
*.log

View File

@ -1,31 +1,25 @@
/*
* Build logic for Progressia
* build.gradle
*
* Please refer to
*
* docs/building/BuildScriptReference.md
*
* for user reference.
* build.gradle for Progressia
*/
plugins {
id 'java'
// GrGit
// A JGit wrapper for Groovy used to access git commit info
id 'org.ajoberstar.grgit' version '4.1.1'
// Apply the java-library plugin to add support for Java Library
id 'java-library'
/*
* Uncomment the following line to enable the Eclipse plugin.
* This is only necessary if you don't use Buildship plugin from the IDE
*/
//id 'eclipse'
}
/*
* Configure Java version
*/
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
/*
* We're Java 8 for now.
* Why? As of 2020, most users have Oracle Java, which only supports Java 8.
*/
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
compileJava {
@ -35,373 +29,289 @@ compileJava {
* However, on JDK 9 and later versions, '--release' option is required,
* which is missing on JDK 8.
*/
if (JavaVersion.current() != JavaVersion.VERSION_1_8) {
options.compilerArgs.addAll(['--release', '8'])
}
if (JavaVersion.current() != JavaVersion.VERSION_1_8) {
options.compilerArgs.addAll(['--release', '8'])
}
}
/*
* Dependencies
*/
repositories {
mavenCentral()
/*
* Windcorp Maven repository
* Currently used by:
* - ru.windcorp.fork.io.github.java-graphics:glm:1.0.1
*/
maven { url 'https://windcorp.ru/./maven' }
jcenter()
/*
* Specify Windcorp Maven repository
* Currently used by:
* - ru.windcorp.fork.io.github.java-graphics:glm:1.0.1
*/
maven { url 'https://windcorp.ru/./maven' }
}
dependencies {
// Google Guava
// A generic utilities library
implementation 'com.google.guava:guava:30.0-jre'
// Trove4j
// Provides optimized Collections for primitive types
implementation 'net.sf.trove4j:trove4j:3.0.3'
// java-graphics
// A GLM (OpenGL Mathematics) port to Java
// Unfortunately, Maven Central Repository provides an outdated version of this library, which contains several critical bugs
implementation 'ru.windcorp.fork.io.github.java-graphics:glm:1.0.1'
implementation 'com.google.guava:guava:30.0-jre'
// Trove4j
// Provides optimized Collections for primitive types
implementation 'net.sf.trove4j:trove4j:3.0.3'
// java-graphics
// A GLM (OpenGL Mathematics) port to Java
// Unfortunately, Maven Central Repository provides an outdated version of this library, which contains several critical bugs
implementation 'ru.windcorp.fork.io.github.java-graphics:glm:1.0.1'
// Log4j
// A logging library
implementation 'org.apache.logging.log4j:log4j-api:2.17.0'
implementation 'org.apache.logging.log4j:log4j-core:2.17.0'
implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.13.3'
implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.13.3'
// JUnit
// A unit-testing library
testImplementation 'junit:junit:4.13.2'
// Also see LWJGL dependencies in build_logic/lwjgl.gradle
testImplementation 'junit:junit:4.12'
// See LWJGL dependencies below
}
/*
* Version resolution
* Progressia uses the following LWJGL libraries:
* - Core libraries
* - OpenGL
* - OpenAL
* - GLFW
* - STB
*/
import org.ajoberstar.grgit.*
/*
* LWJGL
* (auto-generated script)
* ((here be dragons))
*/
// Pattern: vMAJOR.MINOR.PATCH[-SUFFIX]
project.ext.tagFormat = /^v(\d+)\.(\d+)\.(\d+)(-[\w\-]*)?$/
import org.gradle.internal.os.OperatingSystem
String version_parseVersion(String tag, boolean increment) {
try {
project.ext.lwjglVersion = "3.2.3"
switch (OperatingSystem.current()) {
case OperatingSystem.LINUX:
def osArch = System.getProperty("os.arch")
project.ext.lwjglNatives = osArch.startsWith("arm") || osArch.startsWith("aarch64")
? "natives-linux-${osArch.contains("64") || osArch.startsWith("armv8") ? "arm64" : "arm32"}"
: "natives-linux"
break
case OperatingSystem.MAC_OS:
project.ext.lwjglNatives = "natives-macos"
break
case OperatingSystem.WINDOWS:
project.ext.lwjglNatives = System.getProperty("os.arch").contains("64") ? "natives-windows" : "natives-windows-x86"
break
}
dependencies {
implementation 'org.jetbrains:annotations:20.1.0'
implementation platform("org.lwjgl:lwjgl-bom:$lwjglVersion")
implementation "org.lwjgl:lwjgl"
implementation "org.lwjgl:lwjgl-glfw"
implementation "org.lwjgl:lwjgl-openal"
implementation "org.lwjgl:lwjgl-opengl"
implementation "org.lwjgl:lwjgl-stb"
def data = (tag =~ tagFormat)[0]
runtimeOnly "org.lwjgl:lwjgl::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-glfw::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-openal::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-opengl::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-stb::$lwjglNatives"
}
// LWJGL END
/*
* Tasks
*/
/*
* Additional native libraries specification
*/
project.ext.platforms = new HashSet<>()
task addNativeDependencies {
doFirst {
def archs = project.ext.platforms
def major = data[1]
def minor = data[2]
def patch = data[3] as int
def suffix = data[4] ?: ''
if (increment) {
def oldVersion = "$major.$minor.$patch$suffix"
patch++
def newVersion = "$major.$minor.$patch$suffix"
logger.info "Version parsed from Git: $oldVersion, incremented to $newVersion"
return newVersion
} else {
def newVersion = "$major.$minor.$patch$suffix"
logger.info "Version parsed from Git: $newVersion"
return newVersion
switch (archs.size()) {
case 0:
println "Adding LWJGL native dependencies for local platform only:\n\t$lwjglNatives"
archs.add project.ext.lwjglNatives
break
case 1:
println "Adding LWJGL native dependencies for platform\n\t" + archs.get(0)
break
default:
println "Adding LWJGL native dependencies for platforms:\n\t" + archs.join("\n\t")
}
} catch (any) {
logger.warn "Could not parse version from tag \"$tag\""
return tag
if (project.ext.lwjglNatives.isEmpty()) println "WTF"
dependencies {
archs.each { arch ->
runtimeOnly "org.lwjgl:lwjgl::$arch"
runtimeOnly "org.lwjgl:lwjgl-glfw::$arch"
runtimeOnly "org.lwjgl:lwjgl-openal::$arch"
runtimeOnly "org.lwjgl:lwjgl-opengl::$arch"
runtimeOnly "org.lwjgl:lwjgl-stb::$arch"
}
}
}
}
compileJava.mustRunAfter addNativeDependencies // Make sure runtimeOnly has not been resolved
task requestLinuxDependencies {
description 'Adds linux, linux-arm64 and linux-arm32 native libraries to built artifacts.'
doFirst {
project.ext.platforms.addAll(['natives-linux', 'natives-linux-arm64', 'natives-linux-arm32'])
}
}
task requestWindowsDependencies {
description 'Adds windows and windows-x86 native libraries to built artifacts.'
doFirst {
project.ext.platforms.addAll(['natives-windows', 'natives-windows-x86'])
}
}
task requestMacOSDependencies {
description 'Adds macos native libraries to built artifacts.'
doFirst {
project.ext.platforms.addAll(['natives-macos'])
}
}
Tag version_findRelevantTag(Grgit git) {
def tags = git.tag.list()
def commits = [ git.head() ]
def visited = new HashSet<>()
while (true) {
if (commits.isEmpty()) return null
def nextCommits = new HashSet<>()
def formatSpecificationPrinted = false
for (def commit : commits) {
def tag = tags.findAll { it.commit == commit } ?.max { it.dateTime }
if (tag != null) {
if (tag.name ==~ tagFormat) {
return tag
} else {
if (!formatSpecificationPrinted) {
formatSpecificationPrinted = true
logger.info 'Expecting tag format: vMAJOR.MINOR.PATCH[-SUFFIX]'
}
logger.info 'Ignoring tag due to invalid format: {}', tag.name
}
}
nextCommits.addAll commit.parentIds.collect(git.resolve.&toCommit)
}
visited.addAll commits
nextCommits.removeAll visited
commits = nextCommits
}
def dependencySpecificationTasks = tasks.findAll { task -> task.name.startsWith('request') && task.name.endsWith('Dependencies') }
task requestCrossPlatformDependencies {
description 'Adds native libraries for all available platforms to built artifacts.'
dependsOn dependencySpecificationTasks
}
task resolveVersion {
description 'Resolves version information from Git repository or project properties.'
addNativeDependencies.mustRunAfter dependencySpecificationTasks
/*
* Determines if the provided dependency should be packaged
*/
def isDependencyRequested(String dep) {
if (dep.endsWith(".jar")) {
dep = dep.substring(0, dep.length() - ".jar".length())
}
return !dep.contains("natives-") ||
project.ext.platforms.contains(dep.substring(dep.indexOf("natives-"), dep.length()))
}
/*
* Manifest specification
*/
task specifyLocalManifest {
dependsOn addNativeDependencies // Make sure all native dependencies are specified
doFirst {
try {
def git = Grgit.open(dir: project.projectDir)
project.ext.commit = git.head().id
project.ext.branch = git.branch.current().name
if (project.version != 'unspecified') {
// Leave version as-is
return
}
def tag = version_findRelevantTag(git)
if (tag == null) {
String suffix
if (project.hasProperty('buildId')) {
suffix = project.buildId;
} else {
suffix = java.time.ZonedDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern('yyyy_MM_dd'))
}
project.version = "999.0.0-$suffix"
logger.warn 'Git repository does not contain an applicable tag, using dummy version {}\nSpecify version with -Pversion=1.2.3 or create a Git tag named v1.2.3', project.version
def classPath = []
configurations.runtimeClasspath.each {
if (isDependencyRequested(it.getName())) {
classPath.add("lib/" + it.getName())
} else {
project.version = version_parseVersion(tag.name, tag.commit != git.head())
println "\tRemoving from JAR classpath (not requested): " + it.getName()
}
} catch (org.eclipse.jgit.errors.RepositoryNotFoundException e) {
if (project.version == 'unspecified') project.version = 'dev'
project.ext.commit = '-'
project.ext.branch = '-'
logger.warn 'No Git repository found in project root, using dummy version {}\nSpecify version with -Pversion=1.2.3 or create a Git tag named v1.2.3', project.version
}
}
if (classPath.size() == configurations.runtimeClasspath.size()) {
println "Nothing removed from JAR classpath"
}
doLast {
if (!project.hasProperty('buildId')) {
project.ext.buildId = '-'
jar {
manifest {
attributes(
"Main-Class": "ru.windcorp.progressia.client.ProgressiaClientMain",
"Class-Path": configurations.runtimeClasspath.collect { "lib/" + it.getName() } .findAll { isDependencyRequested(it) } .join(' ')
)
}
}
}
}
jar.dependsOn specifyLocalManifest
/*
* Configure JAR manifest
*/
task configureManifest {
description 'Populates JAR manifest with Main-Class, Class-Path and version metadata.'
jar.dependsOn configureManifest
dependsOn resolveVersion
doFirst {
jar.manifest.attributes(
'Main-Class': 'ru.windcorp.progressia.client.ProgressiaClientMain',
'Class-Path': configurations.runtimeClasspath.collect { "lib/${it.name}" } .join(' '),
'Specification-Title': 'Progressia',
'Implementation-Title': 'Progressia',
'Implementation-Version': project.version,
'Implementation-Version-Git-Commit': project.commit,
'Implementation-Version-Git-Branch': project.branch,
'Implementation-Version-BuildId': project.buildId,
)
}
}
/*
* Copy libraries into buil/libs/lib directory, next to Progressia.jar
*/
task exportLibs(type: Sync) {
description 'Copies runtime libraries into a subdirectory next to the output JAR.'
jar.dependsOn exportLibs
from configurations.runtimeClasspath
into 'build/libs/lib'
}
/*
* Apply LWJGL logic
*/
apply from: 'build_logic/lwjgl.gradle'
/*
* Packaging working directory configuration
*/
import java.nio.file.*
import java.nio.file.attribute.*
task createPackagingDirs() {
description 'Resets build/tmp/packaging directory.'
doLast {
def tmpDir = buildDir.toPath().resolve 'tmp/packaging'
def nuke = new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException {
if (e == null) {
Files.delete(dir);
return FileVisitResult.CONTINUE;
} else {
// directory iteration failed
throw e;
}
}
}
// Fckn nuke tmpDir from orbit
// I'm so done with deleting file trees in Java/Groovy/whatever
// ...Not using File.deleteDir() because the latter recurses into symlinks, and we don't want to wipe build/libs/lib
if (Files.exists(tmpDir)) {
Files.walkFileTree tmpDir, nuke
}
Files.createDirectories tmpDir.resolve('workingDir')
Files.createDirectories buildDir.toPath().resolve('packages')
}
}
task linkBuildOutputForPackaging() {
description 'Symlinks the contents of build/libs into packaging working directory.'
dependsOn build
dependsOn createPackagingDirs
onlyIf { preparePackaging.ext.mode == 'symlink' }
doLast {
def from = buildDir.toPath().resolve 'libs'
def into = buildDir.toPath().resolve "tmp/packaging/workingDir/${preparePackaging.ext.buildDest}"
Files.createDirectories into
Files.list(from).each {
def fileName = it.fileName.toString()
// Exclude all JARs except the current one
if (fileName ==~ "${project.name}.*\\.jar" && fileName != tasks.jar.archiveFileName.get())
return
Files.createSymbolicLink into.resolve(it.fileName), it
}
}
}
task copyBuildOutputForPackaging(type: Copy) {
description 'Copies the contents of build/libs into packaging working directory.'
dependsOn build
dependsOn createPackagingDirs
onlyIf { preparePackaging.ext.mode == 'copy' }
from 'build/libs'
filesMatching("${project.name}*.jar") {
include tasks.jar.archiveFileName.get()
}
into "build/tmp/packaging/workingDir/${ -> preparePackaging.ext.buildDest}"
}
task preparePackaging {
preparePackaging.ext.buildDest = ''
preparePackaging.ext.mode = 'symlink'
dependsOn createPackagingDirs
dependsOn linkBuildOutputForPackaging
dependsOn copyBuildOutputForPackaging
}
/*
* Apply all packaging scripts
*/
new File(projectDir, 'build_logic/packaging').list().each {
apply from: "build_logic/packaging/$it/script.gradle"
}
/*
* Ensure no more than one packaging task is scheduled
* Library export
*/
gradle.taskGraph.whenReady { graph ->
if (graph.allTasks.count { it.name ==~ /package[^_]*/ } > 1) {
def offenders = graph.allTasks.findAll { it.name ==~ /package[^_]*/ }
throw new GradleException("Cannot execute multiple package tasks within a single build\n" +
"\tOffending tasks: $offenders")
}
task exportLibs(type: Sync) {
mustRunAfter addNativeDependencies
into libsDirectory.get().getAsFile().getPath() + "/lib"
exclude { !isDependencyRequested(it.getName()) }
from configurations.runtimeClasspath
}
jar.dependsOn(exportLibs)
/*
* Convenience build tasks
* Packaging
*/
task packageDebian(type: Exec) {
description 'Builds the project and creates a Debain package.'
group 'Progressia'
dependsOn build
dependsOn requestLinuxDependencies
commandLine './buildPackages.sh', 'debian'
doLast {
println "Debian package available in build_packages/"
}
}
task packageWindows(type: Exec) {
description 'Builds the project and creates a Windows installer.'
group 'Progressia'
dependsOn build
dependsOn requestWindowsDependencies
commandLine './buildPackages.sh', 'windows'
doLast {
println "Windows installer available in build_packages/"
}
}
task buildCrossPlatform {
description 'Builds the project including native libraries for all available platforms.'
group 'Progressia'
dependsOn requestCrossPlatformDependencies
dependsOn build
doLast {
logger.info 'Native libraries for all platforms have been added'
}
description 'Builds the project including native libraries for all available platforms.'
group 'Progressia'
dependsOn requestCrossPlatformDependencies
dependsOn build
doLast {
println "Native libraries for all platforms have been added"
}
}
task buildLocal {
description "Builds the project including only native libraries for current platform (${lwjgl.localArch})."
group 'Progressia'
dependsOn build
doLast {
logger.info "Native libraries only for platform ${lwjgl.localArch} have been added"
}
description "Builds the project including only native libraries for current platform ($lwjglNatives)."
group 'Progressia'
dependsOn build
doLast {
println "Native libraries only for platform $lwjglNatives have been added"
}
}

191
buildPackages.sh Executable file
View File

@ -0,0 +1,191 @@
#!/bin/bash
#
# 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/>.
#
echoerr() { echo "$@" 1>&2; }
buildDebianPackage() {
# Commands that must be available to execute this action
requiredCommands='dpkg-deb fakeroot'
# Package name. Sync with control file manually!
name='progressia-techdemo'
# Version that the package will receive. Sync with control file manually!
version='1.0_all'
# This directory will be copied into $tmpDir
templateDirectory="build_packages/DEB/template"
# Files that must be present
requiredFiles="$templateDirectory/DEBIAN/control"
nameAndVersion="$name-$version"
tmpDir="build_packages/DEB/$nameAndVersion"
outputFile="build_packages/DEB/$nameAndVersion.deb"
echo "Checking environment to build Debian package"
for item in $requiredCommands; do
if command -v "$item" &> /dev/null; then
echo "- $item found"
else
echoerr "Command $item not found, cannot package"
exit 100
fi
done
for file in $requiredFiles; do
if ! [ -r "$file" ]; then
echoerr "$file is missing or not readable, cannot package"
exit 101
else
echo "- $file is present and readable"
fi
done
echo "Environment OK; packaging Debian package"
exitCode=0
{
shareDir="$tmpDir/usr/share/progressia"
mkdir -p "$tmpDir" &&
mkdir -p "$shareDir" &&
cp -r "$templateDirectory"/* "$tmpDir" &&
cp -r 'build/libs/lib' "$shareDir/lib" &&
cp 'build/libs/Progressia.jar' "$shareDir/Progressia.jar" &&
echo "------ DPKG-DEB ------" &&
fakeroot dpkg-deb --build "$tmpDir" &&
echo "---- DPKG-DEB END ----" &&
mv "$outputFile" build_packages
} || {
echoerr "Could not create Debian package"
exitCode=1
}
{
if [ -d "$tmpDir" ]; then
rm -r "$tmpDir"
fi
echo "Cleaned up"
} || {
echoerr "Could not clean up after packaging Debian package"
exitCode=2
}
exit "$exitCode"
}
buildWindowsInstaller() {
# Commands that must be available to execute this action
requiredCommands='makensis'
# NSIS configuration file that must be present
configurationFile='build_packages/NSIS/ProgressiaInstaller.nsi'
# File that will be output
outputFile='build_packages/NSIS/ProgressiaInstaller.exe'
echo "Checking environment to build Windows installer"
for item in $requiredCommands; do
if command -v "$item" &> /dev/null; then
echo "- $item found"
else
echoerr "Command $item not found, cannot build"
exit 100
fi
done
if ! [ -r "$configurationFile" ]; then
echoerr "$configurationFile is missing or not readable, cannot build"
exit 101
else
echo "- $configurationFile is present and readable"
fi
echo "Environment OK; building Windows installer"
exitCode=0
{
cp -r 'build/libs/lib' 'build_packages/NSIS/lib' &&
cp 'build/libs/Progressia.jar' 'build_packages/NSIS/Progressia.jar' &&
cp 'LICENSE' 'build_packages/NSIS/LICENSE.txt' &&
echo "------ NSIS ------" &&
makensis "$configurationFile" &&
echo "---- NSIS END ----" &&
mv "$outputFile" build_packages
} || {
echoerr "Could not build Windows installer"
exitCode=1
}
{
if [ -d 'build_packages/NSIS/lib' ]; then
rm -r 'build_packages/NSIS/lib'
fi
if [ -e 'build_packages/NSIS/Progressia.jar' ]; then
rm 'build_packages/NSIS/Progressia.jar'
fi
if [ -e 'build_packages/NSIS/LICENSE.txt' ]; then
rm 'build_packages/NSIS/LICENSE.txt'
fi
echo "Cleaned up"
} || {
echoerr "Could not clean up after building Windows installer"
exitCode=2
}
exit "$exitCode"
}
printUsage() {
echoerr "Usage: $0 TARGET"
echoerr " where TARGET is 'debian' or 'windows'"
}
if [ -n "$2" ]; then
echoerr "Too many arguments."
printUsage
exit 202
fi
case "$1" in
"debian")
buildDebianPackage
;;
"windows")
buildWindowsInstaller
;;
"")
echoerr "No action specified"
printUsage
exit 200
;;
"--help" | "-help" | "help" | "?")
printUsage
;;
*)
echoerr "Unknown action '$1'"
printUsage
exit 201
;;
esac

View File

@ -1,117 +0,0 @@
/*
* Build logic for Progressia
* LWJGL dependency logic
*/
project.ext.lwjgl = new HashMap<>()
// Version of LWJGL
lwjgl.version = '3.2.3'
/*
* Target platforms for current operation.
* This is filled in by the request* tasks. This is referenced by the addLwjglNatives task.
* When empty, current platform is assumed.
*/
lwjgl.targets = new HashSet<>()
// LWJGL components. To include org.lwjgl:lwjgl-foobar, add 'foobar' to this list.
lwjgl.libraries = [
'opengl',
'glfw',
'openal',
'stb'
]
// Determine the architecture of the build environment
import org.gradle.internal.os.OperatingSystem
switch (OperatingSystem.current()) {
case OperatingSystem.LINUX:
def osArch = System.getProperty('os.arch')
lwjgl.localArch = osArch.startsWith('arm') || osArch.startsWith('aarch64')
? "linux-${osArch.contains('64') || osArch.startsWith('armv8') ? 'arm64' : 'arm32'}"
: 'linux'
break
case OperatingSystem.MAC_OS:
lwjgl.localArch = 'macos'
break
case OperatingSystem.WINDOWS:
lwjgl.localArch = System.getProperty('os.arch').contains('64') ? 'windows' : 'windows-x86'
break
}
// Declare pure-Java dependencies
dependencies {
// BOM
implementation platform("org.lwjgl:lwjgl-bom:${lwjgl.version}")
// Core
implementation 'org.lwjgl:lwjgl'
// Components
lwjgl.libraries.each { implementation "org.lwjgl:lwjgl-$it" }
}
/*
* Adds LWJGL native libraries to runtimeOnly configuration
*/
task lwjgl_addNativesToRuntimeOnly {
// Make sure runtimeOnly has not been resolved
compileJava.dependsOn lwjgl_addNativesToRuntimeOnly
configureManifest.dependsOn lwjgl_addNativesToRuntimeOnly
exportLibs.dependsOn lwjgl_addNativesToRuntimeOnly
doFirst {
if (project.hasProperty('forceTargets')) {
try {
def oldTargets = lwjgl.targets.join(',')
lwjgl.targets.clear()
lwjgl.targets.addAll project.forceTargets.split(',')*.trim().collect { it == 'local' ? lwjgl.localArch : it }
logger.info 'Overriding selected platforms {} with {}', oldTargets, lwjgl.targets.join(',')
} catch (Exception e) {
throw new GradleException("Could not parse forceTargets \"${project.forceTargets}\", expecting platform-1,platform-2,local", e)
}
}
if (lwjgl.targets.isEmpty()) {
logger.info 'Adding LWJGL native dependencies for local platform only: {}', lwjgl.localArch
lwjgl.targets.add lwjgl.localArch
} else {
logger.info 'Adding LWJGL native dependencies for platforms: {}', lwjgl.targets.sort().join(', ')
}
dependencies {
lwjgl.targets.each { target ->
runtimeOnly "org.lwjgl:lwjgl::natives-$target"
lwjgl.libraries.each { lib ->
runtimeOnly "org.lwjgl:lwjgl-$lib::natives-$target"
}
}
}
}
}
task requestCrossPlatformDependencies {
description 'Adds LWJGL natives for all available platforms.'
lwjgl_addNativesToRuntimeOnly.mustRunAfter requestCrossPlatformDependencies
}
def requestTask(String name, String... targets) {
def theTask = task "request${name}Dependencies"
theTask.doFirst {
lwjgl.targets.addAll targets
}
theTask.description "Adds LWJGL natives for $name (${targets.join(', ')})."
requestCrossPlatformDependencies.dependsOn theTask
lwjgl_addNativesToRuntimeOnly.mustRunAfter theTask
}
requestTask 'Linux', 'linux', 'linux-arm32', 'linux-arm64'
requestTask 'Windows', 'windows', 'windows-x86'
requestTask 'MacOS', 'macos'

View File

@ -1,38 +0,0 @@
task packageDeb_processResources(type: Copy) {
dependsOn resolveVersion
dependsOn preparePackaging
from 'src/packaging/deb'
filesMatching('DEBIAN/control') {
expand(version: { -> project.version})
}
into 'build/tmp/packaging/workingDir'
}
task packageDeb_configure() {
preparePackaging.mustRunAfter packageDeb_configure
doLast {
tasks.preparePackaging.ext.buildDest = '/usr/share/progressia'
tasks.preparePackaging.ext.mode = 'copy'
}
}
task packageDeb(type: Exec) {
description 'Builds the project and creates a Debian package.'
group 'Progressia'
dependsOn packageDeb_configure
dependsOn requestLinuxDependencies
dependsOn build
dependsOn preparePackaging
dependsOn packageDeb_processResources
executable 'dpkg-deb'
args '--root-owner-group'
args '--build', 'build/tmp/packaging/workingDir'
args 'build/packages'
}

View File

@ -1,50 +0,0 @@
task packageNsis_processResources(type: Copy) {
dependsOn preparePackaging
from ('src/packaging/nsis') {
exclude 'left_side.png'
}
from('LICENSE') {
rename 'LICENSE', 'LICENSE.txt'
}
into 'build/tmp/packaging/workingDir'
}
task packageNsis_generateIcon(type: Exec) {
mustRunAfter preparePackaging
executable 'convert'
args files('src/main/resources/assets/icons/*.original.png').files*.path
args 'build/tmp/packaging/workingDir/logo.ico'
}
task packageNsis_generateLeftSide(type: Exec) {
mustRunAfter preparePackaging
executable 'convert'
args 'src/packaging/nsis/left_side.png'
args '-alpha', 'off'
args 'BMP3:build/tmp/packaging/workingDir/left_side.bmp'
}
task packageNsis(type: Exec) {
description 'Builds the project and creates a Windows NSIS installer.'
group 'Progressia'
dependsOn requestWindowsDependencies
dependsOn build
dependsOn resolveVersion
dependsOn preparePackaging
dependsOn packageNsis_processResources
dependsOn packageNsis_generateIcon
dependsOn packageNsis_generateLeftSide
executable 'makensis'
args '-NOCONFIG'
args "-DPROJECT_NAME=${project.name}"
args "-DPROJECT_VERSION=${ -> project.version}"
args "-DMAIN_JAR_FILE=${ -> project.tasks.jar.archiveFileName.get()}"
args "-DOUTPUT_DIR=${project.buildDir.absolutePath}/packages"
args 'build/tmp/packaging/workingDir/config.nsi'
}

View File

@ -1,41 +0,0 @@
task packageZip_processResources(type: Copy) {
dependsOn preparePackaging
from ('src/packaging/zip') {
filesMatching('start.*') {
filter(
org.apache.tools.ant.filters.ReplaceTokens,
tokens: [mainJarFile: project.tasks.jar.archiveFileName.get()]
)
}
}
from ('src/main/resource/assets/icons/logo256.original.png') {
rename 'logo256.original.png', 'logo.png'
}
from('LICENSE') {
rename 'LICENSE', 'LICENSE.txt'
}
into 'build/tmp/packaging/workingDir'
}
task packageZip(type: Zip) {
description 'Builds the project and creates a cross-platform ZIP package.'
group 'Progressia'
dependsOn resolveVersion
dependsOn requestCrossPlatformDependencies
dependsOn build
dependsOn preparePackaging
dependsOn packageZip_processResources
archiveBaseName = project.name
archiveAppendix = 'universal'
doFirst {
archiveVersion = project.version
}
from 'build/tmp/packaging/workingDir'
destinationDirectory = file('build/packages')
}

View File

@ -1,8 +1,8 @@
Package: progressia
Version: ${version}
Package: progressia-techdemo
Version: 1.0
Section: custom
Priority: optional
Architecture: all
Maintainer: Javapony <kvadropups@gmail.com>
Depends: java8-runtime
Description: Progressia - a 3D sandbox survival game
Description: Progressia Techdemo release

View File

@ -1,164 +1,161 @@
;NSIS Modern User Interface
;Welcome/Finish Page Example Script
;Written by Joost Verburg
;--------------------------------
;Include Modern UI
!include "MUI2.nsh"
;--------------------------------
;General
; Expecting the following symbols from caller:
; PROJECT_NAME
; PROJECT_VERSION
; MAIN_JAR_FILE
; 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 "${PROJECT_NAME}"
OutFile "${OUTPUT_DIR}/${PROJECT_NAME}-${PROJECT_VERSION}-installer.exe"
Unicode True
;Default installation folder
InstallDir "$PROGRAMFILES\${PROJECT_NAME}"
;Get installation folder from registry if available
InstallDirRegKey HKLM "Software\${PROJECT_NAME}" ""
;Request application privileges for Windows Vista
RequestExecutionLevel admin
;--------------------------------
;Interface Settings
!define MUI_ABORTWARNING
;--------------------------------
;Pages
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE "LICENSE.txt"
!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!define MUI_FINISHPAGE_RUN
!define MUI_FINISHPAGE_RUN_TEXT "Start ${PROJECT_NAME}"
!define MUI_FINISHPAGE_RUN_FUNCTION "LaunchLink"
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_WELCOME
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_COMPONENTS
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_UNPAGE_FINISH
;--------------------------------
;Languages
!insertmacro MUI_LANGUAGE "English"
;--------------------------------
;Installer Sections
Section "Install ${PROJECT_NAME}" SEC0000
SectionIn RO ;Make it read-only
SetOutPath "$INSTDIR"
SetOverwrite on
;Files
File "${MAIN_JAR_FILE}"
File logo.ico
File /r lib
;Store installation folder
WriteRegStr HKLM "SOFTWARE\${PROJECT_NAME}" "Install_Dir" "$INSTDIR"
;Create uninstaller
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PROJECT_NAME}" "DisplayName" "${PROJECT_NAME} (remove only)"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PROJECT_NAME}" "UninstallString" "$INSTDIR\Uninstall.exe"
WriteUninstaller "$INSTDIR\Uninstall.exe"
SectionEnd
Section "Create Desktop Shortcut" SEC0001
SetOutPath "$APPDATA\${PROJECT_NAME}"
CreateShortCut "$DESKTOP\${PROJECT_NAME}.lnk" "$INSTDIR\${MAIN_JAR_FILE}" "" "$INSTDIR\logo.ico"
SectionEnd
Section "Start Menu Shortcuts" SEC0002
CreateDirectory "$SMPROGRAMS\${PROJECT_NAME}"
CreateShortcut "$SMPROGRAMS\${PROJECT_NAME}\Uninstall.lnk" "$INSTDIR\Uninstall.exe"
CreateShortcut "$SMPROGRAMS\${PROJECT_NAME}\${PROJECT_NAME}.lnk" "$INSTDIR\${MAIN_JAR_FILE}" "" "$INSTDIR\logo.ico"
SectionEnd
;--------------------------------
;Uninstaller Section
Section "Uninstall"
;ADD YOUR OWN FILES HERE...
Delete $INSTDIR\Uninstall.exe
Delete "$INSTDIR\${MAIN_JAR_FILE}"
Delete $INSTDIR\lib\*.*
Delete $INSTDIR\logo.ico
RMDir $INSTDIR\lib
Delete $DESKTOP\${PROJECT_NAME}.lnk
Delete $SMPROGRAMS\${PROJECT_NAME}\Uninstall.lnk
Delete $SMPROGRAMS\${PROJECT_NAME}\${PROJECT_NAME}.lnk
RMDir $INSTDIR
RMDir /r $SMPROGRAMS\${PROJECT_NAME}
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PROJECT_NAME}"
DeleteRegKey HKLM "Software\${PROJECT_NAME}"
SectionEnd
Section "un.Remove user data"
RMDir /r "$APPDATA\${PROJECT_NAME}"
SectionEnd
;--------------------------------
;Functions
Function LaunchLink
SetOutPath "$APPDATA\${PROJECT_NAME}"
ExecShell "" "$INSTDIR\${MAIN_JAR_FILE}"
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
;NSIS Modern User Interface
;Welcome/Finish Page Example Script
;Written by Joost Verburg
;--------------------------------
;Include Modern UI
!include "MUI2.nsh"
;--------------------------------
;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 "${PROJECT_NAME}"
OutFile "${PROJECT_NAME}Installer.exe"
Unicode True
;Default installation folder
InstallDir "$PROGRAMFILES\${PROJECT_NAME}"
;Get installation folder from registry if available
InstallDirRegKey HKLM "Software\${PROJECT_NAME}" ""
;Request application privileges for Windows Vista
RequestExecutionLevel admin
;--------------------------------
;Interface Settings
!define MUI_ABORTWARNING
;--------------------------------
;Pages
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE "LICENSE.txt"
!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!define MUI_FINISHPAGE_RUN
!define MUI_FINISHPAGE_RUN_TEXT "Start ${PROJECT_NAME}"
!define MUI_FINISHPAGE_RUN_FUNCTION "LaunchLink"
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_WELCOME
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_COMPONENTS
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_UNPAGE_FINISH
;--------------------------------
;Languages
!insertmacro MUI_LANGUAGE "English"
;--------------------------------
;Installer Sections
Section "Install ${PROJECT_NAME}" SEC0000
SectionIn RO ;Make it read-only
SetOutPath "$INSTDIR"
SetOverwrite on
;Files
File Progressia.jar
File logo.ico
File /r lib
;Store installation folder
WriteRegStr HKLM SOFTWARE\Progressia "Install_Dir" "$INSTDIR"
;Create uninstaller
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PROJECT_NAME}" "DisplayName" "${PROJECT_NAME} (remove only)"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PROJECT_NAME}" "UninstallString" "$INSTDIR\Uninstall.exe"
WriteUninstaller "$INSTDIR\Uninstall.exe"
SectionEnd
Section "Create Desktop Shortcut" SEC0001
SetOutPath "$APPDATA\${PROJECT_NAME}"
CreateShortCut "$DESKTOP\${PROJECT_NAME}.lnk" "$INSTDIR\${PROJECT_NAME}.jar" "" "$INSTDIR\logo.ico"
SectionEnd
Section "Start Menu Shortcuts" SEC0002
CreateDirectory "$SMPROGRAMS\${PROJECT_NAME}"
CreateShortcut "$SMPROGRAMS\${PROJECT_NAME}\Uninstall.lnk" "$INSTDIR\Uninstall.exe"
CreateShortcut "$SMPROGRAMS\${PROJECT_NAME}\${PROJECT_NAME}.lnk" "$INSTDIR\${PROJECT_NAME}.jar" "" "$INSTDIR\logo.ico"
SectionEnd
;--------------------------------
;Uninstaller Section
Section "Uninstall"
;ADD YOUR OWN FILES HERE...
Delete $INSTDIR\Uninstall.exe
Delete $INSTDIR\Progressia.jar
Delete $INSTDIR\lib\*.*
Delete $INSTDIR\logo.ico
RMDir $INSTDIR\lib
Delete $DESKTOP\${PROJECT_NAME}.lnk
Delete $SMPROGRAMS\${PROJECT_NAME}\Uninstall.lnk
Delete $SMPROGRAMS\${PROJECT_NAME}\${PROJECT_NAME}.lnk
RMDir $INSTDIR
RMDir /r $SMPROGRAMS\${PROJECT_NAME}
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PROJECT_NAME}"
DeleteRegKey HKLM "Software\${PROJECT_NAME}"
SectionEnd
Section "un.Remove user data"
RMDir /r "$APPDATA\${PROJECT_NAME}"
SectionEnd
;--------------------------------
;Functions
Function LaunchLink
SetOutPath "$APPDATA\${PROJECT_NAME}"
ExecShell "" "$INSTDIR\${PROJECT_NAME}.jar"
FunctionEnd
;--------------------------------
;Descriptions
;Language strings
LangString DESC_SecDummy ${LANG_ENGLISH} "Install ${PROJECT_NAME}."
;Assign language strings to sections
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
!insertmacro MUI_DESCRIPTION_TEXT ${SEC0000} $(DESC_SecDummy)
!insertmacro MUI_FUNCTION_DESCRIPTION_END

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

View File

@ -1,12 +0,0 @@
# Progressia Region File
## Description
The `.progressia_region` file type is used for all region files in the game Progressia. Each region file contains a cube of 16x16x16 chunks.
## Header
The header of the file is 16&nbsp;400 bytes. Every file starts with the string byte sequence `\x50\x52\x4F\x47` (UTF-8 for `PROG`), followed with the three integer values of the region position, in region coordinates. After this, there is exactly 16KiB of space in the header, which stores the offsets to the chunks' data. This space holds an integer, 4 bytes, for each chunk in the region. The integer value starts at 0 for every chunk, and is changed to the location of the chunk data once created. These are indexed in order by flattening the 3D in-chunk coordinates into a number between 0 and 4095 according to the formula `offset = 256*x+ 16*y + z` for chunk at (x, y, z). To convert from this offset value to the offset in bytes, use `byte_offset = 16400 + 64*n`.
## Sectors
Sectors are what is used to store chunk data, and are not linear, but are followed until they reach an ending block. Each is 64 bytes, which is used in the header section to find the byte offset. Each sector starts with a identification byte, followed by the sector data.
0. Ending - This sector is empty, and marks the end of the chunk data (This may change in the future.
1. Data - This sector contains chunk data for a single chunk. The second byte of this sector contains a counter byte, which is a form of "checksum" to make sure that the program is reading the proper sectors in order. This starts at 0 for the first data sector and increments by one for each new data sector.
2. Partition Link - This sector only contains another offset value, which is where the next sector is. This allows for infinite chunk size, avoiding "chunk dupes" as were present in Minecraft without reverting any chunks.
3. Bulk Data - These would be used for many chunks in the same region that contain exactly the same data, e.g. all solid chunks underground. Exists so the program knows not to overwrite them, and just make new chunk data if modified. Not yet implemented.

View File

@ -1,10 +1,8 @@
# Build Guide
This document is a guide to building Progressia from source. For quick reference, see
[Build Script Reference](BuildScriptReference.md).
This document is a guide to building Progressia from source.
Compilation should be possible on all platforms that support JDK 8 or later, however, packaging scripts require
additional programs in `PATH`.
Compilation should be possible on all platforms that support JDK 8 or later, however, packaging scripts require Bash.
This guide assumes you are familiar with using a terminal or Windows Command Prompt or PowerShell.
@ -152,46 +150,54 @@ GNU/Linux and Windows natives:
./gradlew build requestLinuxDependencies requestWindowsDependencies
```
For finer control please edit `build.gradle` manually by adding the desired natives to the `project.ext.platforms` set like so:
```
project.ext.platforms = new HashSet<>()
project.ext.platforms.add 'natives-windows-x86'
```
## Packaging
A universal ZIP distribution, a Debian package and a Windows NSIS installer may be created automatically by the build
script.
### Creating a universal ZIP package
A universal cross-platform ZIP archive can be created with the following Gradle task:
```
./gradlew packageZip
```
Gradle will then build all artifacts necessary to run the game on all available platforms and package game files,
libraries, launch scripts, etc. into a compressed ZIP archive.
The resulting file can be found in `build/packages/`
A Debian package and a Windows installer can be created automatically on systems that support Bash. These tasks are delegated
by Gradle to `buildPackages.sh` in repository root. This script checks the environment and assembles the requested output; the
resulting files are moved into `build_packages`.
### Creating a Debian package
A Debian package can be created with the following Gradle task:
```
./gradlew packageDeb
./gradlew packageDebian
```
Gradle will then build all artifacts necessary to run the game on GNU/Linux (all three architectures) and invoke
`dpkg-deb`. Commands `dpkg-deb` must be available in system path in order to build the package.
`./buildPackages.sh debian`. Commands `dpkg-deb` and `fakeroot` must be available in system path in order to build the package.
### Creating a Windows installer
A Windows NSIS installer can be created with the following Gradle task:
A Windows installer can be created with the following Gradle task:
```
./gradlew packageNsis
./gradlew packageWindows
```
Gradle will then build all artifacts necessary to run the game on Windows (both x64 and x86 architectures) and invoke
`makensis`.
`./buildPackages.sh windows`.
Windows installers are implemented with [NSIS](https://nsis.sourceforge.io/). [ImageMagick](https://imagemagick.org),
a command-line image editing tool, is used to generate some assets for the installer. Commands `makensis` and
`convert` (from ImageMagick) must be available in system path in order to build the installer.
Windows installers are implemented with [NSIS](https://nsis.sourceforge.io/). Command `makensis` must be available in system
path in order to build the installer.
## Gradle tasks summary
- `buildLocal` creates a build optimized for current platform. Use this to quickly build the game during development.
- `buildCrossPlatform` creates a build that supports all known architectures. Use this to build a universal version of the game.
- `build` currently a synonym of `buildLocal`; creates a default build.
- `packageDebian` creates a Debian package. Do not invoke together with `packageWindows`.
- `packageWindows` creates a Windows installer. Do not invoke together with `packageDebian`.
- `requestLinuxDependencies` requests that `natives-linux`, `natives-linux-arm32` and `natives-linux-arm64` binaries are included when building.
- `requestWindowsDependencies` requests that `natives-windows` and `natives-windows-x86` binaries are included when building.
- `requestMacOSDependencies` requests that `natives-macos` binaries are included when building.
- `requestCrossPlatformDependencies` requests that all binaries are included when building.
All other basic and Java-related Gradle tasks are available as well.

View File

@ -1,103 +0,0 @@
# Build Script Reference
This document is a user's reference for the build script of Progressia. For a beginner-friendly guide, see
[Build Guide](BuildGuide.md).
## Gradle tasks summary
- `buildLocal` creates a build optimized for current platform. Use this to quickly build the game during development.
- `buildCrossPlatform` creates a build that supports all known architectures. Use this to build a universal version of the game.
- `build` currently a synonym of `buildLocal`; creates a default build.
- `packageZip` creates a universal ZIP. Incompatible with other `package` tasks.
- `packageDeb` creates a Debian package. Incompatible with other `package` tasks.
- `packageNsis` creates a Windows NSIS installer. Incompatible with other `package` tasks.
- `requestLinuxDependencies` requests that `natives-linux`, `natives-linux-arm32` and `natives-linux-arm64` binaries are included when building.
- `requestWindowsDependencies` requests that `natives-windows` and `natives-windows-x86` binaries are included when building.
- `requestMacOSDependencies` requests that `natives-macos` binaries are included when building.
- `requestCrossPlatformDependencies` requests that all binaries are included when building.
To execute a task, run `./gradlew <task-name>`.
`build`-type tasks output the executable JAR and all libraries required at runtime into `build/libs`. `package`-type
tasks output packages into `build/packages`.
## Packaging tasks
Some packaging tasks require additional software in `PATH`.
| Task | Commands required in `PATH` |
|---------------|------------------------------------------|
| `packageDeb` | `dpkg-deb` |
| `packageZip` | _none_ |
| `packageNsis` | `makensis`, `convert` (from ImageMagick) |
## Version and metadata
### Version scheme
Progressia builds are identified by four parameters: version, Git commit, Git branch and build ID.
Versions roughly follow [semantic versioning](https://semver.org/spec/v2.0.0.html), with each version fitting the
`MAJOR.MINOR.PATCH[-SUFFIX]` pattern. Depending on the build environment (see below), version is either "real" with
no metadata (e.g. `0.43.2` or `1.2.1-beta`) or a dummy fallback with build metadata (e.g. `999.0.0-2021_07_23` or
`999.0.0-WJ3`).
### Version detection
Build script considers three scenarios when determining the version:
1. `version` project property is set explicitly. This may be done in a variety of ways, for example with command line
argument `-Pversion=1.2.3`
(see [Gradle docs](https://docs.gradle.org/current/userguide/build_environment.html#sec:project_properties))
2. Local Git repository is found, and HEAD is tagged appropriately: version is the tag name with leading `v`
stripped. Example: `v1.2.3` is version `1.2.3`
3. Local Git repository is found, and some ancestor of HEAD is tagged appropriately: version is the tag name with
leading `v` stripped and PATCH incremented by one. Example: `v1.2.3` is version `1.2.4`
Tags not named like `vMAJOR.MINOR.PATCH[-SUFFIX]` are ignored for cases 2 and 3.
In all other cases, a fallback dummy value is used for version, appended with build ID or current date.
### Git metadata
Git commit and Git branch are correspond to the state of the local Git repository, if any. In case Git metadata is
unavailable, `-` fallback is used for both fields.
### Build ID
Build ID uniquely identifies artifacts produced by automated build systems. For example, builds executed by WindCorp
Jenkins suite have build IDs like `WJ3` or `WJ142`. Build ID must be provided explicitly; it is `-` unless specified
otherwise.
Build ID may be set with `buildId` project property. This may be done in a variety of ways, for example with command
line argument `-PbuildId=WJ3`
(see [Gradle docs](https://docs.gradle.org/current/userguide/build_environment.html#sec:project_properties)).
## Native libraries
LWJGL uses native libraries. Build script declares platform-specific dependencies based on the set of target
platforms, `project.ext.lwjgl.targets` (aka `lwjgl.targets`). These dependencies are added to `runtimeOnly`
configuration.
When this set is empty, the script selects natives for current platform. Otherwise, all platforms in the set are
included.
`lwjgl.targets` is populated automatically by packaging tasks and by `buildCrossPlatform`. To add extra targets,
``requestXxxDependencies` tasks may be used.
Target selection mechanism may be overridden with `forceTargets` project property. This may be done in a variety of
ways, for example with command line argument `-PforceTargets=windows-x86,local`
(see [Gradle docs](https://docs.gradle.org/current/userguide/build_environment.html#sec:project_properties)). The
value is a comma-separated list of target architectures. `local` target will be replaced with the automatically
detected current architecture.
### Available targets
| Name | Task |
|---------------|------------------------------|
| `linux` | `requestLinuxDependencies` |
| `linux-arm32` | `requestLinuxDependencies` |
| `linux-arm64` | `requestLinuxDependencies` |
| `windows` | `requestWindowsDependencies` |
| `windows-x86` | `requestWindowsDependencies` |
| `macos` | `requestMacOSDependencies` |

View File

@ -41,17 +41,4 @@ Run configurations are used by Intellij IDEA to specify how a project must be ru
8. Append `\run` to the 'Working directory' field. Alternatively, specify another location outside of the project's root directory.
9. Click 'Apply' to save changes.
Step 8 is required to specify that the game must run in some directory other than the project root, which is the default in Intellij IDEA.
### Applying formatting templates
Windcorp's Progressia repository is formatted with a style defined for Eclipse IDE (sic) in
`templates_and_presets/eclipse_ide`.
Please apply these templates to the project to automatically format the source in a similar fashion.
1. In project context menu, click 'File->Properties'. (`Ctrl+Alt+S`)
2. In 'Editor' > 'Code Style' > 'Java', press gear icon, then click 'Import Scheme' > 'Eclipse code style'
3. In Scheme select 'Project'
4. Open the file `templates_and_presets/eclipse_ide/FormatterProfile.xml` in 'Select Path'.
5. Inside 'Import Scheme' widow click 'Current Scheme' check box after press OK
Step 8 is required to specify that the game must run in some directory other than the project root, which is the default in Intellij IDEA.

View File

@ -1 +0,0 @@
22:26:25.948 [Music Thread ] WARN ru.windcorp.progressia.test.TestMusicPlayer > No music found

View File

@ -15,180 +15,9 @@
* 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;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import org.apache.logging.log4j.LogManager;
import ru.windcorp.progressia.common.util.crash.CrashReports;
/**
* A class providing access to build metadata.
*/
public class Progressia {
private static final String NAME = "Progressia";
private static String version;
private static String gitCommit;
private static String gitBranch;
private static String buildId;
static {
try {
Manifest manifest = findManifest();
if (manifest == null) {
setDevelopmentMetadata();
LogManager.getLogger().info(
"Manifest with Specification-Title not found. "
+ "Either you are in a development environment or something has gone horribly wrong with classloaders."
);
} else {
fillMetadata(manifest);
}
} catch (Throwable t) {
CrashReports.crash(t, "Something went wrong while loading metadata");
}
}
private static Manifest findManifest() {
try {
Enumeration<URL> resources = Progressia.class.getClassLoader().getResources("META-INF/MANIFEST.MF");
Collection<IOException> exceptions = new ArrayList<>();
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
try {
Manifest manifest = new Manifest(url.openStream());
Attributes mainAttributes = manifest.getMainAttributes();
if (NAME.equals(mainAttributes.getValue("Specification-Title"))) {
return manifest;
}
} catch (IOException e) {
exceptions.add(e);
}
}
if (exceptions.isEmpty()) {
return null;
}
IOException scapegoat = null;
for (IOException e : exceptions) {
if (scapegoat == null) {
scapegoat = e;
} else {
scapegoat.addSuppressed(e);
}
}
throw CrashReports.report(scapegoat, "Could not read manifest");
} catch (IOException e) {
throw CrashReports.report(e, "Could not read manifest");
}
}
private static void setDevelopmentMetadata() {
version = "dev";
gitCommit = "-";
gitBranch = "-";
buildId = "-";
}
private static void fillMetadata(Manifest manifest) {
version = getAttributeOrCrash(manifest, "Implementation-Version");
gitCommit = getAttributeOrCrash(manifest, "Implementation-Version-Git-Commit");
gitBranch = getAttributeOrCrash(manifest, "Implementation-Version-Git-Branch");
buildId = getAttributeOrCrash(manifest, "Implementation-Version-BuildId");
}
private static String getAttributeOrCrash(Manifest manifest, String key) {
String result = manifest.getMainAttributes().getValue(key);
if (result == null) {
throw CrashReports.report(null, "Manifest exists but attribute " + key + " not found");
}
return result;
}
public static String getName() {
return NAME;
}
/**
* Returns the version of the game as a String. Version data is retrieved
* from a {@code META-INF/MANIFEST.MF} file located in the main JAR. Version
* format depends on way the game was built:
* <ul>
* <li><code>dev</code> if no matching manifest was found, e.g. when launching from an IDE</li>
* <li>The value of <code>Implementation-Version</code> specified in the manifest:
* <ul>
* <li>[Stage-]Major.Minor.Patch, e.g. <code>alpha-0.3.2</code> or <code>1.4.2</code>, for released versions</li>
* <li>BuildId, e.g. <code>WJ7</code>, for snapshots built by automation systems</li>
* <li>YYYY-MM-DD, e.g. <code>2021-12-32</code>, for snapshots built manually</li>
* </ul>
* </li>
* </ul>
*
* @return the version
*/
public static String getVersion() {
return version;
}
public static String getFullerVersion() {
if (isDefaultGitBranch() || "-".equals(gitBranch)) {
return version;
} else {
return String.format("%s/%s", version, gitBranch);
}
}
/**
* @return the buildId or <code>"-"</code>
*/
public static String getBuildId() {
return buildId;
}
/**
* @return the Git commit or <code>"-"</code>
*/
public static String getGitCommit() {
return gitCommit;
}
public static String getGitCommitShort() {
if (gitCommit == null || "-".equals(gitCommit)) {
return gitCommit;
}
return gitCommit.substring(0, Math.min(7, gitCommit.length()));
}
/**
* @return the Git branch or <code>"-"</code>
*/
public static String getGitBranch() {
return gitBranch;
}
public static boolean isDefaultGitBranch() {
return "master".equals(gitBranch) || "main".equals(gitBranch);
}
public static String getFullVersion() {
return String.format("%s/%s/%s/%s", version, gitBranch, getGitCommitShort(), buildId);
}
}

View File

@ -15,40 +15,25 @@
* 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;
import org.apache.logging.log4j.LogManager;
import ru.windcorp.progressia.client.graphics.GUI;
import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.common.util.crash.analyzers.OutOfMemoryAnalyzer;
import ru.windcorp.progressia.common.util.crash.providers.*;
import ru.windcorp.progressia.test.LayerTitle;
public class ProgressiaLauncher {
public static String[] arguments;
private static Proxy proxy;
public static void launch(String[] args, Proxy proxy) {
arguments = args.clone();
setupCrashReports();
LogManager.getRootLogger().info("Launching " + Progressia.getName() + " version " + Progressia.getFullVersion());
proxy.initialize();
ProgressiaLauncher.proxy = proxy;
GUI.addTopLayer(new LayerTitle("Title"));
}
public static Proxy getProxy() {
return proxy;
}
private static void setupCrashReports() {
// Context providers
CrashReports.registerProvider(new VersionProvider());
CrashReports.registerProvider(new OSContextProvider());
CrashReports.registerProvider(new RAMContextProvider());
CrashReports.registerProvider(new JavaVersionContextProvider());

View File

@ -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.LocalPlayer;
import ru.windcorp.progressia.client.world.WorldRender;
import ru.windcorp.progressia.common.world.DefaultWorldData;
import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.common.world.entity.EntityData;
public class Client {
@ -36,7 +36,7 @@ public class Client {
private final ServerCommsChannel comms;
public Client(DefaultWorldData world, ServerCommsChannel comms) {
public Client(WorldData world, ServerCommsChannel comms) {
this.world = new WorldRender(world, this);
this.comms = comms;

View File

@ -15,7 +15,7 @@
* 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;
import ru.windcorp.progressia.Proxy;
@ -28,8 +28,10 @@ import ru.windcorp.progressia.client.graphics.font.Typefaces;
import ru.windcorp.progressia.client.graphics.texture.Atlases;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.localization.Localizer;
import ru.windcorp.progressia.common.modules.TaskManager;
import ru.windcorp.progressia.common.resource.ResourceManager;
import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.server.ServerState;
import ru.windcorp.progressia.test.TestContent;
import ru.windcorp.progressia.test.TestMusicPlayer;
@ -37,9 +39,7 @@ public class ClientProxy implements Proxy {
@Override
public void initialize() {
GraphicsBackend.initialize();
try {
RenderTaskQueue.waitAndInvoke(FlatRenderProgram::init);
RenderTaskQueue.waitAndInvoke(WorldRenderProgram::init);
@ -58,7 +58,13 @@ public class ClientProxy implements Proxy {
Atlases.loadAllAtlases();
AudioSystem.initialize();
TaskManager.getInstance().startLoading();
ServerState.startServer();
ClientState.connectToLocalServer();
TestMusicPlayer.start();
}
}

View File

@ -15,18 +15,15 @@
* 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;
import ru.windcorp.progressia.client.comms.localhost.LocalServerCommsChannel;
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.common.world.DefaultWorldData;
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.server.ServerState;
import ru.windcorp.progressia.test.LayerAbout;
import ru.windcorp.progressia.test.LayerTestText;
import ru.windcorp.progressia.test.LayerTestUI;
import ru.windcorp.progressia.test.TestContent;
@ -44,7 +41,7 @@ public class ClientState {
public static void connectToLocalServer() {
DefaultWorldData world = new DefaultWorldData();
WorldData world = new WorldData();
LocalServerCommsChannel channel = new LocalServerCommsChannel(
ServerState.getInstance()
@ -55,39 +52,11 @@ public class ClientState {
channel.connect(TestContent.PLAYER_LOGIN);
setInstance(client);
displayLoadingScreen();
}
GUI.addBottomLayer(new LayerWorld(client));
GUI.addTopLayer(new LayerTestUI());
GUI.addTopLayer(new LayerAbout());
private static void displayLoadingScreen() {
GUI.addTopLayer(new LayerTestText("Text", new MutableStringLocalized("LayerText.Load"), layer -> {
Client client = ClientState.getInstance();
// TODO refacetor and remove
if (client != null) {
client.getComms().processPackets();
}
if (client != null && client.getLocalPlayer().hasEntity()) {
GUI.removeLayer(layer);
// 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() {

View File

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

View File

@ -143,6 +143,20 @@ public class ControlTriggers {
);
}
//
//
///
///
//
//
//
//
//
//
//
//
//
public static ControlTriggerInputBased localOf(
String id,
Consumer<InputEvent> action,

View File

@ -19,7 +19,7 @@
package ru.windcorp.progressia.client.comms.controls;
import ru.windcorp.progressia.client.Client;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
import ru.windcorp.progressia.client.graphics.input.bus.Input;
import ru.windcorp.progressia.common.comms.packets.Packet;
public class InputBasedControls {
@ -36,12 +36,12 @@ public class InputBasedControls {
.toArray(ControlTriggerInputBased[]::new);
}
public void handleInput(InputEvent event) {
public void handleInput(Input input) {
for (ControlTriggerInputBased c : controls) {
Packet packet = c.onInputEvent(event);
Packet packet = c.onInputEvent(input.getEvent());
if (packet != null) {
event.consume();
input.consume();
client.getComms().sendPacket(packet);
break;
}

View File

@ -15,7 +15,7 @@
* 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.localhost;
import java.io.IOException;

View File

@ -15,13 +15,12 @@
* 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.localhost;
import ru.windcorp.progressia.client.comms.ServerCommsChannel;
import ru.windcorp.progressia.common.comms.packets.Packet;
import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.ServerState;
public class LocalServerCommsChannel extends ServerCommsChannel {
@ -55,7 +54,7 @@ public class LocalServerCommsChannel extends ServerCommsChannel {
@Override
public void disconnect() {
ServerState.getInstance().getClientManager().disconnectClient(localClient);
// Do nothing
}
}

View File

@ -34,13 +34,7 @@ public class Colors {
DEBUG_BLUE = toVector(0xFF0000FF),
DEBUG_CYAN = toVector(0xFF00FFFF),
DEBUG_MAGENTA = toVector(0xFFFF00FF),
DEBUG_YELLOW = toVector(0xFFFFFF00),
LIGHT_GRAY = toVector(0xFFCBCBD0),
BLUE = toVector(0xFF37A2E6),
HOVER_BLUE = toVector(0xFFC3E4F7),
DISABLED_GRAY = toVector(0xFFE5E5E5),
DISABLED_BLUE = toVector(0xFFB2D8ED);
DEBUG_YELLOW = toVector(0xFFFFFF00);
public static Vec4 toVector(int argb) {
return toVector(argb, new Vec4());

View File

@ -15,16 +15,21 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.graphics;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import com.google.common.eventbus.Subscribe;
import ru.windcorp.progressia.client.graphics.input.CursorEvent;
import ru.windcorp.progressia.client.graphics.input.FrameResizeEvent;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.graphics.input.WheelEvent;
import ru.windcorp.progressia.client.graphics.input.bus.Input;
public class GUI {
@ -39,31 +44,28 @@ public class GUI {
private static final List<LayerStackModification> MODIFICATION_QUEUE = Collections
.synchronizedList(new ArrayList<>());
private static class ModifiableInput extends Input {
@Override
public void initialize(InputEvent event, Target target) {
super.initialize(event, target);
}
}
private static final ModifiableInput THE_INPUT = new ModifiableInput();
private GUI() {
}
public static void addBottomLayer(Layer layer) {
Objects.requireNonNull(layer, "layer");
modify(layers -> {
layers.add(layer);
layer.onAdded();
});
modify(layers -> layers.add(layer));
}
public static void addTopLayer(Layer layer) {
Objects.requireNonNull(layer, "layer");
modify(layers -> {
layers.add(0, layer);
layer.onAdded();
});
modify(layers -> layers.add(0, layer));
}
public static void removeLayer(Layer layer) {
Objects.requireNonNull(layer, "layer");
modify(layers -> {
layers.remove(layer);
layer.onRemoved();
});
modify(layers -> layers.remove(layer));
}
private static void modify(LayerStackModification mod) {
@ -76,33 +78,12 @@ public class GUI {
public static void render() {
synchronized (LAYERS) {
if (!MODIFICATION_QUEUE.isEmpty()) {
MODIFICATION_QUEUE.forEach(action -> action.affect(LAYERS));
MODIFICATION_QUEUE.clear();
boolean isMouseCurrentlyCaptured = GraphicsInterface.isMouseCaptured();
Layer.CursorPolicy policy = Layer.CursorPolicy.REQUIRE;
for (Layer layer : LAYERS) {
Layer.CursorPolicy currentPolicy = layer.getCursorPolicy();
if (currentPolicy != Layer.CursorPolicy.INDIFFERENT) {
policy = currentPolicy;
break;
}
}
boolean shouldCaptureMouse = (policy == Layer.CursorPolicy.FORBID);
if (shouldCaptureMouse != isMouseCurrentlyCaptured) {
GraphicsInterface.setMouseCaptured(shouldCaptureMouse);
}
}
MODIFICATION_QUEUE.forEach(action -> action.affect(LAYERS));
MODIFICATION_QUEUE.clear();
for (int i = LAYERS.size() - 1; i >= 0; --i) {
LAYERS.get(i).render();
}
}
}
@ -110,12 +91,43 @@ public class GUI {
LAYERS.forEach(Layer::invalidate);
}
public static void dispatchInput(InputEvent event) {
synchronized (LAYERS) {
for (int i = 0; i < LAYERS.size(); ++i) {
LAYERS.get(i).handleInput(event);
private static void dispatchInputEvent(InputEvent event) {
Input.Target target;
if (event instanceof KeyEvent) {
if (((KeyEvent) event).isMouse()) {
target = Input.Target.HOVERED;
} else {
target = Input.Target.FOCUSED;
}
} else if (event instanceof CursorEvent) {
target = Input.Target.HOVERED;
} else if (event instanceof WheelEvent) {
target = Input.Target.HOVERED;
} else if (event instanceof FrameResizeEvent) {
return;
} else {
target = Input.Target.ALL;
}
THE_INPUT.initialize(event, target);
LAYERS.forEach(l -> l.handleInput(THE_INPUT));
}
public static Object getEventSubscriber() {
return new Object() {
@Subscribe
public void onFrameResized(FrameResizeEvent event) {
GUI.invalidateEverything();
}
@Subscribe
public void onInput(InputEvent event) {
dispatchInputEvent(event);
}
};
}
}

View File

@ -21,7 +21,7 @@ package ru.windcorp.progressia.client.graphics;
import java.util.concurrent.atomic.AtomicBoolean;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
import ru.windcorp.progressia.client.graphics.input.bus.Input;
public abstract class Layer {
@ -30,52 +30,15 @@ public abstract class Layer {
private boolean hasInitialized = 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) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Layer " + name;
}
public CursorPolicy getCursorPolicy() {
return cursorPolicy;
}
public void setCursorPolicy(CursorPolicy cursorPolicy) {
this.cursorPolicy = cursorPolicy;
}
void render() {
GraphicsInterface.startNextLayer();
@ -106,7 +69,7 @@ public abstract class Layer {
protected abstract void doRender();
public abstract void handleInput(InputEvent input);
protected abstract void handleInput(Input input);
protected int getWidth() {
return GraphicsInterface.getFrameWidth();
@ -115,13 +78,5 @@ public abstract class Layer {
protected int getHeight() {
return GraphicsInterface.getFrameHeight();
}
protected void onAdded() {
// Do nothing
}
protected void onRemoved() {
// Do nothing
}
}

View File

@ -19,7 +19,6 @@
package ru.windcorp.progressia.client.graphics.backend;
import glm.vec._2.i.Vec2i;
import org.lwjgl.glfw.GLFWVidMode;
import static org.lwjgl.glfw.GLFW.*;
@ -43,13 +42,6 @@ public class GraphicsBackend {
private static boolean vSyncEnabled = false;
private static boolean isGLFWInitialized = false;
private static boolean isOpenGLInitialized = false;
private static boolean allowDisablingCursor;
static {
String key = GraphicsBackend.class.getName() + ".allowDisablingCursor";
allowDisablingCursor = Boolean.parseBoolean(System.getProperty(key, "true"));
}
private static boolean forceCursorToCenter = false;
private GraphicsBackend() {
}
@ -122,10 +114,6 @@ public class GraphicsBackend {
frameLength = now - frameStart;
frameStart = now;
}
if (forceCursorToCenter) {
glfwSetCursorPos(windowHandle, FRAME_SIZE.x / 2.0, FRAME_SIZE.y / 2.0);
}
}
static void endFrame() {
@ -204,26 +192,4 @@ public class GraphicsBackend {
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
return vidmode.refreshRate();
}
public static boolean isMouseCaptured() {
if (!allowDisablingCursor) {
return forceCursorToCenter;
}
return glfwGetInputMode(windowHandle, GLFW_CURSOR) == GLFW_CURSOR_DISABLED;
}
public static void setMouseCaptured(boolean capture) {
if (!allowDisablingCursor) {
forceCursorToCenter = capture;
return;
}
int mode = capture ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL;
glfwSetInputMode(windowHandle, GLFW_CURSOR, mode);
if (!capture) {
glfwSetCursorPos(windowHandle, FRAME_SIZE.x / 2.0, FRAME_SIZE.y / 2.0);
}
}
}

View File

@ -68,10 +68,6 @@ public class GraphicsInterface {
public static void subscribeToInputEvents(Object listener) {
InputHandler.register(listener);
}
public static void unsubscribeFromInputEvents(Object listener) {
InputHandler.unregister(listener);
}
public static void startNextLayer() {
GraphicsBackend.startNextLayer();
@ -85,13 +81,5 @@ public class GraphicsInterface {
}
GraphicsBackend.setVSyncEnabled(GraphicsBackend.isVSyncEnabled());
}
public static boolean isMouseCaptured() {
return GraphicsBackend.isMouseCaptured();
}
public static void setMouseCaptured(boolean capture) {
GraphicsBackend.setMouseCaptured(capture);
}
}

View File

@ -39,7 +39,6 @@ public class InputHandler {
public void initialize(int key, int scancode, int action, int mods) {
this.setTime(GraphicsInterface.getTime());
this.setConsumed(false);
this.key = key;
this.scancode = scancode;
this.action = action;
@ -60,7 +59,7 @@ public class InputHandler {
if (GraphicsBackend.getWindowHandle() != window)
return;
THE_KEY_EVENT.initialize(key, scancode, action, mods);
INPUT_EVENT_BUS.post(THE_KEY_EVENT);
dispatch(THE_KEY_EVENT);
switch (action) {
case GLFW.GLFW_PRESS:
@ -91,7 +90,6 @@ public class InputHandler {
public void initialize(double x, double y) {
this.setTime(GraphicsInterface.getTime());
this.setConsumed(false);
getNewPosition().set(x, y);
}
@ -111,7 +109,7 @@ public class InputHandler {
InputTracker.initializeCursorPosition(x, y);
THE_CURSOR_MOVE_EVENT.initialize(x, y);
INPUT_EVENT_BUS.post(THE_CURSOR_MOVE_EVENT);
dispatch(THE_CURSOR_MOVE_EVENT);
InputTracker.getCursorPosition().set(x, y);
}
@ -126,7 +124,6 @@ public class InputHandler {
public void initialize(double xOffset, double yOffset) {
this.setTime(GraphicsInterface.getTime());
this.setConsumed(false);
this.getOffset().set(xOffset, yOffset);
}
@ -142,7 +139,7 @@ public class InputHandler {
if (GraphicsBackend.getWindowHandle() != window)
return;
THE_WHEEL_SCROLL_EVENT.initialize(xoffset, yoffset);
INPUT_EVENT_BUS.post(THE_WHEEL_SCROLL_EVENT);
dispatch(THE_WHEEL_SCROLL_EVENT);
}
// FrameResizeEvent
@ -155,7 +152,6 @@ public class InputHandler {
public void initialize(int width, int height) {
this.setTime(GraphicsInterface.getTime());
this.setConsumed(false);
this.getNewSize().set(width, height);
}
@ -171,17 +167,17 @@ public class InputHandler {
int height
) {
THE_FRAME_RESIZE_EVENT.initialize(width, height);
INPUT_EVENT_BUS.post(THE_FRAME_RESIZE_EVENT);
dispatch(THE_FRAME_RESIZE_EVENT);
}
// Misc
private static void dispatch(InputEvent event) {
INPUT_EVENT_BUS.post(event);
}
public static void register(Object listener) {
INPUT_EVENT_BUS.register(listener);
}
public static void unregister(Object listener) {
INPUT_EVENT_BUS.unregister(listener);
}
}

View File

@ -15,30 +15,16 @@
* 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.backend;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.system.MemoryUtil.*;
import java.io.IOException;
import org.lwjgl.glfw.GLFWImage;
import org.lwjgl.opengl.GL;
import com.google.common.eventbus.Subscribe;
import ru.windcorp.progressia.Progressia;
import ru.windcorp.progressia.client.graphics.GUI;
import ru.windcorp.progressia.client.graphics.input.FrameResizeEvent;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
import ru.windcorp.progressia.client.graphics.texture.TextureDataEditor;
import ru.windcorp.progressia.client.graphics.texture.TextureLoader;
import ru.windcorp.progressia.client.graphics.texture.TextureSettings;
import ru.windcorp.progressia.common.resource.Resource;
import ru.windcorp.progressia.common.resource.ResourceManager;
import ru.windcorp.progressia.common.util.crash.CrashReports;
class LWJGLInitializer {
@ -73,20 +59,16 @@ class LWJGLInitializer {
glfwWindowHint(GLFW_FOCUSED, GLFW_TRUE);
glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE);
long handle = glfwCreateWindow(
800,
600,
Progressia.getName() + " " + Progressia.getFullerVersion(),
NULL,
NULL
);
long handle = glfwCreateWindow(900, 900, "ProgressiaTest", NULL, NULL);
// TODO Check that handle != NULL
GraphicsBackend.setWindowHandle(handle);
glfwSetInputMode(handle, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
glfwMakeContextCurrent(handle);
glfwSwapInterval(0); // TODO: remove after config system is added
glfwSwapInterval(0); // TODO: remove after config system is added
}
private static void positionWindow() {
@ -95,30 +77,8 @@ class LWJGLInitializer {
}
private static void createWindowIcons() {
if (glfwGetVersionString().toLowerCase().contains("wayland")) {
// glfwSetWindowIcon is not supported on Wayland
return;
}
final String prefix = "assets/icons/";
// TODO Auto-generated method stub
String[] sizes = ResourceManager.getResource(prefix + "logoSizes.txt").readAsString().split(" ");
try (GLFWImage.Buffer buffer = GLFWImage.malloc(sizes.length)) {
for (int i = 0; i < sizes.length; ++i) {
Resource resource = ResourceManager.getResource(prefix + "logo" + sizes[i].trim() + ".png");
TextureDataEditor icon = TextureLoader.loadPixels(resource, new TextureSettings(false, true));
buffer.position(i)
.width(icon.getContentWidth())
.height(icon.getContentHeight())
.pixels(icon.getData().getData());
}
glfwSetWindowIcon(GraphicsBackend.getWindowHandle(), buffer);
} catch (IOException e) {
throw CrashReports.report(e, "Could not load window icons");
}
}
private static void initializeOpenGL() {
@ -149,20 +109,7 @@ class LWJGLInitializer {
glfwSetScrollCallback(handle, InputHandler::handleWheelScroll);
GraphicsInterface.subscribeToInputEvents(new Object() {
@Subscribe
public void onFrameResized(FrameResizeEvent event) {
GUI.invalidateEverything();
}
@Subscribe
public void onInputEvent(InputEvent event) {
GUI.dispatchInput(event);
}
});
GraphicsInterface.subscribeToInputEvents(GUI.getEventSubscriber());
}
}

View File

@ -29,8 +29,8 @@ import glm.vec._3.Vec3;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.backend.Usage;
import ru.windcorp.progressia.client.graphics.model.ShapePart;
import ru.windcorp.progressia.client.graphics.model.ShapeParts;
import ru.windcorp.progressia.client.graphics.model.Face;
import ru.windcorp.progressia.client.graphics.model.Faces;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.texture.Texture;
@ -84,7 +84,7 @@ public class RenderTarget {
private final Deque<TransformedMask> maskStack = new LinkedList<>();
private final Deque<Mat4> transformStack = new LinkedList<>();
private final List<ShapePart> currentClipFaces = new ArrayList<>();
private final List<Face> currentClipFaces = new ArrayList<>();
private int depth = 0;
@ -94,8 +94,8 @@ public class RenderTarget {
protected void assembleCurrentClipFromFaces() {
if (!currentClipFaces.isEmpty()) {
ShapePart[] faces = currentClipFaces.toArray(
new ShapePart[currentClipFaces.size()]
Face[] faces = currentClipFaces.toArray(
new Face[currentClipFaces.size()]
);
currentClipFaces.clear();
@ -189,13 +189,16 @@ public class RenderTarget {
public void addCustomRenderer(Renderable renderable) {
assembleCurrentClipFromFaces();
float depth = this.depth--;
Mat4 transform = new Mat4().translate(0, 0, depth).mul(getTransform());
assembled.add(new Clip(maskStack, transform, renderable));
assembled.add(
new Clip(
maskStack,
getTransform(),
renderable
)
);
}
protected void addFaceToCurrentClip(ShapePart face) {
protected void addFaceToCurrentClip(Face face) {
currentClipFaces.add(face);
}
@ -267,7 +270,7 @@ public class RenderTarget {
fill(Colors.toVector(color));
}
public ShapePart createRectagleFace(
public Face createRectagleFace(
int x,
int y,
int width,
@ -277,7 +280,7 @@ public class RenderTarget {
) {
float depth = this.depth--;
return ShapeParts.createRectangle(
return Faces.createRectangle(
FlatRenderProgram.getDefault(),
texture,
color,
@ -288,7 +291,7 @@ public class RenderTarget {
);
}
public ShapePart createRectagleFace(
public Face createRectagleFace(
int x,
int y,
int width,

View File

@ -45,7 +45,7 @@ public class GNUUnifontLoader {
private static final AtlasGroup ATLAS_GROUP_GNU_UNIFONT = new AtlasGroup("GNUUnifont", 1 << 12);
private static final TextureSettings TEXTURE_SETTINGS = new TextureSettings(false, false);
private static final TextureSettings TEXTURE_SETTINGS = new TextureSettings(false);
private static final int BITS_PER_HEX_DIGIT = 4;
private static final int PREFIX_LENGTH = "0000:".length();

View File

@ -33,8 +33,8 @@ import gnu.trove.stack.TIntStack;
import gnu.trove.stack.array.TIntArrayStack;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.backend.Usage;
import ru.windcorp.progressia.client.graphics.model.ShapePart;
import ru.windcorp.progressia.client.graphics.model.ShapeParts;
import ru.windcorp.progressia.client.graphics.model.Face;
import ru.windcorp.progressia.client.graphics.model.Faces;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
@ -144,7 +144,7 @@ public abstract class SpriteTypeface extends Typeface {
return new Shape(
Usage.STATIC,
getProgram(),
ShapeParts.createRectangle(
Faces.createRectangle(
getProgram(),
getTexture(c),
Colors.WHITE,
@ -167,7 +167,7 @@ public abstract class SpriteTypeface extends Typeface {
private final Renderable unitLine = new Shape(
Usage.STATIC,
getProgram(),
ShapeParts.createRectangle(
Faces.createRectangle(
getProgram(),
null,
Vectors.UNIT_4,
@ -257,7 +257,7 @@ public abstract class SpriteTypeface extends Typeface {
private class SDWorkspace extends SpriteTypeface.Workspace {
private final Collection<ShapePart> faces = new ArrayList<>();
private final Collection<Face> faces = new ArrayList<>();
private final Vec3 origin = new Vec3();
private final Vec3 width = new Vec3();
@ -298,7 +298,7 @@ public abstract class SpriteTypeface extends Typeface {
workspace.height.sub(workspace.origin);
workspace.faces.add(
ShapeParts.createRectangle(
Faces.createRectangle(
getProgram(),
texture,
color,
@ -314,7 +314,7 @@ public abstract class SpriteTypeface extends Typeface {
return new Shape(
Usage.STATIC,
getProgram(),
workspace.faces.toArray(new ShapePart[workspace.faces.size()])
workspace.faces.toArray(new Face[workspace.faces.size()])
);
}

View File

@ -1,28 +0,0 @@
package ru.windcorp.progressia.client.graphics.gui;
import glm.mat._4.Mat4;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
import ru.windcorp.progressia.client.graphics.texture.Texture;
public class Background extends GUILayer {
protected Texture backgroundTexture;
public Background(String name, Layout layout, Texture inTexture) {
super(name, layout);
backgroundTexture = inTexture;
}
@Override
protected void assemble(RenderTarget target) {
getRoot().setBounds(0, 0, getWidth(), getHeight());
getRoot().invalidate();
target.pushTransform(new Mat4(1).translate(new Vec3(0,0,500)));
target.drawTexture(0, 0, getWidth(), getHeight(), backgroundTexture);
target.popTransform();
getRoot().assemble(target);
}
}

View File

@ -1,155 +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.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
addInputListener(KeyEvent.class, e -> {
if (e.isRepeat())
return;
if (
e.isLeftMouseButton() ||
e.getKey() == GLFW.GLFW_KEY_SPACE ||
e.getKey() == GLFW.GLFW_KEY_ENTER
) {
setPressed(e.isPress());
e.consume();
}
});
addListener(new Object() {
// Release when losing focus
@Subscribe
public void onFocusChange(FocusEvent e) {
if (!e.getNewState()) {
setPressed(false);
}
}
// Release when hover ends
@Subscribe
public void onHoverEnded(HoverEvent e) {
if (!e.isNowHovered()) {
setPressed(false);
}
}
// Release when disabled
@Subscribe
public void onDisabled(EnableEvent e) {
if (!e.getComponent().isEnabled()) {
setPressed(false);
}
}
// Trigger virtualClick when button is released
@Subscribe
public void onRelease(ButtonEvent.Release e) {
virtualClick();
}
});
}
public BasicButton(String name, String label, Font labelFont) {
this(name, new Label(name + ".Label", labelFont, label));
}
public BasicButton(String name, String label) {
this(name, label, new Font());
}
public boolean isPressed() {
return isPressed;
}
public void click() {
setPressed(true);
setPressed(false);
}
public void setPressed(boolean isPressed) {
if (this.isPressed != isPressed) {
this.isPressed = isPressed;
requestReassembly();
if (isPressed) {
takeFocus();
}
dispatchEvent(ButtonEvent.create(this, this.isPressed));
}
}
public BasicButton addAction(Consumer<BasicButton> action) {
this.actions.add(Objects.requireNonNull(action, "action"));
return this;
}
public boolean removeAction(Consumer<BasicButton> action) {
return this.actions.remove(action);
}
public void virtualClick() {
this.actions.forEach(action -> {
action.accept(this);
});
}
public Label getLabel() {
return label;
}
}

View File

@ -1,85 +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.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;
/** Class for a traditional button that gets clicked to activate
*
* @author opfromthestart
*/
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()) {
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));
}
}
}

View File

@ -1,149 +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.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));
}
}
}

View File

@ -19,28 +19,24 @@
package ru.windcorp.progressia.client.graphics.gui;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import org.lwjgl.glfw.GLFW;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import glm.vec._2.i.Vec2i;
import ru.windcorp.progressia.client.graphics.backend.InputTracker;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
import ru.windcorp.progressia.client.graphics.gui.event.ChildAddedEvent;
import ru.windcorp.progressia.client.graphics.gui.event.ChildRemovedEvent;
import ru.windcorp.progressia.client.graphics.gui.event.EnableEvent;
import ru.windcorp.progressia.client.graphics.gui.event.FocusEvent;
import ru.windcorp.progressia.client.graphics.gui.event.HoverEvent;
import ru.windcorp.progressia.client.graphics.gui.event.ParentChangedEvent;
import ru.windcorp.progressia.client.graphics.input.CursorMoveEvent;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
import ru.windcorp.progressia.client.graphics.input.bus.Input;
import ru.windcorp.progressia.client.graphics.input.bus.InputBus;
import ru.windcorp.progressia.client.graphics.input.bus.InputListener;
import ru.windcorp.progressia.common.util.Named;
@ -54,7 +50,7 @@ public class Component extends Named {
private Component parent = null;
private EventBus eventBus = null;
private final InputBus inputBus = new InputBus(this);
private InputBus inputBus = null;
private int x, y;
private int width, height;
@ -65,8 +61,6 @@ public class Component extends Named {
private Object layoutHint = null;
private Layout layout = null;
private boolean isEnabled = true;
private boolean isFocusable = false;
private boolean isFocused = false;
@ -75,9 +69,6 @@ public class Component extends Named {
public Component(String name) {
super(name);
// Update hover flag when cursor moves
addInputListener(CursorMoveEvent.class, this::updateHoverFlag, InputBus.Option.ALWAYS);
}
public Component getParent() {
@ -294,30 +285,9 @@ public class Component extends Named {
return this;
}
/**
* Checks whether this component is focusable. A component needs to be
* focusable to become focused. A component that is focusable may not
* necessarily be ready to gain focus (see {@link #canGainFocusNow()}).
*
* @return {@code true} iff the component is focusable
* @see #canGainFocusNow()
*/
public boolean isFocusable() {
return isFocusable;
}
/**
* Checks whether this component can become focused at this moment.
* <p>
* The implementation of this method in {@link Component} considers the
* component a focus candidate if it is both focusable and enabled.
*
* @return {@code true} iff the component can receive focus
* @see #isFocusable()
*/
public boolean canGainFocusNow() {
return isFocusable() && isEnabled();
}
public Component setFocusable(boolean focusable) {
this.isFocusable = focusable;
@ -367,7 +337,7 @@ public class Component extends Named {
return;
}
if (component.canGainFocusNow()) {
if (component.isFocusable()) {
setFocused(false);
component.setFocused(true);
return;
@ -409,7 +379,7 @@ public class Component extends Named {
return;
}
if (component.canGainFocusNow()) {
if (component.isFocusable()) {
setFocused(false);
component.setFocused(true);
return;
@ -462,52 +432,13 @@ public class Component extends Named {
return null;
}
public boolean isEnabled() {
return isEnabled;
}
/**
* Enables or disables this component. An {@link EnableEvent} is dispatched
* if the state changes.
*
* @param enabled {@code true} to enable the component, {@code false} to
* disable the component
* @see #setEnabledRecursively(boolean)
*/
public void setEnabled(boolean enabled) {
if (this.isEnabled != enabled) {
if (isFocused() && isEnabled()) {
focusNext();
}
if (isEnabled()) {
setHovered(false);
}
this.isEnabled = enabled;
dispatchEvent(new EnableEvent(this));
}
}
/**
* Enables or disables this component and all of its children recursively.
*
* @param enabled {@code true} to enable the components, {@code false} to
* disable the components
* @see #setEnabled(boolean)
*/
public void setEnabledRecursively(boolean enabled) {
setEnabled(enabled);
getChildren().forEach(c -> c.setEnabledRecursively(enabled));
}
public boolean isHovered() {
return isHovered;
}
protected void setHovered(boolean isHovered) {
if (this.isHovered != isHovered && isEnabled()) {
if (this.isHovered != isHovered) {
this.isHovered = isHovered;
if (!isHovered && !getChildren().isEmpty()) {
@ -523,10 +454,6 @@ public class Component extends Named {
dispatchEvent(new HoverEvent(this, isHovered));
}
}
private void updateHoverFlag(CursorMoveEvent e) {
setHovered(contains((int) InputTracker.getCursorX(), (int) InputTracker.getCursorY()));
}
public void addListener(Object listener) {
if (eventBus == null) {
@ -548,32 +475,121 @@ public class Component extends Named {
eventBus.post(event);
}
public <T extends InputEvent> void addInputListener(Class<? extends T> type, InputListener<T> listener, InputBus.Option... options) {
inputBus.register(type, listener, options);
}
public void addKeyListener(KeyMatcher matcher, InputListener<? super KeyEvent> listener, InputBus.Option... options) {
inputBus.register(matcher, listener, options);
public <T extends InputEvent> void addListener(
Class<? extends T> type,
boolean handlesConsumed,
InputListener<T> listener
) {
if (inputBus == null) {
inputBus = new InputBus();
}
inputBus.register(type, handlesConsumed, listener);
}
public void removeInputListener(InputListener<?> listener) {
public <T extends InputEvent> void addListener(Class<? extends T> type, InputListener<T> listener) {
if (inputBus == null) {
inputBus = new InputBus();
}
inputBus.register(type, listener);
}
public void removeListener(InputListener<?> listener) {
if (inputBus != null) {
inputBus.unregister(listener);
}
}
protected boolean passInputToChildren(InputEvent e) {
return true;
protected void handleInput(Input input) {
if (inputBus != null) {
inputBus.dispatch(input);
}
}
InputBus getInputBus() {
return inputBus;
public void dispatchInput(Input input) {
try {
switch (input.getTarget()) {
case FOCUSED:
dispatchInputToFocused(input);
break;
case HOVERED:
dispatchInputToHovered(input);
break;
case ALL:
default:
dispatchInputToAll(input);
break;
}
} catch (Exception e) {
throw CrashReports.report(e, "Could not dispatch input to Component %s", this);
}
}
private void dispatchInputToFocused(Input input) {
Component c = findFocused();
if (c == null)
return;
if (attemptFocusTransfer(input, c))
return;
while (c != null) {
c.handleInput(input);
c = c.getParent();
}
}
private void dispatchInputToHovered(Input input) {
getChildren().forEach(child -> {
if (child.containsCursor()) {
child.setHovered(true);
if (!input.isConsumed()) {
child.dispatchInput(input);
}
} else {
child.setHovered(false);
}
});
handleInput(input);
}
private void dispatchInputToAll(Input input) {
getChildren().forEach(c -> c.dispatchInput(input));
handleInput(input);
}
private boolean attemptFocusTransfer(Input input, Component focused) {
if (input.isConsumed())
return false;
if (!(input.getEvent() instanceof KeyEvent))
return false;
KeyEvent keyInput = (KeyEvent) input.getEvent();
if (keyInput.getKey() == GLFW.GLFW_KEY_TAB && !keyInput.isRelease()) {
input.consume();
if (keyInput.hasShift()) {
focused.focusPrevious();
} else {
focused.focusNext();
}
return true;
}
return false;
}
public synchronized boolean contains(int x, int y) {
return x >= getX() && x < getX() + getWidth() && y >= getY() && y < getY() + getHeight();
}
public boolean containsCursor() {
return contains((int) InputTracker.getCursorX(), (int) InputTracker.getCursorY());
}
public void requestReassembly() {
if (parent != null) {
parent.requestReassembly();
@ -582,17 +598,6 @@ public class Component extends Named {
}
}
/**
* Schedules the reassembly to occur.
* <p>
* This method is invoked in root components whenever a
* {@linkplain #requestReassembly() reassembly request} is made by one of
* its children. When creating the dedicated root component, override this
* method to perform any implementation-specific actions that will cause a
* reassembly as soon as possible.
* <p>
* The default implementation of this method does nothing.
*/
protected void handleReassemblyRequest() {
// To be overridden
}
@ -632,135 +637,6 @@ public class Component extends Named {
protected void assembleChildren(RenderTarget 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(triggerObject);
autoReassemblyTriggerObjects.put(trigger, triggerObject);
}
}
}
/**
* Requests that {@link #requestReassembly()} is no longer invoked on this
* component whenever any of the specified changes occur. After a trigger is
* removed, it may be reinstalled with
* {@link #reassembleAt(ARTrigger...)}. Attempts to remove a
* nonexistant trigger are silently ignored.
* <p>
* {@code triggers} may be empty, which results in a no-op. It must not be
* {@code null}.
*
* @param triggers the {@linkplain ARTrigger triggers} to remove
* @see #reassemblyAt(ARTrigger...)
*/
protected synchronized void disableAutoReassemblyAt(ARTrigger... triggers) {
Objects.requireNonNull(triggers, "triggers");
if (triggers.length == 0)
return;
if (autoReassemblyTriggerObjects == null)
return;
for (ARTrigger trigger : triggers) {
Object triggerObject = autoReassemblyTriggerObjects.remove(trigger);
if (triggerObject != null) {
removeListener(trigger);
}
}
}
// /**
// * Returns a component that displays this component in its center.

View File

@ -1,77 +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.graphics.gui;
import java.util.Objects;
import glm.vec._2.d.Vec2d;
import ru.windcorp.progressia.client.graphics.gui.event.DragEvent;
import ru.windcorp.progressia.client.graphics.gui.event.DragStartEvent;
import ru.windcorp.progressia.client.graphics.gui.event.DragStopEvent;
import ru.windcorp.progressia.client.graphics.input.CursorMoveEvent;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
import ru.windcorp.progressia.client.graphics.input.bus.InputBus;
public class DragManager {
private Component component;
private boolean isDragged = false;
private final Vec2d change = new Vec2d();
public void install(Component c) {
Objects.requireNonNull(c, "c");
if (c == component) {
return;
}
if (component != null) {
throw new IllegalStateException("Already installed on " + component + "; attempted to install on " + c);
}
component = c;
c.addInputListener(CursorMoveEvent.class, this::onCursorMove, InputBus.Option.ALWAYS);
c.addKeyListener(KeyMatcher.LMB, this::onLMB, InputBus.Option.ALWAYS, InputBus.Option.IGNORE_ACTION);
}
private void onCursorMove(CursorMoveEvent e) {
if (isDragged) {
Vec2d currentChange = e.getChange(null);
change.add(currentChange);
component.dispatchEvent(new DragEvent(component, currentChange, change));
}
}
private void onLMB(KeyEvent e) {
if (isDragged && e.isRelease()) {
isDragged = false;
component.dispatchEvent(new DragStopEvent(component, change));
} else if (!isDragged && !e.isConsumed() && e.isPress() && component.isHovered()) {
isDragged = true;
change.set(0, 0);
component.dispatchEvent(new DragStartEvent(component));
e.consume();
}
}
}

View File

@ -15,20 +15,12 @@
* 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.Iterator;
import org.lwjgl.glfw.GLFW;
import ru.windcorp.progressia.client.graphics.flat.AssembledFlatLayer;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.graphics.input.bus.InputBus;
import ru.windcorp.progressia.common.util.LowOverheadCache;
import ru.windcorp.progressia.common.util.StashingStack;
import ru.windcorp.progressia.client.graphics.input.bus.Input;
public abstract class GUILayer extends AssembledFlatLayer {
@ -41,9 +33,7 @@ public abstract class GUILayer extends AssembledFlatLayer {
public GUILayer(String name, Layout layout) {
super(name);
getRoot().setLayout(layout);
getRoot().addInputListener(KeyEvent.class, this::attemptFocusTransfer, InputBus.Option.IGNORE_FOCUS);
}
public Component getRoot() {
@ -57,85 +47,9 @@ public abstract class GUILayer extends AssembledFlatLayer {
getRoot().assemble(target);
}
/**
* Stack frame for {@link #handleInput(InputEvent)}.
*/
private static class EventHandlingFrame {
Component component;
Iterator<Component> children;
void init(Component c) {
component = c;
children = c.getChildren().iterator();
}
void reset() {
component = null;
children = null;
}
}
/**
* Stacks for {@link #handleInput(InputEvent)}.
*/
private final LowOverheadCache<StashingStack<EventHandlingFrame>> pathCache = new LowOverheadCache<>(
() -> new StashingStack<>(64, EventHandlingFrame::new)
);
/*
* This is essentially a depth-first iteration of the component tree. The
* recursive procedure has been unrolled to reduce call stack length.
*/
@Override
public void handleInput(InputEvent event) {
StashingStack<EventHandlingFrame> path = pathCache.grab();
if (!path.isEmpty()) {
throw new IllegalStateException("path is not empty: " + path);
}
path.push().init(root);
while (!path.isEmpty()) {
Iterator<Component> it = path.peek().children;
if (it.hasNext()) {
Component c = it.next();
if (c.isEnabled()) {
if (c.getChildren().isEmpty() || !c.passInputToChildren(event)) {
c.getInputBus().dispatch(event);
} else {
path.push().init(c);
}
}
} else {
path.peek().component.getInputBus().dispatch(event);
path.pop().reset();
}
}
pathCache.release(path);
}
private void attemptFocusTransfer(KeyEvent e) {
Component focused = getRoot().findFocused();
if (focused == null) {
return;
}
if (e.getKey() == GLFW.GLFW_KEY_TAB && !e.isRelease()) {
e.consume();
if (e.hasShift()) {
focused.focusPrevious();
} else {
focused.focusNext();
}
}
protected void handleInput(Input input) {
getRoot().dispatchInput(input);
}
@Override

View File

@ -82,10 +82,6 @@ public class Label extends Component {
public Font getFont() {
return font;
}
public void setFont(Font font) {
this.font = font;
}
public String getCurrentText() {
return currentText;
@ -100,7 +96,11 @@ public class Label extends Component {
float startX = getX() + font.getAlign() * (getWidth() - currentSize.x);
target.pushTransform(
new Mat4().identity().translate(startX, getY(), 0).scale(2)
new Mat4().identity().translate(startX, getY(), -1000) // TODO wtf
// is this
// magic
// <---
.scale(2)
);
target.addCustomRenderer(font.assemble(currentText, maxWidth));

View File

@ -15,66 +15,14 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.graphics.gui;
import java.util.Objects;
public class Panel extends Component {
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) {
this(name, layout, Colors.WHITE, Colors.LIGHT_GRAY);
}
/**
* @return the fill
*/
public Vec4 getFill() {
return fill;
}
/**
* @param fill the fill to set
*/
public void setFill(Vec4 fill) {
this.fill = Objects.requireNonNull(fill, "fill");
}
/**
* @return the border
*/
public Vec4 getBorder() {
return border;
}
/**
* @param border the border to set
*/
public void setBorder(Vec4 border) {
this.border = border;
}
@Override
protected void assembleSelf(RenderTarget target) {
if (border == null) {
target.fill(getX(), getY(), getWidth(), getHeight(), fill);
} else {
target.fill(getX(), getY(), getWidth(), getHeight(), border);
target.fill(getX() + 2, getY() + 2, getWidth() - 4, getHeight() - 4, fill);
}
super(name);
setLayout(layout);
}
}

View File

@ -1,204 +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.graphics.gui;
import org.lwjgl.glfw.GLFW;
import glm.vec._2.i.Vec2i;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.font.Typefaces;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutHorizontal;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
public class RadioButton extends BasicButton {
private class Tick extends Component {
public Tick() {
super(RadioButton.this.getName() + ".Tick");
setPreferredSize(new Vec2i(Typefaces.getDefault().getLineHeight() * 3 / 2));
}
private void cross(RenderTarget target, int x, int y, int size, Vec4 color) {
target.fill(x + 4, y, size - 8, size, color);
target.fill(x + 2, y + 2, size - 4, size - 4, color);
target.fill(x, y + 4, size, size - 8, color);
}
@Override
protected void assembleSelf(RenderTarget target) {
int size = getPreferredSize().x;
int x = getX();
int y = getY() + (getHeight() - size) / 2;
// Border
Vec4 borderColor;
if (RadioButton.this.isPressed() || RadioButton.this.isHovered() || RadioButton.this.isFocused()) {
borderColor = Colors.BLUE;
} else {
borderColor = Colors.LIGHT_GRAY;
}
cross(target, x, y, size, borderColor);
// Inside area
if (RadioButton.this.isPressed()) {
// Do nothing
} else {
Vec4 backgroundColor;
if (RadioButton.this.isHovered() && RadioButton.this.isEnabled()) {
backgroundColor = Colors.HOVER_BLUE;
} else {
backgroundColor = Colors.WHITE;
}
cross(target, x + 2, y + 2, size - 4, backgroundColor);
}
// "Tick"
if (RadioButton.this.isChecked()) {
cross(target, x + 4, y + 4, size - 8, Colors.BLUE);
}
}
}
private boolean checked;
private RadioButtonGroup group = null;
public RadioButton(String name, String label, Font labelFont, boolean check) {
super(name, label, labelFont);
this.checked = check;
assert getChildren().size() == 1 : "RadioButton expects that BasicButton contains exactly one child";
Component basicChild = getChild(0);
Group group = new Group(getName() + ".LabelAndTick", new LayoutHorizontal(0, 10));
removeChild(basicChild);
setLayout(new LayoutAlign(0, 0.5f, 10));
group.setLayoutHint(basicChild.getLayoutHint());
group.addChild(new Tick());
group.addChild(basicChild);
addChild(group);
addInputListener(KeyEvent.class, e -> {
if (e.isRelease()) return;
if (e.getKey() == GLFW.GLFW_KEY_LEFT || e.getKey() == GLFW.GLFW_KEY_UP) {
if (this.group != null) {
this.group.selectPrevious();
this.group.getSelected().takeFocus();
}
e.consume();
} else if (e.getKey() == GLFW.GLFW_KEY_RIGHT || e.getKey() == GLFW.GLFW_KEY_DOWN) {
if (this.group != null) {
this.group.selectNext();
this.group.getSelected().takeFocus();
}
e.consume();
}
});
addAction(b -> setChecked(true));
}
public RadioButton(String name, String label, Font labelFont) {
this(name, label, labelFont, false);
}
public RadioButton(String name, String label, boolean check) {
this(name, label, new Font(), check);
}
public RadioButton(String name, String label) {
this(name, label, false);
}
/**
* @param group the group to set
*/
public RadioButton setGroup(RadioButtonGroup group) {
if (this.group != null) {
group.selectNext();
removeAction(group.listener);
group.buttons.remove(this);
group.getSelected(); // Clear reference if this was the only button
// in the group
}
this.group = group;
if (this.group != null) {
group.buttons.add(this);
addAction(group.listener);
}
setChecked(false);
return this;
}
/**
* @return the checked
*/
public boolean isChecked() {
return checked;
}
/**
* @param checked the checked to set
*/
public void setChecked(boolean checked) {
this.checked = checked;
if (group != null) {
group.listener.accept(this); // Failsafe for manual invocations of
// setChecked()
}
}
@Override
protected void assembleSelf(RenderTarget target) {
// Change label font color
if (isPressed()) {
getLabel().setFont(getLabel().getFont().withColor(Colors.BLUE));
} else {
getLabel().setFont(getLabel().getFont().withColor(Colors.BLACK));
}
}
@Override
protected void postAssembleSelf(RenderTarget target) {
// Apply disable tint
if (!isEnabled()) {
target.fill(getX(), getY(), getWidth(), getHeight(), Colors.toVector(0x88FFFFFF));
}
}
}

View File

@ -1,119 +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.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);
}
}
}

View File

@ -1,23 +0,0 @@
package ru.windcorp.progressia.client.graphics.gui;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
import ru.windcorp.progressia.client.graphics.texture.Texture;
public class TextureComponent extends Component {
private final Texture texture;
public TextureComponent(String name, Texture texture2) {
super(name);
texture = texture2;
setPreferredSize(texture.getSprite().getWidth(),texture.getSprite().getHeight());
}
@Override
protected void assembleSelf(RenderTarget target)
{
target.drawTexture(getX(), getY(), getWidth(), getHeight(), texture);
}
}

View File

@ -1,60 +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.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;
}
}

View File

@ -1,58 +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.graphics.gui.event;
import glm.vec._2.d.Vec2d;
import ru.windcorp.progressia.client.graphics.gui.Component;
public class DragEvent extends ComponentEvent {
private final Vec2d currentChange = new Vec2d();
private final Vec2d totalChange = new Vec2d();
public DragEvent(Component component, Vec2d currentChange, Vec2d totalChange) {
super(component);
this.currentChange.set(currentChange.x, currentChange.y);
this.totalChange.set(totalChange.x, totalChange.y);
}
public Vec2d getCurrentChange() {
return currentChange;
}
public double getCurrentChangeX() {
return currentChange.x;
}
public double getCurrentChangeY() {
return currentChange.y;
}
public Vec2d getTotalChange() {
return totalChange;
}
public double getTotalChangeX() {
return totalChange.x;
}
public double getTotalChangeY() {
return totalChange.y;
}
}

View File

@ -1,11 +0,0 @@
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);
}
}

View File

@ -27,8 +27,8 @@ import ru.windcorp.progressia.client.graphics.gui.Layout;
public class LayoutAlign implements Layout {
protected final int margin;
protected double alignX, alignY;
private final int margin;
private double alignX, alignY;
public LayoutAlign(double alignX, double alignY, int margin) {
this.alignX = alignX;
@ -72,7 +72,7 @@ public class LayoutAlign implements Layout {
Vec2i result = new Vec2i(0, 0);
c.getChildren().stream()
.map(Component::getPreferredSize)
.map(child -> child.getPreferredSize())
.forEach(size -> {
result.x = max(size.x, result.x);
result.y = max(size.y, result.y);

View File

@ -1,56 +0,0 @@
package ru.windcorp.progressia.client.graphics.gui.layout;
import glm.vec._2.i.Vec2i;
import ru.windcorp.progressia.client.graphics.gui.Component;
import ru.windcorp.progressia.client.graphics.gui.Layout;
import static java.lang.Math.max;
import static java.lang.Math.min;
public class LayoutEdges implements Layout {
private final int margin;
public LayoutEdges(int margin) {
this.margin = margin;
}
@Override
public void layout(Component c) {
for (int i=0;i<2;i++)
{
Component child = c.getChild(i);
Vec2i size = child.getPreferredSize();
int cWidth = c.getWidth() - 2 * margin;
int cHeight = c.getHeight() - 2 * margin;
size.x = min(size.x, cWidth);
size.y = min(size.y, cHeight);
if (i==0) {
child.setBounds(
c.getX() + margin,
c.getY(),
size
);
} else {
child.setBounds(
1920 - size.x - margin,
c.getY(),
size
);
}
}
}
@Override
public Vec2i calculatePreferredSize(Component c) {
Vec2i result = new Vec2i(1920,0);
c.getChildren().stream()
.map(Component::getPreferredSize)
.forEach(size -> result.y = max(Math.abs(size.y), result.y));
return result;
}
}

View File

@ -1,78 +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.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 + ")";
}
}

View File

@ -97,27 +97,16 @@ public class LayoutGrid implements Layout {
void setBounds(int column, int row, Component child, Component parent) {
if (!isSummed)
throw new IllegalStateException("Not summed yet");
int width, height;
if (column == columns.length - 1) {
width = parent.getWidth() - margin - columns[column];
} else {
width = columns[column + 1] - columns[column] - gap;
}
if (row == rows.length - 1) {
height = parent.getHeight() - margin - rows[row];
} else {
height = rows[row + 1] - rows[row] - gap;
}
child.setBounds(
parent.getX() + columns[column],
parent.getY() + parent.getHeight() - (rows[row] + height),
parent.getY() + rows[row],
width,
height
(column != (columns.length - 1) ? (columns[column + 1] - columns[column] - gap)
: (parent.getWidth() - margin - columns[column])),
(row != (rows.length - 1) ? (rows[row + 1] - rows[row] - gap)
: (parent.getHeight() - margin - rows[row]))
);
}
}
@ -143,9 +132,10 @@ public class LayoutGrid implements Layout {
GridDimensions grid = calculateGrid(c);
grid.sum();
int[] coords;
for (Component child : c.getChildren()) {
Vec2i coords = (Vec2i) child.getLayoutHint();
grid.setBounds(coords.x, coords.y, child, c);
coords = (int[]) child.getLayoutHint();
grid.setBounds(coords[0], coords[1], child, c);
}
}
}
@ -159,10 +149,11 @@ public class LayoutGrid implements Layout {
private GridDimensions calculateGrid(Component parent) {
GridDimensions result = new GridDimensions();
int[] coords;
for (Component child : parent.getChildren()) {
Vec2i coords = (Vec2i) child.getLayoutHint();
result.add(coords.x, coords.y, child.getPreferredSize());
coords = (int[]) child.getLayoutHint();
result.add(coords[0], coords[1], child.getPreferredSize());
}
return result;

View File

@ -1,114 +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.graphics.gui.menu;
import org.lwjgl.glfw.GLFW;
import glm.vec._2.i.Vec2i;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.GUI;
import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.gui.Component;
import ru.windcorp.progressia.client.graphics.gui.GUILayer;
import ru.windcorp.progressia.client.graphics.gui.Label;
import ru.windcorp.progressia.client.graphics.gui.Layout;
import ru.windcorp.progressia.client.graphics.gui.Panel;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutFill;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.localization.MutableString;
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
public class MenuLayer extends GUILayer {
private final Component content;
private final Component background;
private final Runnable closeAction = () -> {
GUI.removeLayer(this);
};
public MenuLayer(String name, Component content) {
super(name, new LayoutFill(0));
setCursorPolicy(CursorPolicy.REQUIRE);
this.background = new Panel(name + ".Background", new LayoutAlign(10), Colors.toVector(0x66000000), null);
this.content = content;
background.addChild(content);
getRoot().addChild(background);
}
public MenuLayer(String name, Layout contentLayout) {
this(name, new Panel(name + ".Content", contentLayout));
}
public MenuLayer(String name) {
this(name, new LayoutVertical(20, 10));
}
public Component getContent() {
return content;
}
public Component getBackground() {
return background;
}
protected void addTitle() {
String translationKey = "Layer" + getName() + ".Title";
MutableString titleText = new MutableStringLocalized(translationKey);
Font titleFont = new Font().deriveBold().withColor(Colors.BLACK).withAlign(0.5f);
Label label = new Label(getName() + ".Title", titleFont, titleText);
getContent().addChild(label);
Panel panel = new Panel(getName() + ".Title.Underscore", null, Colors.BLUE, null);
panel.setLayout(new LayoutFill() {
@Override
public Vec2i calculatePreferredSize(Component c) {
return new Vec2i(label.getPreferredSize().x + 40, 4);
}
});
getContent().addChild(panel);
}
protected Runnable getCloseAction() {
return closeAction;
}
@Override
public void handleInput(InputEvent event) {
if (!event.isConsumed()) {
if (event instanceof KeyEvent) {
KeyEvent keyEvent = (KeyEvent) event;
if (keyEvent.isPress() && keyEvent.getKey() == GLFW.GLFW_KEY_ESCAPE) {
getCloseAction().run();
}
}
}
super.handleInput(event);
event.consume();
}
}

View File

@ -18,6 +18,7 @@
package ru.windcorp.progressia.client.graphics.input;
import glm.vec._2.Vec2;
import glm.vec._2.d.Vec2d;
public class CursorMoveEvent extends CursorEvent {
@ -80,10 +81,7 @@ public class CursorMoveEvent extends CursorEvent {
return getNewY() - getPreviousY();
}
public Vec2d getChange(Vec2d result) {
if (result == null) {
result = new Vec2d();
}
public Vec2 getChange(Vec2 result) {
return result.set(getChangeX(), getChangeY());
}

View File

@ -15,35 +15,13 @@
* 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.input;
import ru.windcorp.progressia.client.graphics.gui.Component;
/**
* An instance of user input.
* <p>
* User input events are typically generated by graphics backend between frames
* and passed to the graphics layers from top to bottom. Layers that use
* {@link Component}s will forward this event through the Component hierarchy.
* <p>
* Events have a {@code consumed} flag. A freshly-generated event will have this
* flag set to {@code false}. Event listeners that process the event will
* usually choose to raise the flag ("consume the event") to ask future
* listeners to ignore this event. This is done to avoid multiple UI interfaces
* reacting to single input. By default, listeners will not receive consumed
* events; however, some listeners may choose to receive, handle and even
* un-consume the event.
* <p>
* {@code InputEvent} objects may be reused for future input events after their
* processing is complete; to obtain a static copy, use {@link #snapshot()}.
*/
public abstract class InputEvent {
private double time;
private boolean isConsumed = false;
public InputEvent(double time) {
this.time = time;
}
@ -58,16 +36,4 @@ public abstract class InputEvent {
public abstract InputEvent snapshot();
public boolean isConsumed() {
return isConsumed;
}
public void setConsumed(boolean isConsumed) {
this.isConsumed = isConsumed;
}
public void consume() {
setConsumed(true);
}
}

View File

@ -18,75 +18,16 @@
package ru.windcorp.progressia.client.graphics.input;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.function.Predicate;
import org.lwjgl.glfw.GLFW;
import com.google.common.collect.ImmutableMap;
public class KeyMatcher {
private static final Pattern DECLAR_SPLIT_REGEX = Pattern.compile("\\s*\\+\\s*");
private static final Map<String, Integer> MOD_TOKENS = ImmutableMap.of(
"SHIFT", GLFW.GLFW_MOD_SHIFT,
"CONTROL", GLFW.GLFW_MOD_CONTROL,
"ALT", GLFW.GLFW_MOD_ALT,
"SUPER", GLFW.GLFW_MOD_SUPER
);
public static final KeyMatcher LMB = new KeyMatcher(GLFW.GLFW_MOUSE_BUTTON_LEFT);
public static final KeyMatcher RMB = new KeyMatcher(GLFW.GLFW_MOUSE_BUTTON_RIGHT);
public static final KeyMatcher MMB = new KeyMatcher(GLFW.GLFW_MOUSE_BUTTON_MIDDLE);
private final int key;
private final int mods;
public KeyMatcher(int key, int mods) {
this.key = key;
this.mods = mods;
}
public KeyMatcher(int key) {
this.key = key;
this.mods = 0;
}
public KeyMatcher(String declar) {
String[] tokens = DECLAR_SPLIT_REGEX.split(declar);
if (tokens.length == 0) {
throw new IllegalArgumentException("No tokens found in \"" + declar + "\"");
}
int key = -1;
int mods = 0;
for (String token : tokens) {
token = token.toUpperCase();
if (MOD_TOKENS.containsKey(token)) {
int mod = MOD_TOKENS.get(token);
if ((mods & mod) != 0) {
throw new IllegalArgumentException("Duplicate modifier \"" + token + "\" in \"" + declar + "\"");
}
mods |= mod;
} else if (key != -1) {
throw new IllegalArgumentException("Too many non-modifier tokens in \"" + declar + "\": maximum one key, first offender: \"" + token + "\"");
} else {
token = token.replace(' ', '_');
if (token.startsWith("KEYPAD_")) {
token = "KP_" + token.substring("KEYPAD_".length());
}
key = Keys.getCode(token);
if (key == -1) {
throw new IllegalArgumentException("Unknown token \"" + token + "\" in \"" + declar + "\"");
}
}
}
protected KeyMatcher(int key, int mods) {
this.key = key;
this.mods = mods;
}
@ -101,15 +42,6 @@ public class KeyMatcher {
return true;
}
public boolean matchesIgnoringAction(KeyEvent event) {
if (event.getKey() != getKey())
return false;
if ((event.getMods() & getMods()) != getMods())
return false;
return true;
}
public int getKey() {
return key;
@ -118,25 +50,49 @@ public class KeyMatcher {
public int getMods() {
return mods;
}
public KeyMatcher with(int modifier) {
return new KeyMatcher(key, mods | modifier);
public static KeyMatcher.Builder of(int key) {
return new KeyMatcher.Builder(key);
}
public KeyMatcher withShift() {
return with(GLFW.GLFW_MOD_SHIFT);
}
public static class Builder {
public KeyMatcher withCtrl() {
return with(GLFW.GLFW_MOD_CONTROL);
}
private final int key;
private int mods = 0;
public KeyMatcher withAlt() {
return with(GLFW.GLFW_MOD_ALT);
}
public Builder(int key) {
this.key = key;
}
public Builder with(int modifier) {
this.mods += modifier;
return this;
}
public Builder withShift() {
return with(GLFW.GLFW_MOD_SHIFT);
}
public Builder withCtrl() {
return with(GLFW.GLFW_MOD_CONTROL);
}
public Builder withAlt() {
return with(GLFW.GLFW_MOD_ALT);
}
public Builder withSuper() {
return with(GLFW.GLFW_MOD_SUPER);
}
public KeyMatcher build() {
return new KeyMatcher(key, mods);
}
public Predicate<KeyEvent> matcher() {
return build()::matches;
}
public KeyMatcher withSuper() {
return with(GLFW.GLFW_MOD_SUPER);
}
}

View File

@ -139,7 +139,7 @@ public class Keys {
}
public static int getCode(String internalName) {
if (!NAMES_TO_CODES.containsKey(internalName)) {
if (NAMES_TO_CODES.containsKey(internalName)) {
return -1;
} else {
return NAMES_TO_CODES.get(internalName);

View File

@ -15,30 +15,48 @@
* 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;
package ru.windcorp.progressia.client.graphics.input.bus;
import glm.vec._2.d.Vec2d;
import ru.windcorp.progressia.client.graphics.gui.Component;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
public class DragStopEvent extends ComponentEvent {
public class Input {
private final Vec2d totalChange = new Vec2d();
public DragStopEvent(Component component, Vec2d totalChange) {
super(component);
this.totalChange.set(totalChange.x, totalChange.y);
public static enum Target {
FOCUSED, HOVERED, ALL
}
public Vec2d getTotalChange() {
return totalChange;
private InputEvent event;
private boolean isConsumed;
private Target target;
protected void initialize(InputEvent event, Target target) {
this.event = event;
this.target = target;
this.isConsumed = false;
}
public double getTotalChangeX() {
return totalChange.x;
public InputEvent getEvent() {
return event;
}
public double getTotalChangeY() {
return totalChange.y;
public boolean isConsumed() {
return isConsumed;
}
public void setConsumed(boolean isConsumed) {
this.isConsumed = isConsumed;
}
public void consume() {
setConsumed(true);
}
public Target getTarget() {
return target;
}
}

View File

@ -15,401 +15,73 @@
* 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.input.bus;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;
import ru.windcorp.jputil.ArrayUtil;
import ru.windcorp.progressia.client.graphics.gui.Component;
import ru.windcorp.progressia.client.graphics.input.CursorEvent;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
import ru.windcorp.progressia.client.graphics.input.WheelEvent;
import ru.windcorp.progressia.common.util.crash.CrashReports;
/**
* An event bus optionally related to a {@link Component} that delivers input
* events to input listeners. This bus may skip listeners based on circumstance;
* behavior can be customized for each listener with {@link Option}s.
* <p>
* By default, events are filtered by four checks before being delivered to each
* listener:
* <ol>
* <li><em>Consumption check</em>: unless {@link Option#RECEIVE_CONSUMED
* RECEIVE_CONSUMED} is set, events that are consumed will not be
* delivered.</li>
* <li><em>Hover check</em>: for certain event types (for example,
* {@link WheelEvent} or {@link KeyEvent} that {@link KeyEvent#isMouse()
* isMouse()}), the event will only be delivered if the component is hovered.
* This check may be bypassed with option {@link Option#IGNORE_HOVER
* IGNORE_HOVER} or made mandatory for all events with
* {@link Option#REQUIRE_HOVER REQUIRE_HOVER}. Hover check automatically
* succeeds if no component is provided.</li>
* <li><em>Focus check</em>: for certain event types (for example,
* {@link KeyEvent} that {@code !isMouse()}), the event will only be delivered
* if the component has focus. This check may be bypassed with option
* {@link Option#IGNORE_FOCUS IGNORE_FOCUS} or made mandatory for all events
* with {@link Option#REQUIRE_FOCUS REQUIRE_FOCUS}. Focus check automatically
* succeeds if no component is provided.</li>
* <li><em>Type check</em>: events of type {@code E} are only delivered to
* listeners registered with event type {@code T} if objects of type {@code E}
* can be cast to {@code T}.</li>
* </ol>
* Checks 1-3 are bypassed when option {@link Option#ALWAYS ALWAYS} is
* specified.
*/
public class InputBus {
/**
* Options that allow customization of checks for listeners.
*/
public enum Option {
/**
* Ignore checks for consumed events, hover and focus; deliver event if
* at all possible. This is shorthand for {@link #RECEIVE_CONSUMED},
* {@link #IGNORE_HOVER} and {@link #IGNORE_FOCUS}.
*/
ALWAYS,
/**
* Receive events that were previously consumed.
*/
RECEIVE_CONSUMED,
/**
* Do not process events if the listener is registered with a component
* and the component is not hovered.
*/
REQUIRE_HOVER,
/**
* Deliver events even if the event is limited to hovered components by
* default.
*/
IGNORE_HOVER,
/**
* Do not process events if the listener is registered with a component
* and the component is not focused.
*/
REQUIRE_FOCUS,
/**
* Deliver events even if the event is limited to focused components by
* default.
*/
IGNORE_FOCUS,
/**
* Deliver events according to
* {@link KeyMatcher#matchesIgnoringAction(KeyEvent)} rather than
* {@link KeyMatcher#matches(KeyEvent)} when a {@link KeyMatcher} is
* specified.
*/
IGNORE_ACTION;
}
private enum YesNoDefault {
YES, NO, DEFAULT;
}
/**
* A listener with check preferences resolved and type specified.
*/
private class WrappedListener {
private static class WrappedListener {
private final Class<?> type;
private final boolean dropIfConsumed;
private final YesNoDefault dropIfNotHovered;
private final YesNoDefault dropIfNotFocused;
private final boolean handleConsumed;
private final InputListener<?> listener;
public WrappedListener(
Class<?> type,
boolean dropIfConsumed,
YesNoDefault dropIfNotHovered,
YesNoDefault dropIfNotFocused,
boolean handleConsumed,
InputListener<?> listener
) {
this.type = type;
this.dropIfConsumed = dropIfConsumed;
this.dropIfNotHovered = dropIfNotHovered;
this.dropIfNotFocused = dropIfNotFocused;
this.handleConsumed = handleConsumed;
this.listener = listener;
}
private boolean handles(InputEvent input) {
if (dropIfConsumed && input.isConsumed())
return false;
switch (dropIfNotHovered) {
case YES:
if (!isHovered())
return false;
break;
case NO:
break;
default:
if (isHovered())
break;
if (input instanceof KeyEvent && ((KeyEvent) input).isMouse())
return false;
if (input instanceof CursorEvent)
return false;
if (input instanceof WheelEvent)
return false;
break;
}
switch (dropIfNotFocused) {
case YES:
if (!isFocused())
return false;
break;
case NO:
break;
default:
if (isFocused())
break;
if (input instanceof KeyEvent && !((KeyEvent) input).isMouse())
return false;
break;
}
if (!type.isInstance(input))
return false;
return true;
private boolean handles(Input input) {
return (!input.isConsumed() || handleConsumed) &&
type.isInstance(input.getEvent());
}
/**
* Invokes the listener if the event is deemed appropriate by the four
* checks.
*
* @param event the event to deliver
*/
@SuppressWarnings("unchecked")
public void handle(InputEvent event) {
if (handles(event)) {
// A runtime check of types has been performed; this is safe.
InputListener<InputEvent> castListener = (InputListener<InputEvent>) listener;
try {
castListener.handle(event);
} catch (Exception e) {
throw CrashReports.report(
e,
"InputListener %s for component %s has failed to receive event %s",
listener,
owner,
event
public void handle(Input input) {
if (handles(input)) {
boolean consumed = ((InputListener<InputEvent>) listener)
.handle(
(InputEvent) type.cast(input.getEvent())
);
}
input.setConsumed(consumed);
}
}
}
/**
* The component queried for focus and hover. May be {@code null}.
*/
private final Component owner;
/**
* Registered listeners.
*/
private final Collection<WrappedListener> listeners = new ArrayList<>(4);
/**
* Creates a new input bus that consults the specified {@link Component} to
* determine hover and focus.
*
* @param owner the component to use for hover and focus tests
* @see #InputBus()
*/
public InputBus(Component owner) {
this.owner = Objects.requireNonNull(owner, "owner");
public void dispatch(Input input) {
listeners.forEach(l -> l.handle(input));
}
/**
* Creates a new input bus that assumes all hover and focus checks are
* successful.
*
* @see #InputBus(Component)
*/
public InputBus() {
this.owner = null;
}
/**
* Determines whether hover should be assumed for this event bus.
*
* @return {@code true} iff no component is linked or the linked component
* is hovered
*/
private boolean isHovered() {
return owner == null ? true : owner.isHovered();
}
/**
* Determines whether focus should be assumed for this event bus.
*
* @return {@code true} iff no component is linked or the linked component
* is focused
*/
private boolean isFocused() {
return owner == null ? true : owner.isFocused();
}
/**
* Dispatches (delivers) the provided event to all appropriate listeners.
*
* @param event the event to process
*/
public void dispatch(InputEvent event) {
Objects.requireNonNull(event, "event");
for (WrappedListener listener : listeners) {
listener.handle(event);
}
}
/**
* Registers a listener on this bus.
* <p>
* {@code type} specifies the class of events that should be passed to this
* listener. Only events of types that extend, implement or equal
* {@code type} are processed.
* <p>
* Zero or more {@link Option}s may be specified to enable or disable the
* processing of certain events in certain circumstances. See
* {@linkplain InputBus class description} for a detailed breakdown of the
* checks performed and the effects of various options. When providing
* options to this method, later options override the effects of previous
* options.
* <p>
* Option {@link Option#IGNORE_ACTION IGNORE_ACTION} is ignored silently.
*
* @param type the event class to deliver
* @param listener the listener
* @param options the options for this listener
*/
public <T extends InputEvent> void register(
Class<? extends T> type,
InputListener<T> listener,
Option... options
boolean handlesConsumed,
InputListener<T> listener
) {
Objects.requireNonNull(type, "type");
Objects.requireNonNull(listener, "listener");
boolean dropIfConsumed = true;
YesNoDefault dropIfNotHovered = YesNoDefault.DEFAULT;
YesNoDefault dropIfNotFocused = YesNoDefault.DEFAULT;
if (options != null) {
for (Option option : options) {
switch (option) {
case ALWAYS:
dropIfConsumed = false;
dropIfNotHovered = YesNoDefault.NO;
dropIfNotFocused = YesNoDefault.NO;
break;
case RECEIVE_CONSUMED:
dropIfConsumed = false;
break;
case REQUIRE_HOVER:
dropIfNotHovered = YesNoDefault.YES;
break;
case IGNORE_HOVER:
dropIfNotFocused = YesNoDefault.NO;
break;
case REQUIRE_FOCUS:
dropIfNotHovered = YesNoDefault.YES;
break;
case IGNORE_FOCUS:
dropIfNotFocused = YesNoDefault.NO;
break;
case IGNORE_ACTION:
// Ignore
break;
default:
throw new IllegalArgumentException("Unexpected option " + option);
}
}
}
listeners.add(new WrappedListener(type, dropIfConsumed, dropIfNotHovered, dropIfNotFocused, listener));
listeners.add(new WrappedListener(type, handlesConsumed, listener));
}
/**
* Registers a {@link KeyEvent} listener on this bus. An event has to match
* the provided {@link KeyMatcher} to be delivered to the listener.
* <p>
* Zero or more {@link Option}s may be specified to enable or disable the
* processing of certain events in certain circumstances. See
* {@linkplain InputBus class description} for a detailed breakdown of the
* checks performed and the effects of various options. When providing
* options to this method, later options override the effects of previous
* options.
* <p>
* Option {@link Option#IGNORE_ACTION IGNORE_ACTION} requests that events
* are delivered according to
* {@link KeyMatcher#matchesIgnoringAction(KeyEvent)} rather than
* {@link KeyMatcher#matches(KeyEvent)}.
* specified.
*
* @param matcher an event filter
* @param listener the listener
* @param options the options for this listener
*/
public void register(KeyMatcher matcher, InputListener<? super KeyEvent> listener, Option... options) {
Objects.requireNonNull(matcher, "matcher");
Objects.requireNonNull(listener, "listener");
InputListener<KeyEvent> filteringListener;
if (ArrayUtil.firstIndexOf(options, Option.IGNORE_ACTION) != -1) {
filteringListener = e -> {
if (matcher.matchesIgnoringAction(e)) {
listener.handle(e);
}
};
} else {
filteringListener = e -> {
if (matcher.matches(e)) {
listener.handle(e);
}
};
}
register(KeyEvent.class, filteringListener, options);
public <T extends InputEvent> void register(
Class<? extends T> type,
InputListener<T> listener
) {
register(type, false, listener);
}
/**
* Removes all occurrences of the provided listener from this bus.
*
* @param listener the listener to unregister
*/
public void unregister(InputListener<?> listener) {
if (listener == null) {
return;
}
listeners.removeIf(l -> l.listener == listener);
}

View File

@ -23,6 +23,6 @@ import ru.windcorp.progressia.client.graphics.input.InputEvent;
@FunctionalInterface
public interface InputListener<T extends InputEvent> {
void handle(T event);
boolean handle(T event);
}

View File

@ -18,23 +18,23 @@
package ru.windcorp.progressia.client.graphics.model;
import static ru.windcorp.progressia.common.world.rels.AbsFace.*;
import static ru.windcorp.progressia.common.world.block.BlockFace.*;
import com.google.common.collect.ImmutableMap;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.common.world.rels.AbsFace;
import ru.windcorp.progressia.common.world.block.BlockFace;
class BlockFaceVectors {
private static BlockFaceVectors createInner(BlockFaceVectors outer) {
ImmutableMap.Builder<AbsFace, Vec3> originBuilder = ImmutableMap.builder();
ImmutableMap.Builder<BlockFace, Vec3> originBuilder = ImmutableMap.builder();
ImmutableMap.Builder<AbsFace, Vec3> widthBuilder = ImmutableMap.builder();
ImmutableMap.Builder<BlockFace, Vec3> widthBuilder = ImmutableMap.builder();
ImmutableMap.Builder<AbsFace, Vec3> heightBuilder = ImmutableMap.builder();
ImmutableMap.Builder<BlockFace, Vec3> heightBuilder = ImmutableMap.builder();
for (AbsFace face : getFaces()) {
for (BlockFace face : getFaces()) {
Vec3 width = outer.getWidth(face);
Vec3 height = outer.getHeight(face);
@ -59,36 +59,36 @@ class BlockFaceVectors {
static {
OUTER = new BlockFaceVectors(
ImmutableMap.<AbsFace, Vec3>builder()
ImmutableMap.<BlockFace, Vec3>builder()
.put(POS_Z, new Vec3(-0.5f, +0.5f, +0.5f))
.put(NEG_Z, new Vec3(-0.5f, -0.5f, -0.5f))
.put(POS_X, new Vec3(+0.5f, -0.5f, -0.5f))
.put(NEG_X, new Vec3(-0.5f, +0.5f, -0.5f))
.put(POS_Y, new Vec3(+0.5f, +0.5f, -0.5f))
.put(NEG_Y, new Vec3(-0.5f, -0.5f, -0.5f))
.put(TOP, new Vec3(-0.5f, +0.5f, +0.5f))
.put(BOTTOM, new Vec3(-0.5f, -0.5f, -0.5f))
.put(NORTH, new Vec3(+0.5f, -0.5f, -0.5f))
.put(SOUTH, new Vec3(-0.5f, +0.5f, -0.5f))
.put(WEST, new Vec3(+0.5f, +0.5f, -0.5f))
.put(EAST, new Vec3(-0.5f, -0.5f, -0.5f))
.build(),
ImmutableMap.<AbsFace, Vec3>builder()
ImmutableMap.<BlockFace, Vec3>builder()
.put(POS_Z, new Vec3(0, -1, 0))
.put(NEG_Z, new Vec3(0, +1, 0))
.put(POS_X, new Vec3(0, +1, 0))
.put(NEG_X, new Vec3(0, -1, 0))
.put(POS_Y, new Vec3(-1, 0, 0))
.put(NEG_Y, new Vec3(+1, 0, 0))
.put(TOP, new Vec3(0, -1, 0))
.put(BOTTOM, new Vec3(0, +1, 0))
.put(NORTH, new Vec3(0, +1, 0))
.put(SOUTH, new Vec3(0, -1, 0))
.put(WEST, new Vec3(-1, 0, 0))
.put(EAST, new Vec3(+1, 0, 0))
.build(),
ImmutableMap.<AbsFace, Vec3>builder()
ImmutableMap.<BlockFace, Vec3>builder()
.put(POS_Z, new Vec3(+1, 0, 0))
.put(NEG_Z, new Vec3(+1, 0, 0))
.put(POS_X, new Vec3(0, 0, +1))
.put(NEG_X, new Vec3(0, 0, +1))
.put(POS_Y, new Vec3(0, 0, +1))
.put(NEG_Y, new Vec3(0, 0, +1))
.put(TOP, new Vec3(+1, 0, 0))
.put(BOTTOM, new Vec3(+1, 0, 0))
.put(NORTH, new Vec3(0, 0, +1))
.put(SOUTH, new Vec3(0, 0, +1))
.put(WEST, new Vec3(0, 0, +1))
.put(EAST, new Vec3(0, 0, +1))
.build()
);
@ -100,29 +100,29 @@ class BlockFaceVectors {
return inner ? INNER : OUTER;
}
private final ImmutableMap<AbsFace, Vec3> origins;
private final ImmutableMap<AbsFace, Vec3> widths;
private final ImmutableMap<AbsFace, Vec3> heights;
private final ImmutableMap<BlockFace, Vec3> origins;
private final ImmutableMap<BlockFace, Vec3> widths;
private final ImmutableMap<BlockFace, Vec3> heights;
public BlockFaceVectors(
ImmutableMap<AbsFace, Vec3> origins,
ImmutableMap<AbsFace, Vec3> widths,
ImmutableMap<AbsFace, Vec3> heights
ImmutableMap<BlockFace, Vec3> origins,
ImmutableMap<BlockFace, Vec3> widths,
ImmutableMap<BlockFace, Vec3> heights
) {
this.origins = origins;
this.widths = widths;
this.heights = heights;
}
public Vec3 getOrigin(AbsFace face) {
public Vec3 getOrigin(BlockFace face) {
return origins.get(face);
}
public Vec3 getWidth(AbsFace face) {
public Vec3 getWidth(BlockFace face) {
return widths.get(face);
}
public Vec3 getHeight(AbsFace face) {
public Vec3 getHeight(BlockFace face) {
return heights.get(face);
}
}

View File

@ -24,7 +24,7 @@ import java.util.Objects;
import ru.windcorp.progressia.client.graphics.texture.Texture;
public class ShapePart implements Comparable<ShapePart> {
public class Face implements Comparable<Face> {
private static final ShortBuffer GENERATE_SUCCESSIVE_LATER = null;
@ -40,7 +40,7 @@ public class ShapePart implements Comparable<ShapePart> {
private ShortBuffer userIndices;
private boolean userIndicesUpdated = true;
public ShapePart(
public Face(
Texture texture,
ByteBuffer vertices,
ShortBuffer indices
@ -50,7 +50,7 @@ public class ShapePart implements Comparable<ShapePart> {
setIndices(indices);
}
public ShapePart(
public Face(
Texture texture,
ByteBuffer vertices
) {
@ -155,7 +155,7 @@ public class ShapePart implements Comparable<ShapePart> {
return vertices;
}
public ShapePart setVertices(ByteBuffer vertices) {
public Face setVertices(ByteBuffer vertices) {
this.vertices = Objects.requireNonNull(vertices, "vertices");
markForVertexUpdate();
return this;
@ -202,7 +202,7 @@ public class ShapePart implements Comparable<ShapePart> {
return userIndices.remaining();
}
public ShapePart setIndices(ShortBuffer indices) {
public Face setIndices(ShortBuffer indices) {
if (indices == null) {
indices = GENERATE_SUCCESSIVE_LATER;
}
@ -245,7 +245,7 @@ public class ShapePart implements Comparable<ShapePart> {
}
@Override
public int compareTo(ShapePart o) {
public int compareTo(Face o) {
return Integer.compare(getSortingIndex(), o.getSortingIndex());
}

View File

@ -21,13 +21,13 @@ package ru.windcorp.progressia.client.graphics.model;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.graphics.texture.TexturePrimitive;
public class ShapePartGroup {
public class FaceGroup {
private final TexturePrimitive texture;
private final int indexCount;
private final int byteOffsetOfIndices;
ShapePartGroup(ShapePart[] faces, int start, int end) {
FaceGroup(Face[] faces, int start, int end) {
Texture t = faces[start].getTexture();
this.texture = t == null ? null : t.getSprite().getPrimitive();
@ -36,7 +36,7 @@ public class ShapePartGroup {
int indexCount = 0;
for (int i = start; i < end; ++i) {
ShapePart face = faces[i];
Face face = faces[i];
assert this.texture == null
? (face.getTexture() == null)

View File

@ -25,15 +25,14 @@ import glm.vec._3.Vec3;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram.VertexBuilder;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram.WRPVertexBuilder;
import ru.windcorp.progressia.common.world.rels.AbsFace;
import ru.windcorp.progressia.common.world.block.BlockFace;
public class ShapeParts {
public class Faces {
private ShapeParts() {
private Faces() {
}
public static ShapePart createRectangle(
public static Face createRectangle(
ShapeRenderProgram program,
Texture texture,
Vec4 colorMultiplier,
@ -41,66 +40,26 @@ public class ShapeParts {
Vec3 width,
Vec3 height,
boolean flip
) {
return createRectangle(program, texture, colorMultiplier, origin, width, height, flip, null);
}
public static ShapePart createRectangle(
ShapeRenderProgram program,
Texture texture,
Vec4 colorMultiplier,
Vec3 origin,
Vec3 width,
Vec3 height,
boolean flip,
Vec3 forcedNormals
) {
VertexBuilder builder = program.getVertexBuilder();
if (forcedNormals != null && builder instanceof WRPVertexBuilder) {
((WRPVertexBuilder) builder).addVertex(
origin,
colorMultiplier,
new Vec2(0, 0),
forcedNormals
);
((WRPVertexBuilder) builder).addVertex(
origin.add_(height),
colorMultiplier,
new Vec2(0, 1),
forcedNormals
);
((WRPVertexBuilder) builder).addVertex(
origin.add_(width),
colorMultiplier,
new Vec2(1, 0),
forcedNormals
);
((WRPVertexBuilder) builder).addVertex(
origin.add_(width).add(height),
colorMultiplier,
new Vec2(1, 1),
forcedNormals
);
} else {
builder.addVertex(
origin,
colorMultiplier,
new Vec2(0, 0)
).addVertex(
origin.add_(height),
colorMultiplier,
new Vec2(0, 1)
).addVertex(
origin.add_(width),
colorMultiplier,
new Vec2(1, 0)
).addVertex(
origin.add_(width).add(height),
colorMultiplier,
new Vec2(1, 1)
);
}
builder.addVertex(
origin,
colorMultiplier,
new Vec2(0, 0)
).addVertex(
origin.add_(height),
colorMultiplier,
new Vec2(0, 1)
).addVertex(
origin.add_(width),
colorMultiplier,
new Vec2(1, 0)
).addVertex(
origin.add_(width).add(height),
colorMultiplier,
new Vec2(1, 1)
);
ShortBuffer buffer = flip ? ShortBuffer.wrap(
new short[] {
@ -123,19 +82,19 @@ public class ShapeParts {
}
);
return new ShapePart(
return new Face(
texture,
builder.assemble(),
buffer
);
}
public static ShapePart createBlockFace(
public static Face createBlockFace(
ShapeRenderProgram program,
Texture texture,
Vec4 colorMultiplier,
Vec3 blockCenter,
AbsFace face,
BlockFace face,
boolean inner
) {
BlockFaceVectors vectors = BlockFaceVectors.get(inner);
@ -153,6 +112,6 @@ public class ShapeParts {
height,
inner
);
}
}
}

View File

@ -30,10 +30,10 @@ import ru.windcorp.progressia.client.graphics.backend.VertexBufferObject;
public class Shape implements Renderable {
private final ShapeRenderProgram program;
private final ShapePart[] parts;
private final Face[] faces;
private final Usage usage;
private ShapePartGroup[] groups;
private FaceGroup[] groups;
private ByteBuffer vertices;
private ShortBuffer indices;
@ -45,33 +45,33 @@ public class Shape implements Renderable {
private VertexBufferObject verticesVbo;
private VertexBufferObject indicesVbo;
public Shape(Usage usage, ShapeRenderProgram program, ShapePart... parts) {
public Shape(Usage usage, ShapeRenderProgram program, Face... faces) {
this.program = program;
this.parts = parts;
this.faces = faces;
this.usage = usage;
configureParts();
configureFaces();
program.preprocess(this);
assembleBuffers();
}
private void configureParts() {
for (ShapePart part : parts) {
part.setShape(this);
private void configureFaces() {
for (Face face : faces) {
face.setShape(this);
}
}
private void assembleBuffers() {
// TODO optimize: only update faces that requested it
sortParts();
sortFaces();
resizeBuffers();
for (ShapePart part : parts) {
assembleVertices(part);
assembleIndices(part);
part.resetUpdateFlags();
for (Face face : faces) {
assembleVertices(face);
assembleIndices(face);
face.resetUpdateFlags();
}
this.vertices.flip();
@ -85,110 +85,110 @@ public class Shape implements Renderable {
private void resizeBuffers() {
int verticesRequired = 0, indicesRequired = 0;
for (ShapePart part : parts) {
verticesRequired += part.getVertices().remaining();
indicesRequired += part.getIndices().remaining();
for (Face face : faces) {
verticesRequired += face.getVertices().remaining();
indicesRequired += face.getIndices().remaining();
}
if (vertices == null || vertices.capacity() < verticesRequired) {
if (this.vertices == null || vertices.capacity() < verticesRequired) {
this.vertices = BufferUtils.createByteBuffer(verticesRequired);
} else {
vertices.position(0).limit(verticesRequired);
this.vertices.position(0).limit(verticesRequired);
}
if (indices == null || indices.capacity() < indicesRequired) {
if (this.indices == null || this.indices.capacity() < indicesRequired) {
this.indices = BufferUtils.createShortBuffer(indicesRequired);
} else {
indices.position(0).limit(indicesRequired);
this.indices.position(0).limit(indicesRequired);
}
}
private void assembleVertices(ShapePart part) {
part.locationOfVertices = this.vertices.position();
private void assembleVertices(Face face) {
face.locationOfVertices = this.vertices.position();
insertVertices(part);
linkVerticesWith(part);
insertVertices(face);
linkVerticesWith(face);
}
private void insertVertices(ShapePart part) {
ByteBuffer partVertices = part.getVertices();
private void insertVertices(Face face) {
ByteBuffer faceVertices = face.getVertices();
partVertices.mark();
this.vertices.put(partVertices);
partVertices.reset();
faceVertices.mark();
this.vertices.put(faceVertices);
faceVertices.reset();
}
private void linkVerticesWith(ShapePart part) {
private void linkVerticesWith(Face face) {
int limit = vertices.limit();
int position = vertices.position();
vertices.limit(position).position(part.getLocationOfVertices());
part.vertices = vertices.slice();
vertices.limit(position).position(face.getLocationOfVertices());
face.vertices = vertices.slice();
vertices.position(position).limit(limit);
}
private void assembleIndices(ShapePart part) {
short vertexOffset = (short) (part.getLocationOfVertices() / program.getBytesPerVertex());
private void assembleIndices(Face face) {
short vertexOffset = (short) (face.getLocationOfVertices() / program.getBytesPerVertex());
part.locationOfIndices = indices.position();
face.locationOfIndices = indices.position();
ShortBuffer partIndices = part.getIndices();
ShortBuffer faceIndices = face.getIndices();
if (partIndices == null) {
for (int i = 0; i < part.getVertexCount(); ++i) {
if (faceIndices == null) {
for (int i = 0; i < face.getVertexCount(); ++i) {
this.indices.put((short) (vertexOffset + i));
}
} else {
for (int i = partIndices.position(); i < partIndices.limit(); ++i) {
short partIndex = partIndices.get(i);
partIndex += vertexOffset;
this.indices.put(partIndex);
for (int i = faceIndices.position(); i < faceIndices.limit(); ++i) {
short faceIndex = faceIndices.get(i);
faceIndex += vertexOffset;
this.indices.put(faceIndex);
}
}
}
private void sortParts() {
Arrays.sort(parts);
private void sortFaces() {
Arrays.sort(faces);
}
private void assembleGroups() {
int unique = countUniqueParts();
this.groups = new ShapePartGroup[unique];
int unique = countUniqueFaces();
this.groups = new FaceGroup[unique];
if (parts.length == 0)
if (faces.length == 0)
return;
int previousHandle = parts[0].getSortingIndex();
int previousHandle = faces[0].getSortingIndex();
int start = 0;
int groupIndex = 0;
for (int i = 1; i < parts.length; ++i) {
if (previousHandle != parts[i].getSortingIndex()) {
for (int i = 1; i < faces.length; ++i) {
if (previousHandle != faces[i].getSortingIndex()) {
groups[groupIndex] = new ShapePartGroup(parts, start, i);
groups[groupIndex] = new FaceGroup(faces, start, i);
start = i;
groupIndex++;
previousHandle = parts[i].getSortingIndex();
previousHandle = faces[i].getSortingIndex();
}
}
assert groupIndex == groups.length - 1;
groups[groupIndex] = new ShapePartGroup(parts, start, parts.length);
groups[groupIndex] = new FaceGroup(faces, start, faces.length);
}
private int countUniqueParts() {
if (parts.length == 0)
private int countUniqueFaces() {
if (faces.length == 0)
return 0;
int result = 1;
int previousHandle = parts[0].getSortingIndex();
int previousHandle = faces[0].getSortingIndex();
for (int i = 1; i < parts.length; ++i) {
if (previousHandle != parts[i].getSortingIndex()) {
for (int i = 1; i < faces.length; ++i) {
if (previousHandle != faces[i].getSortingIndex()) {
result++;
previousHandle = parts[i].getSortingIndex();
previousHandle = faces[i].getSortingIndex();
}
}
@ -238,11 +238,11 @@ public class Shape implements Renderable {
return program;
}
public ShapePart[] getParts() {
return parts;
public Face[] getFaces() {
return faces;
}
public ShapePartGroup[] getGroups() {
public FaceGroup[] getGroups() {
return groups;
}

View File

@ -1,248 +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.graphics.model;
import java.nio.ShortBuffer;
import java.util.Objects;
import glm.mat._3.Mat3;
import glm.mat._4.Mat4;
import glm.vec._2.Vec2;
import glm.vec._3.Vec3;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram.VertexBuilder;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram.WRPVertexBuilder;
import ru.windcorp.progressia.common.util.StashingStack;
import ru.windcorp.progressia.common.util.VectorUtil;
public class ShapePrototype {
private final ShapeRenderProgram program;
private Texture texture;
private final Vec3[] positions;
private final Vec4[] colorMultipliers;
private final Vec2[] textureCoords;
private final Vec3[] forcedNormals;
private ShortBuffer indices;
private ShortBuffer flippedIndices = null;
protected static final int TRANSFORM_STACK_SIZE = 64;
private final StashingStack<Mat4> transformStack = new StashingStack<>(
TRANSFORM_STACK_SIZE,
Mat4::new
);
public ShapePrototype(
ShapeRenderProgram program,
Texture texture,
Vec3[] positions,
Vec4[] colorMultipliers,
Vec2[] textureCoords,
Vec3[] forcedNormals,
ShortBuffer indices
) {
this.program = Objects.requireNonNull(program, "program");
this.texture = texture;
this.indices = Objects.requireNonNull(indices, "indices");
Objects.requireNonNull(positions, "positions");
Objects.requireNonNull(colorMultipliers, "colorMultipliers");
Objects.requireNonNull(textureCoords, "textureCoords");
if (forcedNormals != null && !(program instanceof WorldRenderProgram)) {
throw new IllegalArgumentException("Cannot force normals on non-WorldRenderPrograms cuz javahorse stupiddd");
}
if (forcedNormals == null) {
forcedNormals = new Vec3[positions.length];
}
this.positions = positions;
this.colorMultipliers = colorMultipliers;
this.textureCoords = textureCoords;
this.forcedNormals = forcedNormals;
if (positions.length != colorMultipliers.length) {
throw new IllegalArgumentException("positions.length (" + positions.length + ") != colorMultipliers.length (" + colorMultipliers + ")");
}
if (positions.length != textureCoords.length) {
throw new IllegalArgumentException("positions.length (" + positions.length + ") != textureCoords.length (" + textureCoords + ")");
}
if (positions.length != forcedNormals.length) {
throw new IllegalArgumentException("positions.length (" + positions.length + ") != forcedNormals.length (" + forcedNormals + ")");
}
transformStack.push().identity();
}
public ShapePart build() {
VertexBuilder builder = program.getVertexBuilder();
Vec3 transformedPosition = new Vec3();
for (int i = 0; i < positions.length; ++i) {
transformedPosition.set(positions[i]);
if (transformStack.getSize() > 1) {
VectorUtil.applyMat4(transformedPosition, transformStack.getHead());
}
if (forcedNormals[i] != null && builder instanceof WRPVertexBuilder) {
((WRPVertexBuilder) builder).addVertex(
transformedPosition, colorMultipliers[i], textureCoords[i], forcedNormals[i]
);
} else {
builder.addVertex(transformedPosition, colorMultipliers[i], textureCoords[i]);
}
}
return new ShapePart(texture, builder.assemble(), indices);
}
public ShapePrototype apply(Mat4 transform) {
for (Vec3 vector : positions) {
VectorUtil.applyMat4(vector, transform);
}
return this;
}
public ShapePrototype apply(Mat3 transform) {
for (Vec3 vector : positions) {
transform.mul(vector);
}
return this;
}
public Mat4 push() {
Mat4 previous = transformStack.getHead();
return transformStack.push().set(previous);
}
public ShapePrototype pop() {
transformStack.pop();
return this;
}
public ShapePrototype setTexture(Texture texture) {
this.texture = texture;
return this;
}
public ShapePrototype deleteTexture() {
this.texture = null;
return this;
}
public ShapePrototype resetColorMultiplier() {
for (Vec4 color : colorMultipliers) {
color.set(Colors.WHITE);
}
return this;
}
public ShapePrototype addColorMultiplier(Vec4 color) {
for (Vec4 c : colorMultipliers) {
c.mul(color);
}
return this;
}
public ShapePrototype flip() {
ShortBuffer tmp = indices;
indices = flippedIndices;
flippedIndices = tmp;
if (indices == null) {
int length = flippedIndices.limit();
indices = ShortBuffer.allocate(length);
for (int i = 0; i < length; ++i) {
indices.put(i, flippedIndices.get(length - i - 1));
}
}
return this;
}
public ShapePrototype makeDoubleSided() {
int length = indices.limit();
ShortBuffer newIndices = ShortBuffer.allocate(length * 2);
for (int i = 0; i < length; ++i) {
newIndices.put(i, indices.get(i));
}
for (int i = 0; i < length; ++i) {
newIndices.put(i + length, indices.get(length - i - 1));
}
indices = flippedIndices = newIndices;
return this;
}
public static ShapePrototype unitSquare(Texture texture, Vec4 color, ShapeRenderProgram program) {
return new ShapePrototype(
program,
texture,
new Vec3[] {
new Vec3(0, 0, 0),
new Vec3(0, 1, 0),
new Vec3(1, 0, 0),
new Vec3(1, 1, 0)
},
new Vec4[] {
new Vec4(color),
new Vec4(color),
new Vec4(color),
new Vec4(color)
},
new Vec2[] {
new Vec2(0, 0),
new Vec2(0, 1),
new Vec2(1, 0),
new Vec2(1, 1)
},
null,
ShortBuffer.wrap(new short[] {3, 1, 0, 2, 3, 0})
);
}
public static ShapePrototype unitSquare(Texture texture, Vec4 color) {
return unitSquare(texture, color, WorldRenderProgram.getDefault());
}
public static ShapePrototype unitSquare(Texture texture) {
return unitSquare(texture, Colors.WHITE, WorldRenderProgram.getDefault());
}
public static ShapePrototype unitSquare(Vec4 color) {
return unitSquare(null, color, WorldRenderProgram.getDefault());
}
public static ShapePrototype unitSquare() {
return unitSquare(null, Colors.WHITE, WorldRenderProgram.getDefault());
}
}

View File

@ -116,7 +116,7 @@ public class ShapeRenderProgram extends Program {
try {
enableAttributes();
for (ShapePartGroup group : shape.getGroups()) {
for (FaceGroup group : shape.getGroups()) {
renderFaceGroup(group);
}
} finally {
@ -182,7 +182,7 @@ public class ShapeRenderProgram extends Program {
indices.bind(BindTarget.ELEMENT_ARRAY);
}
protected void renderFaceGroup(ShapePartGroup group) {
protected void renderFaceGroup(FaceGroup group) {
TexturePrimitive texture = group.getTexture();
if (texture != null) {
@ -206,12 +206,12 @@ public class ShapeRenderProgram extends Program {
}
public void preprocess(Shape shape) {
for (ShapePart face : shape.getParts()) {
for (Face face : shape.getFaces()) {
applySprites(face);
}
}
private void applySprites(ShapePart face) {
private void applySprites(Face face) {
if (face.getTexture() == null)
return;

View File

@ -20,13 +20,11 @@ package ru.windcorp.progressia.client.graphics.model;
import java.util.Map;
import glm.mat._4.Mat4;
import glm.vec._3.Vec3;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.backend.Usage;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.world.rels.AbsFace;
import ru.windcorp.progressia.common.world.block.BlockFace;
public class Shapes {
@ -52,7 +50,7 @@ public class Shapes {
boolean flip
) {
ShapePart top = ShapeParts.createRectangle(
Face top = Faces.createRectangle(
program,
topTexture,
colorMultiplier,
@ -62,7 +60,7 @@ public class Shapes {
flip
);
ShapePart bottom = ShapeParts.createRectangle(
Face bottom = Faces.createRectangle(
program,
bottomTexture,
colorMultiplier,
@ -72,7 +70,7 @@ public class Shapes {
flip
);
ShapePart north = ShapeParts.createRectangle(
Face north = Faces.createRectangle(
program,
northTexture,
colorMultiplier,
@ -82,7 +80,7 @@ public class Shapes {
flip
);
ShapePart south = ShapeParts.createRectangle(
Face south = Faces.createRectangle(
program,
southTexture,
colorMultiplier,
@ -92,7 +90,7 @@ public class Shapes {
flip
);
ShapePart east = ShapeParts.createRectangle(
Face east = Faces.createRectangle(
program,
eastTexture,
colorMultiplier,
@ -102,7 +100,7 @@ public class Shapes {
flip
);
ShapePart west = ShapeParts.createRectangle(
Face west = Faces.createRectangle(
program,
westTexture,
colorMultiplier,
@ -167,16 +165,16 @@ public class Shapes {
public PppBuilder(
ShapeRenderProgram program,
Map<AbsFace, Texture> textureMap
Map<BlockFace, Texture> textureMap
) {
this(
program,
textureMap.get(AbsFace.POS_Z),
textureMap.get(AbsFace.NEG_Z),
textureMap.get(AbsFace.POS_X),
textureMap.get(AbsFace.NEG_X),
textureMap.get(AbsFace.NEG_Y),
textureMap.get(AbsFace.POS_Y)
textureMap.get(BlockFace.TOP),
textureMap.get(BlockFace.BOTTOM),
textureMap.get(BlockFace.NORTH),
textureMap.get(BlockFace.SOUTH),
textureMap.get(BlockFace.EAST),
textureMap.get(BlockFace.WEST)
);
}
@ -261,34 +259,6 @@ public class Shapes {
public PppBuilder setSize(float size) {
return this.setSize(size, size, size);
}
public PppBuilder centerAt(float x, float y, float z) {
origin.set(x, y, z);
origin.mul(2);
origin.sub(width);
origin.sub(height);
origin.sub(depth);
origin.div(2);
return this;
}
public PppBuilder apply(Mat4 transform) {
VectorUtil.applyMat4(origin, transform);
VectorUtil.rotateOnly(width, transform);
VectorUtil.rotateOnly(height, transform);
VectorUtil.rotateOnly(depth, transform);
return this;
}
public PppBuilder scale(float factor) {
origin.mul(factor);
width.mul(factor);
height.mul(factor);
depth.mul(factor);
return this;
}
public PppBuilder flip() {
this.flip = true;

View File

@ -163,7 +163,7 @@ public class Atlases {
}
}
private static final TextureSettings SETTINGS = new TextureSettings(false, false);
private static final TextureSettings SETTINGS = new TextureSettings(false);
private static final Map<Resource, Sprite> LOADED = new HashMap<>();
private static final Multimap<AtlasGroup, Atlas> ATLASES = MultimapBuilder.hashKeys().arrayListValues().build();

View File

@ -21,7 +21,7 @@ package ru.windcorp.progressia.client.graphics.texture;
import java.util.Map;
import glm.vec._2.Vec2;
import ru.windcorp.progressia.common.world.rels.AbsFace;
import ru.windcorp.progressia.common.world.block.BlockFace;
public class ComplexTexture {
@ -54,14 +54,14 @@ public class ComplexTexture {
);
}
public Map<AbsFace, Texture> getCuboidTextures(
public Map<BlockFace, Texture> getCuboidTextures(
int x,
int y,
int width,
int height,
int depth
) {
return AbsFace.mapToFaces(
return BlockFace.mapToFaces(
get(
x + depth + width,
y + height + depth,
@ -86,7 +86,7 @@ public class ComplexTexture {
);
}
public Map<AbsFace, Texture> getCuboidTextures(
public Map<BlockFace, Texture> getCuboidTextures(
int x,
int y,
int size

View File

@ -28,7 +28,7 @@ import ru.windcorp.progressia.common.util.crash.CrashReports;
public class SimpleTextures {
private static final TextureSettings SETTINGS = new TextureSettings(false, false);
private static final TextureSettings SETTINGS = new TextureSettings(false);
private static final Map<Resource, Texture> TEXTURES = new HashMap<>();

View File

@ -23,7 +23,7 @@ import static org.lwjgl.opengl.GL12.*;
import java.nio.ByteBuffer;
public class TextureData {
class TextureData {
private final ByteBuffer data;

View File

@ -28,7 +28,6 @@ import javax.imageio.ImageIO;
import ru.windcorp.progressia.common.resource.Resource;
import ru.windcorp.progressia.common.util.BinUtil;
import ru.windcorp.progressia.common.util.crash.CrashReports;
public class TextureLoader {
@ -41,17 +40,9 @@ public class TextureLoader {
int width = readResult.getWidth();
int height = readResult.getHeight();
int bufferWidth;
int bufferHeight;
if (settings.allocateExactBuffer()) {
bufferWidth = width;
bufferHeight = height;
} else {
bufferWidth = BinUtil.roundToGreaterPowerOf2(width);
bufferHeight = BinUtil.roundToGreaterPowerOf2(height);
}
int bufferWidth = BinUtil.roundToGreaterPowerOf2(width);
int bufferHeight = BinUtil.roundToGreaterPowerOf2(height);
WritableRaster raster = TextureUtil.createRaster(
bufferWidth,
@ -108,12 +99,7 @@ public class TextureLoader {
TextureSettings settings
)
throws IOException {
InputStream stream = resource.getInputStream();
if (stream == null) {
throw CrashReports.report(null, "Texture \"%s\" not found", resource.getName());
}
return loadPixels(stream, settings);
return loadPixels(resource.getInputStream(), settings);
}
private TextureLoader() {

View File

@ -21,19 +21,13 @@ package ru.windcorp.progressia.client.graphics.texture;
public class TextureSettings {
private final boolean isFiltered;
private final boolean allocateExactBuffer;
public TextureSettings(boolean isFiltered, boolean allocateExactBuffer) {
public TextureSettings(boolean isFiltered) {
this.isFiltered = isFiltered;
this.allocateExactBuffer = allocateExactBuffer;
}
public boolean isFiltered() {
return isFiltered;
}
public boolean allocateExactBuffer() {
return allocateExactBuffer;
}
}

View File

@ -29,9 +29,6 @@ import glm.mat._4.Mat4;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.world.Camera.Anchor.Mode;
import ru.windcorp.progressia.client.world.entity.NPedModel;
import ru.windcorp.progressia.common.util.Matrices;
import ru.windcorp.progressia.common.util.Vectors;
public class Camera {
@ -63,13 +60,13 @@ public class Camera {
}
}
Vec3 getCameraPosition(Vec3 output);
void getCameraPosition(Vec3 output);
Vec3 getCameraVelocity(Vec3 output);
void getCameraVelocity(Vec3 output);
Vec3 getLookingAt(Vec3 output);
Vec3 getUpVector(Vec3 output);
float getCameraYaw();
float getCameraPitch();
Collection<Mode> getCameraModes();
@ -87,11 +84,14 @@ public class Camera {
*/
private final Vec3 lastAnchorPosition = new Vec3();
private final Vec3 lastAnchorLookingAt = new Vec3();
private final Vec3 lastAnchorUpVector = new Vec3();
private float lastAnchorYaw;
private float lastAnchorPitch;
private final Mat4 lastCameraMatrix = new Mat4();
private final Vec3 lastAnchorLookingAt = new Vec3();
private final Vec3 lastAnchorUp = new Vec3();
{
invalidateCache();
}
@ -108,9 +108,6 @@ public class Camera {
*/
public void apply(WorldRenderHelper helper) {
if (NPedModel.flag) {
// System.out.println("Camera.apply()");
}
applyPerspective(helper);
rotateCoordinateSystem(helper);
@ -152,34 +149,26 @@ public class Camera {
}
private void applyDirection(WorldRenderHelper helper) {
anchor.getLookingAt(lastAnchorLookingAt);
anchor.getUpVector(lastAnchorUpVector);
float pitch = anchor.getCameraPitch();
float yaw = anchor.getCameraYaw();
lookAt(helper.pushViewTransform());
}
helper.pushViewTransform()
.rotateY(-pitch)
.rotateZ(-yaw);
private void lookAt(Mat4 result) {
Vec3 f = this.lastAnchorLookingAt;
Vec3 s = Vectors.grab3();
Vec3 u = Vectors.grab3();
f.cross(this.lastAnchorUpVector, s);
s.normalize();
s.cross(f, u);
Mat4 workspace = Matrices.grab4();
workspace.set(
+f.x, -s.x, +u.x, 0,
+f.y, -s.y, +u.y, 0,
+f.z, -s.z, +u.z, 0,
0, 0, 0, 1
this.lastAnchorYaw = yaw;
this.lastAnchorPitch = pitch;
this.lastAnchorLookingAt.set(
cos(pitch) * cos(yaw),
cos(pitch) * sin(yaw),
sin(pitch)
);
this.lastAnchorUp.set(
cos(pitch + PI_F / 2) * cos(yaw),
cos(pitch + PI_F / 2) * sin(yaw),
sin(pitch + PI_F / 2)
);
result.mul(workspace);
Matrices.release(workspace);
Vectors.release(s);
Vectors.release(u);
}
private void applyPosition(WorldRenderHelper helper) {
@ -258,6 +247,8 @@ public class Camera {
private void invalidateCache() {
this.lastAnchorPosition.set(Float.NaN);
this.lastAnchorYaw = Float.NaN;
this.lastAnchorPitch = Float.NaN;
this.lastCameraMatrix.set(
Float.NaN,
@ -279,7 +270,7 @@ public class Camera {
);
this.lastAnchorLookingAt.set(Float.NaN);
this.lastAnchorUpVector.set(Float.NaN);
this.lastAnchorUp.set(Float.NaN);
}
public Anchor.Mode getMode() {
@ -298,6 +289,14 @@ public class Camera {
return currentModeIndex;
}
public float getLastAnchorYaw() {
return lastAnchorYaw;
}
public float getLastAnchorPitch() {
return lastAnchorPitch;
}
public Vec3 getLastAnchorPosition() {
return lastAnchorPosition;
}
@ -311,7 +310,7 @@ public class Camera {
}
public Vec3 getLastAnchorUp() {
return lastAnchorUpVector;
return lastAnchorUp;
}
}

View File

@ -59,46 +59,29 @@ public class EntityAnchor implements Anchor {
}
@Override
public Vec3 getCameraPosition(Vec3 output) {
if (output == null) output = new Vec3();
public void getCameraPosition(Vec3 output) {
model.getViewPoint(output);
output.add(model.getPosition());
return output;
output.add(entity.getPosition());
}
@Override
public Vec3 getCameraVelocity(Vec3 output) {
if (output == null) output = new Vec3();
public void getCameraVelocity(Vec3 output) {
output.set(entity.getVelocity());
return output;
}
@Override
public Vec3 getLookingAt(Vec3 output) {
if (output == null) output = new Vec3();
model.getLookingAt(output);
return output;
public float getCameraYaw() {
return entity.getYaw();
}
@Override
public Vec3 getUpVector(Vec3 output) {
if (output == null) output = new Vec3();
model.getUpVector(output);
return output;
public float getCameraPitch() {
return entity.getPitch();
}
@Override
public Collection<Mode> getCameraModes() {
return modes;
}
public EntityData getEntity() {
return entity;
}
@Override
public String toString() {
return "Anchor for entity " + entity;
}
}

View File

@ -31,7 +31,7 @@ import ru.windcorp.progressia.client.comms.controls.InputBasedControls;
import ru.windcorp.progressia.client.graphics.Layer;
import ru.windcorp.progressia.client.graphics.backend.FaceCulling;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
import ru.windcorp.progressia.client.graphics.input.bus.Input;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
import ru.windcorp.progressia.client.graphics.model.Shapes.PppBuilder;
@ -41,11 +41,9 @@ import ru.windcorp.progressia.common.Units;
import ru.windcorp.progressia.common.collision.Collideable;
import ru.windcorp.progressia.common.collision.colliders.Collider;
import ru.windcorp.progressia.common.util.FloatMathUtil;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.GravityModel;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.test.CollisionModelRenderer;
import ru.windcorp.progressia.test.controls.TestPlayerControls;
import ru.windcorp.progressia.test.TestPlayerControls;
public class LayerWorld extends Layer {
@ -59,8 +57,6 @@ public class LayerWorld extends Layer {
super("World");
this.client = client;
this.inputBasedControls = new InputBasedControls(client);
setCursorPolicy(CursorPolicy.FORBID);
}
@Override
@ -76,8 +72,6 @@ public class LayerWorld extends Layer {
@Override
protected void doRender() {
client.getComms().processPackets();
Camera camera = client.getCamera();
if (camera.hasAnchor()) {
renderWorld();
@ -203,34 +197,27 @@ public class LayerWorld extends Layer {
entity.getVelocity().mul((float) Math.exp(-FRICTION_COEFF / entity.getCollisionMass() * tickLength));
}
private static final float MC_g = Units.get("32 m/s^2");
private static final float IRL_g = Units.get("9.8 m/s^2");
private void tmp_applyGravity(EntityData entity, float tickLength) {
GravityModel gm = ClientState.getInstance().getWorld().getData().getGravityModel();
Vec3 upVector = Vectors.grab3();
gm.getUp(entity.getPosition(), upVector);
entity.changeUpVector(upVector);
Vectors.release(upVector);
if (ClientState.getInstance().getLocalPlayer().getEntity() == entity && tmp_testControls.isFlying()) {
return;
}
if (entity.getId().equals("Test:NoclipCamera")) {
return;
}
Vec3 gravitationalAcceleration = Vectors.grab3();
gm.getGravity(entity.getPosition(), gravitationalAcceleration);
gravitationalAcceleration.mul(tickLength);
entity.getVelocity().add(gravitationalAcceleration);
Vectors.release(gravitationalAcceleration);
final float gravitationalAcceleration = tmp_testControls.useMinecraftGravity() ? MC_g : IRL_g;
entity.getVelocity().add(0, 0, -gravitationalAcceleration * tickLength);
}
@Override
public void handleInput(InputEvent event) {
if (!event.isConsumed()) {
inputBasedControls.handleInput(event);
protected void handleInput(Input input) {
if (input.isConsumed())
return;
tmp_testControls.handleInput(input);
if (!input.isConsumed()) {
inputBasedControls.handleInput(input);
}
}

View File

@ -23,13 +23,13 @@ import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.world.WorldRender;
import ru.windcorp.progressia.common.world.BlockRay;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.rels.AbsFace;
public class Selection {
private final Vec3i block = new Vec3i();
private AbsFace surface = null;
private BlockFace surface = null;
private final Vec2 pointOnSurface = new Vec2(0.5f, 0.5f);
private final Vec3 point = new Vec3();
@ -38,9 +38,10 @@ public class Selection {
private BlockRay ray = new BlockRay();
public void update(WorldRender world, EntityData player) {
Vec3 direction = new Vec3();
Vec3 start = new Vec3();
Vec3 direction = player.getLookingAt();
player.getLookingAtVector(direction);
world.getEntityRenderable(player).getViewPoint(start);
start.add(player.getPosition());
@ -70,7 +71,7 @@ public class Selection {
return exists ? point : null;
}
public AbsFace getSurface() {
public BlockFace getSurface() {
return exists ? surface : null;
}

View File

@ -33,7 +33,7 @@ import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.backend.VertexBufferObject;
import ru.windcorp.progressia.client.graphics.backend.shaders.attributes.*;
import ru.windcorp.progressia.client.graphics.backend.shaders.uniforms.*;
import ru.windcorp.progressia.client.graphics.model.ShapePart;
import ru.windcorp.progressia.client.graphics.model.Face;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
@ -138,12 +138,12 @@ public class WorldRenderProgram extends ShapeRenderProgram {
public void preprocess(Shape shape) {
super.preprocess(shape);
for (ShapePart face : shape.getParts()) {
for (Face face : shape.getFaces()) {
computeNormals(face);
}
}
private void computeNormals(ShapePart face) {
private void computeNormals(Face face) {
Vec3 a = Vectors.grab3();
Vec3 b = Vectors.grab3();
Vec3 c = Vectors.grab3();
@ -183,7 +183,7 @@ public class WorldRenderProgram extends ShapeRenderProgram {
normal.normalize();
}
private void loadVertexPosition(ShapePart face, int index, Vec3 result) {
private void loadVertexPosition(Face face, int index, Vec3 result) {
ByteBuffer vertices = face.getVertices();
int offset = vertices.position() + index * getBytesPerVertex();
@ -194,15 +194,11 @@ public class WorldRenderProgram extends ShapeRenderProgram {
);
}
private void saveVertexNormal(ShapePart face, int index, Vec3 normal) {
private void saveVertexNormal(Face face, int index, Vec3 normal) {
ByteBuffer vertices = face.getVertices();
int offset = vertices.position() + index * getBytesPerVertex() + (3 * Float.BYTES +
4 * Float.BYTES +
2 * Float.BYTES);
if (!Float.isNaN(vertices.getFloat(offset + 0 * Float.BYTES))) {
return; // normals are forced
}
vertices.putFloat(offset + 0 * Float.BYTES, normal.x);
vertices.putFloat(offset + 1 * Float.BYTES, normal.y);
@ -216,7 +212,7 @@ public class WorldRenderProgram extends ShapeRenderProgram {
return new WRPVertexBuilder();
}
public static class WRPVertexBuilder implements VertexBuilder {
private static class WRPVertexBuilder implements VertexBuilder {
// TODO Throw VertexBuilders out the window and rewrite completely.
// I want to _extend_ VBs, not re-implement them for children of SRP
@ -224,17 +220,11 @@ public class WorldRenderProgram extends ShapeRenderProgram {
final Vec3 position;
final Vec4 colorMultiplier;
final Vec2 textureCoords;
final Vec3 normals;
Vertex(Vec3 position, Vec4 colorMultiplier, Vec2 textureCoords) {
this(position, colorMultiplier, textureCoords, new Vec3(Float.NaN, Float.NaN, Float.NaN));
}
Vertex(Vec3 position, Vec4 colorMultiplier, Vec2 textureCoords, Vec3 normals) {
Vertex(Vec3 position, Vec4 colorMultiplier, Vec2 textureCoords) {
this.position = position;
this.colorMultiplier = colorMultiplier;
this.textureCoords = textureCoords;
this.normals = normals;
}
}
@ -301,24 +291,6 @@ public class WorldRenderProgram extends ShapeRenderProgram {
return this;
}
public VertexBuilder addVertex(
Vec3 position,
Vec4 colorMultiplier,
Vec2 textureCoords,
Vec3 normals
) {
vertices.add(
new Vertex(
new Vec3(position),
new Vec4(colorMultiplier),
new Vec2(textureCoords),
new Vec3(normals)
)
);
return this;
}
@Override
public ByteBuffer assemble() {
@ -337,9 +309,9 @@ public class WorldRenderProgram extends ShapeRenderProgram {
.putFloat(v.colorMultiplier.w)
.putFloat(v.textureCoords.x)
.putFloat(v.textureCoords.y)
.putFloat(v.normals.x)
.putFloat(v.normals.y)
.putFloat(v.normals.z);
.putFloat(Float.NaN)
.putFloat(Float.NaN)
.putFloat(Float.NaN);
}
result.flip();

View File

@ -72,12 +72,6 @@ public class Localizer {
public synchronized String getLanguage() {
return language;
}
public List<String> getLanguages() {
List<String> result = new ArrayList<>(langList.keySet());
result.sort(null);
return result;
}
public synchronized String getValue(String key) {
if (data == null) {

View File

@ -27,29 +27,25 @@ import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.client.world.block.BlockRender;
import ru.windcorp.progressia.client.world.block.BlockRenderRegistry;
import ru.windcorp.progressia.client.world.tile.TileRender;
import ru.windcorp.progressia.client.world.tile.TileRenderReference;
import ru.windcorp.progressia.client.world.tile.TileRenderRegistry;
import ru.windcorp.progressia.client.world.tile.TileRenderStack;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.TileDataReference;
import ru.windcorp.progressia.common.world.TileDataStack;
import ru.windcorp.progressia.common.world.generic.ChunkGenericRO;
import ru.windcorp.progressia.common.world.rels.AbsFace;
import ru.windcorp.progressia.common.world.rels.BlockFace;
import ru.windcorp.progressia.common.world.rels.RelFace;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.generic.GenericChunk;
import ru.windcorp.progressia.common.world.tile.TileDataStack;
public class ChunkRender
implements ChunkGenericRO<BlockRender, TileRender, TileRenderStack, TileRenderReference, ChunkRender> {
implements GenericChunk<ChunkRender, BlockRender, TileRender, TileRenderStack> {
private final WorldRender world;
private final DefaultChunkData data;
private final ChunkData data;
private final ChunkRenderModel model;
private final Map<TileDataStack, TileRenderStackImpl> tileRenderLists = Collections
.synchronizedMap(new WeakHashMap<>());
public ChunkRender(WorldRender world, DefaultChunkData data) {
public ChunkRender(WorldRender world, ChunkData data) {
this.world = world;
this.data = data;
this.model = new ChunkRenderModel(this);
@ -59,11 +55,6 @@ public class ChunkRender
public Vec3i getPosition() {
return getData().getPosition();
}
@Override
public AbsFace getUp() {
return getData().getUp();
}
@Override
public BlockRender getBlock(Vec3i posInChunk) {
@ -93,11 +84,11 @@ public class ChunkRender
return world;
}
public DefaultChunkData getData() {
public ChunkData getData() {
return data;
}
public void markForUpdate() {
public synchronized void markForUpdate() {
getWorld().markChunkForUpdate(getPosition());
}
@ -110,28 +101,6 @@ public class ChunkRender
}
private class TileRenderStackImpl extends TileRenderStack {
private class TileRenderReferenceImpl implements TileRenderReference {
private final TileDataReference parent;
public TileRenderReferenceImpl(TileDataReference parent) {
this.parent = parent;
}
@Override
public TileRender get() {
return TileRenderRegistry.getInstance().get(parent.get().getId());
}
@Override
public int getIndex() {
return parent.getIndex();
}
@Override
public TileRenderStack getStack() {
return TileRenderStackImpl.this;
}
}
private final TileDataStack parent;
@ -150,24 +119,9 @@ public class ChunkRender
}
@Override
public RelFace getFace() {
public BlockFace getFace() {
return parent.getFace();
}
@Override
public TileRenderReference getReference(int index) {
return new TileRenderReferenceImpl(parent.getReference(index));
}
@Override
public int getIndexByTag(int tag) {
return parent.getIndexByTag(tag);
}
@Override
public int getTagByIndex(int index) {
return parent.getTagByIndex(index);
}
@Override
public TileRender get(int index) {

View File

@ -35,10 +35,8 @@ 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;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.block.BlockFace;
public class ChunkRenderModel implements Renderable {
@ -55,15 +53,11 @@ public class ChunkRenderModel implements Renderable {
public void render(ShapeRenderHelper renderer) {
if (model == null) return;
float offset = DefaultChunkData.BLOCKS_PER_CHUNK / 2 - 0.5f;
renderer.pushTransform().translate(
chunk.getX() * 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);
chunk.getX() * ChunkData.BLOCKS_PER_CHUNK,
chunk.getY() * ChunkData.BLOCKS_PER_CHUNK,
chunk.getZ() * ChunkData.BLOCKS_PER_CHUNK
);
model.render(renderer);
@ -77,8 +71,8 @@ public class ChunkRenderModel implements Renderable {
optimizers.forEach(ChunkRenderOptimizer::startRender);
GenericChunks.forEachBiC(relBlockInChunk -> {
processBlockAndTiles(relBlockInChunk, sink);
chunk.forEachBiC(blockInChunk -> {
processBlockAndTiles(blockInChunk, sink);
});
for (ChunkRenderOptimizer optimizer : optimizers) {
@ -102,16 +96,16 @@ public class ChunkRenderModel implements Renderable {
}
}
private void processBlockAndTiles(Vec3i relBlockInChunk, Builder sink) {
processBlock(relBlockInChunk, sink);
private void processBlockAndTiles(Vec3i blockInChunk, Builder sink) {
processBlock(blockInChunk, sink);
for (RelFace face : RelFace.getFaces()) {
processTileStack(relBlockInChunk, face, sink);
for (BlockFace face : BlockFace.getFaces()) {
processTileStack(blockInChunk, face, sink);
}
}
private void processBlock(Vec3i relBlockInChunk, Builder sink) {
BlockRender block = chunk.getBlockRel(relBlockInChunk);
private void processBlock(Vec3i blockInChunk, Builder sink) {
BlockRender block = chunk.getBlock(blockInChunk);
if (block instanceof BlockRenderNone) {
return;
@ -119,48 +113,48 @@ public class ChunkRenderModel implements Renderable {
if (block.needsOwnRenderable()) {
sink.addPart(
block.createRenderable(chunk.getData(), relBlockInChunk),
new Mat4().identity().translate(relBlockInChunk.x, relBlockInChunk.y, relBlockInChunk.z)
block.createRenderable(chunk.getData(), blockInChunk),
new Mat4().identity().translate(blockInChunk.x, blockInChunk.y, blockInChunk.z)
);
}
processBlockWithCROs(block, relBlockInChunk);
processBlockWithCROs(block, blockInChunk);
}
private void processBlockWithCROs(BlockRender block, Vec3i relBlockInChunk) {
private void processBlockWithCROs(BlockRender block, Vec3i blockInChunk) {
for (ChunkRenderOptimizer optimizer : optimizers) {
optimizer.addBlock(block, relBlockInChunk);
optimizer.addBlock(block, blockInChunk);
}
}
private void processTileStack(Vec3i relBlockInChunk, RelFace face, Builder sink) {
TileRenderStack trs = chunk.getTilesOrNullRel(relBlockInChunk, face);
private void processTileStack(Vec3i blockInChunk, BlockFace face, Builder sink) {
TileRenderStack trs = chunk.getTilesOrNull(blockInChunk, face);
if (trs == null || trs.isEmpty()) {
return;
}
trs.forEach(tile -> processTile(tile, relBlockInChunk, face, sink));
trs.forEach(tile -> processTile(tile, blockInChunk, face, sink));
}
private void processTile(TileRender tile, Vec3i relBlockInChunk, RelFace face, Builder sink) {
private void processTile(TileRender tile, Vec3i blockInChunk, BlockFace face, Builder sink) {
if (tile instanceof TileRenderNone) {
return;
}
if (tile.needsOwnRenderable()) {
sink.addPart(
tile.createRenderable(chunk.getData(), relBlockInChunk, face),
new Mat4().identity().translate(relBlockInChunk.x, relBlockInChunk.y, relBlockInChunk.z)
tile.createRenderable(chunk.getData(), blockInChunk, face),
new Mat4().identity().translate(blockInChunk.x, blockInChunk.y, blockInChunk.z)
);
}
processTileWithCROs(tile, relBlockInChunk, face);
processTileWithCROs(tile, blockInChunk, face);
}
private void processTileWithCROs(TileRender tile, Vec3i relBlockInChunk, RelFace face) {
private void processTileWithCROs(TileRender tile, Vec3i blockInChunk, BlockFace face) {
for (ChunkRenderOptimizer optimizer : optimizers) {
optimizer.addTile(tile, relBlockInChunk, face);
optimizer.addTile(tile, blockInChunk, face);
}
}

View File

@ -21,11 +21,10 @@ package ru.windcorp.progressia.client.world;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.ChunkDataListener;
import ru.windcorp.progressia.common.world.block.BlockData;
import ru.windcorp.progressia.common.world.rels.AbsFace;
import ru.windcorp.progressia.common.world.rels.RelFace;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.tile.TileData;
class ChunkUpdateListener implements ChunkDataListener {
@ -37,14 +36,14 @@ class ChunkUpdateListener implements ChunkDataListener {
}
@Override
public void onChunkChanged(DefaultChunkData chunk) {
public void onChunkChanged(ChunkData chunk) {
world.getChunk(chunk).markForUpdate();
}
@Override
public void onChunkLoaded(DefaultChunkData chunk) {
public void onChunkLoaded(ChunkData chunk) {
Vec3i cursor = new Vec3i();
for (AbsFace face : AbsFace.getFaces()) {
for (BlockFace face : BlockFace.getFaces()) {
cursor.set(chunk.getX(), chunk.getY(), chunk.getZ());
cursor.add(face.getVector());
world.markChunkForUpdate(cursor);
@ -52,22 +51,22 @@ class ChunkUpdateListener implements ChunkDataListener {
}
@Override
public void onChunkBlockChanged(DefaultChunkData chunk, Vec3i blockInChunk, BlockData previous, BlockData current) {
public void onChunkBlockChanged(ChunkData chunk, Vec3i blockInChunk, BlockData previous, BlockData current) {
onLocationChanged(chunk, blockInChunk);
}
@Override
public void onChunkTilesChanged(
DefaultChunkData chunk,
ChunkData chunk,
Vec3i blockInChunk,
RelFace face,
BlockFace face,
TileData tile,
boolean wasAdded
) {
onLocationChanged(chunk, blockInChunk);
}
private void onLocationChanged(DefaultChunkData chunk, Vec3i blockInChunk) {
private void onLocationChanged(ChunkData chunk, Vec3i blockInChunk) {
Vec3i chunkPos = Vectors.grab3i().set(chunk.getX(), chunk.getY(), chunk.getZ());
checkCoordinate(blockInChunk, chunkPos, VectorUtil.Axis.X);
@ -83,7 +82,7 @@ class ChunkUpdateListener implements ChunkDataListener {
if (block == 0) {
diff = -1;
} else if (block == DefaultChunkData.BLOCKS_PER_CHUNK - 1) {
} else if (block == ChunkData.BLOCKS_PER_CHUNK - 1) {
diff = +1;
} else {
return;

View File

@ -34,58 +34,57 @@ import ru.windcorp.progressia.client.world.block.BlockRender;
import ru.windcorp.progressia.client.world.entity.EntityRenderRegistry;
import ru.windcorp.progressia.client.world.entity.EntityRenderable;
import ru.windcorp.progressia.client.world.tile.TileRender;
import ru.windcorp.progressia.client.world.tile.TileRenderReference;
import ru.windcorp.progressia.client.world.tile.TileRenderStack;
import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.ChunkDataListeners;
import ru.windcorp.progressia.common.world.DefaultWorldData;
import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.common.world.WorldDataListener;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.generic.ChunkSet;
import ru.windcorp.progressia.common.world.generic.ChunkSets;
import ru.windcorp.progressia.common.world.generic.WorldGenericRO;
import ru.windcorp.progressia.common.world.generic.GenericWorld;
public class WorldRender
implements WorldGenericRO<BlockRender, TileRender, TileRenderStack, TileRenderReference, ChunkRender, EntityRenderable> {
implements GenericWorld<BlockRender, TileRender, TileRenderStack, ChunkRender, EntityRenderable> {
private final DefaultWorldData data;
private final WorldData data;
private final Client client;
private final Map<DefaultChunkData, ChunkRender> chunks = Collections.synchronizedMap(new HashMap<>());
private final Map<ChunkData, ChunkRender> chunks = Collections.synchronizedMap(new HashMap<>());
private final Map<EntityData, EntityRenderable> entityModels = Collections.synchronizedMap(new WeakHashMap<>());
private final ChunkSet chunksToUpdate = ChunkSets.newSyncHashSet();
public WorldRender(DefaultWorldData data, Client client) {
public WorldRender(WorldData data, Client client) {
this.data = data;
this.client = client;
data.addListener(ChunkDataListeners.createAdder(new ChunkUpdateListener(this)));
data.addListener(new WorldDataListener() {
@Override
public void onChunkLoaded(DefaultWorldData world, DefaultChunkData chunk) {
public void onChunkLoaded(WorldData world, ChunkData chunk) {
addChunk(chunk);
}
@Override
public void beforeChunkUnloaded(DefaultWorldData world, DefaultChunkData chunk) {
public void beforeChunkUnloaded(WorldData world, ChunkData chunk) {
removeChunk(chunk);
}
});
}
protected void addChunk(DefaultChunkData chunk) {
protected void addChunk(ChunkData chunk) {
chunks.put(chunk, new ChunkRender(WorldRender.this, chunk));
markChunkForUpdate(chunk.getPosition());
}
protected void removeChunk(DefaultChunkData chunk) {
protected void removeChunk(ChunkData chunk) {
chunks.remove(chunk);
}
public DefaultWorldData getData() {
public WorldData getData() {
return data;
}
@ -93,7 +92,7 @@ public class WorldRender
return client;
}
public ChunkRender getChunk(DefaultChunkData chunkData) {
public ChunkRender getChunk(ChunkData chunkData) {
return chunks.get(chunkData);
}
@ -111,13 +110,6 @@ public class WorldRender
public Collection<EntityRenderable> getEntities() {
return entityModels.values();
}
@Override
public EntityRenderable getEntity(long entityId) {
EntityData entityData = getData().getEntity(entityId);
if (entityData == null) return null;
return getEntityRenderable(entityData);
}
public void render(ShapeRenderHelper renderer) {
updateChunks();

View File

@ -18,19 +18,26 @@
package ru.windcorp.progressia.client.world.block;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.generic.BlockGeneric;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.generic.GenericBlock;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.model.Renderable;
public abstract class BlockRender extends Namespaced implements BlockGeneric {
public abstract class BlockRender extends Namespaced implements GenericBlock {
public BlockRender(String id) {
super(id);
}
public Renderable createRenderable(DefaultChunkData chunk, Vec3i relBlockInChunk) {
public void render(ShapeRenderHelper renderer) {
throw new UnsupportedOperationException(
"BlockRender.render() not implemented in " + this
);
}
public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk) {
return null;
}

View File

@ -21,7 +21,7 @@ package ru.windcorp.progressia.client.world.block;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.model.EmptyModel;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.ChunkData;
public class BlockRenderNone extends BlockRender {
@ -30,7 +30,7 @@ public class BlockRenderNone extends BlockRender {
}
@Override
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk) {
public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk) {
return EmptyModel.getInstance();
}

View File

@ -19,7 +19,7 @@
package ru.windcorp.progressia.client.world.block;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.common.world.rels.RelFace;
import ru.windcorp.progressia.common.world.block.BlockFace;
public class BlockRenderOpaqueCube extends BlockRenderTexturedCube {
@ -29,8 +29,8 @@ public class BlockRenderOpaqueCube extends BlockRenderTexturedCube {
Texture bottomTexture,
Texture northTexture,
Texture southTexture,
Texture westTexture,
Texture eastTexture
Texture eastTexture,
Texture westTexture
) {
super(
id,
@ -38,8 +38,8 @@ public class BlockRenderOpaqueCube extends BlockRenderTexturedCube {
bottomTexture,
northTexture,
southTexture,
westTexture,
eastTexture
eastTexture,
westTexture
);
}
@ -54,21 +54,9 @@ public class BlockRenderOpaqueCube extends BlockRenderTexturedCube {
texture
);
}
public BlockRenderOpaqueCube(String id, Texture topTexture, Texture bottomTexture, Texture sideTexture) {
this(
id,
topTexture,
bottomTexture,
sideTexture,
sideTexture,
sideTexture,
sideTexture
);
}
@Override
public boolean isOpaque(RelFace face) {
public boolean isOpaque(BlockFace face) {
return true;
}

View File

@ -18,8 +18,9 @@
package ru.windcorp.progressia.client.world.block;
import static ru.windcorp.progressia.common.world.rels.AbsFace.*;
import static ru.windcorp.progressia.common.world.block.BlockFace.*;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
@ -28,23 +29,22 @@ 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.Face;
import ru.windcorp.progressia.client.graphics.model.Faces;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSurface.BlockOptimizedSurface;
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;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.block.BlockFace;
public abstract class BlockRenderTexturedCube
extends BlockRender
implements BlockOptimizedSurface {
private final Map<RelFace, Texture> textures;
private final Map<BlockFace, Texture> textures = new HashMap<>();
public BlockRenderTexturedCube(
String id,
@ -52,59 +52,65 @@ public abstract class BlockRenderTexturedCube
Texture bottomTexture,
Texture northTexture,
Texture southTexture,
Texture westTexture,
Texture eastTexture
Texture eastTexture,
Texture westTexture
) {
super(id);
this.textures = RelFace.mapToFaces(topTexture, bottomTexture, northTexture, southTexture, westTexture, eastTexture);
textures.put(TOP, topTexture);
textures.put(BOTTOM, bottomTexture);
textures.put(NORTH, northTexture);
textures.put(SOUTH, southTexture);
textures.put(EAST, eastTexture);
textures.put(WEST, westTexture);
}
public Texture getTexture(RelFace blockFace) {
public Texture getTexture(BlockFace blockFace) {
return textures.get(blockFace);
}
public Vec4 getColorMultiplier(RelFace blockFace) {
public Vec4 getColorMultiplier(BlockFace blockFace) {
return Colors.WHITE;
}
@Override
public final void getShapeParts(
DefaultChunkData chunk, Vec3i blockInChunk, RelFace blockFace,
public final void getFaces(
ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace,
boolean inner,
Consumer<ShapePart> output,
Consumer<Face> output,
Vec3 offset
) {
output.accept(createFace(chunk, blockInChunk, blockFace, inner, offset));
}
private ShapePart createFace(
DefaultChunkData chunk, Vec3i blockInChunk, RelFace blockFace,
private Face createFace(
ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace,
boolean inner,
Vec3 offset
) {
return ShapeParts.createBlockFace(
return Faces.createBlockFace(
WorldRenderProgram.getDefault(),
getTexture(blockFace),
getColorMultiplier(blockFace),
offset,
blockFace.resolve(AbsFace.POS_Z),
blockFace,
inner
);
}
@Override
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk) {
public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk) {
boolean opaque = isBlockOpaque();
ShapePart[] faces = new ShapePart[BLOCK_FACE_COUNT + (opaque ? BLOCK_FACE_COUNT : 0)];
Face[] faces = new Face[BLOCK_FACE_COUNT + (opaque ? BLOCK_FACE_COUNT : 0)];
for (int i = 0; i < BLOCK_FACE_COUNT; ++i) {
faces[i] = createFace(chunk, blockInChunk, RelFace.getFaces().get(i), false, Vectors.ZERO_3);
faces[i] = createFace(chunk, blockInChunk, BlockFace.getFaces().get(i), false, Vectors.ZERO_3);
}
if (!opaque) {
for (int i = 0; i < BLOCK_FACE_COUNT; ++i) {
faces[i + BLOCK_FACE_COUNT] = createFace(chunk, blockInChunk, RelFace.getFaces().get(i), true, Vectors.ZERO_3);
faces[i + BLOCK_FACE_COUNT] = createFace(chunk, blockInChunk, BlockFace.getFaces().get(i), true, Vectors.ZERO_3);
}
}

View File

@ -19,7 +19,7 @@
package ru.windcorp.progressia.client.world.block;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.common.world.rels.RelFace;
import ru.windcorp.progressia.common.world.block.BlockFace;
public class BlockRenderTransparentCube extends BlockRenderTexturedCube {
@ -29,8 +29,8 @@ public class BlockRenderTransparentCube extends BlockRenderTexturedCube {
Texture bottomTexture,
Texture northTexture,
Texture southTexture,
Texture westTexture,
Texture eastTexture
Texture eastTexture,
Texture westTexture
) {
super(
id,
@ -38,8 +38,8 @@ public class BlockRenderTransparentCube extends BlockRenderTexturedCube {
bottomTexture,
northTexture,
southTexture,
westTexture,
eastTexture
eastTexture,
westTexture
);
}
@ -54,21 +54,9 @@ public class BlockRenderTransparentCube extends BlockRenderTexturedCube {
texture
);
}
public BlockRenderTransparentCube(String id, Texture topTexture, Texture bottomTexture, Texture sideTexture) {
this(
id,
topTexture,
bottomTexture,
sideTexture,
sideTexture,
sideTexture,
sideTexture
);
}
@Override
public boolean isOpaque(RelFace face) {
public boolean isOpaque(BlockFace face) {
return false;
}

View File

@ -24,7 +24,7 @@ import ru.windcorp.progressia.client.world.ChunkRender;
import ru.windcorp.progressia.client.world.block.BlockRender;
import ru.windcorp.progressia.client.world.tile.TileRender;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import ru.windcorp.progressia.common.world.rels.RelFace;
import ru.windcorp.progressia.common.world.block.BlockFace;
/**
* Chunk render optimizer (CRO) is an object that produces optimized models for
@ -35,12 +35,6 @@ import ru.windcorp.progressia.common.world.rels.RelFace;
* 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
@ -50,7 +44,7 @@ import ru.windcorp.progressia.common.world.rels.RelFace;
* 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
* {@link #addTile(TileRender, Vec3i, BlockFace)} are invoked for each block and
* tile that this CRO should optimize. {@code addTile} specifies tiles in order
* of ascension within a tile stack.</li>
* <li>{@link #endRender()} is invoked. The CRO may perform any pending
@ -104,13 +98,12 @@ public abstract class ChunkRenderOptimizer extends Namespaced {
* method is only invoked once per block. This method is not necessarily
* invoked for each block.
*
* @param block a {@link BlockRender} instance describing the
* block.
* It corresponds to
* {@code getChunk().getBlock(blockInChunk)}.
* @param relBlockInChunk the relative position of the block
* @param block a {@link BlockRender} instance describing the block.
* It corresponds to
* {@code getChunk().getBlock(blockInChunk)}.
* @param blockInChunk the position of the block
*/
public abstract void addBlock(BlockRender block, Vec3i relBlockInChunk);
public abstract void addBlock(BlockRender block, Vec3i blockInChunk);
/**
* Requests that this CRO processes the provided tile. This method may only
@ -119,12 +112,11 @@ public abstract class ChunkRenderOptimizer extends Namespaced {
* 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
* @param tile a {@link BlockRender} instance describing the tile
* @param blockInChunk the position of the block that the tile belongs to
* @param blockFace the face that the tile belongs to
*/
public abstract void addTile(TileRender tile, Vec3i relBlockInChunk, RelFace blockFace);
public abstract void addTile(TileRender tile, Vec3i blockInChunk, BlockFace blockFace);
/**
* Requests that the CRO assembles and outputs its model. This method may

View File

@ -1,99 +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 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()])
);
}
}

View File

@ -18,9 +18,9 @@
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 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;
@ -29,7 +29,7 @@ import java.util.function.Consumer;
import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.backend.Usage;
import ru.windcorp.progressia.client.graphics.model.ShapePart;
import ru.windcorp.progressia.client.graphics.model.Face;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
@ -37,9 +37,8 @@ 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;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.block.BlockFace;
public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
@ -53,42 +52,39 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
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.
* Creates and outputs a set of faces that correspond to this surface.
* The coordinates of the face vertices must be in chunk coordinate
* system.
*
* @param chunk the chunk that contains the requested face
* @param 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
* @param chunk the chunk that contains the requested face
* @param blockInChunk the block in chunk
* @param blockFace the requested face
* @param inner whether this face should be visible from inside
* ({@code true}) or outside ({@code false})
* @param output a consumer that the created faces must be given
* to
* @param offset an additional offset that must be applied to all
* vertices
*/
void getShapeParts(
DefaultChunkData chunk,
Vec3i relBlockInChunk,
RelFace blockFace,
void getFaces(
ChunkData chunk,
Vec3i blockInChunk,
BlockFace blockFace,
boolean inner,
Consumer<ShapePart> output,
Consumer<Face> output,
Vec3 offset /* kostyl 156% */
);
/**
* Returns the opacity of the surface identified by the provided
* {@link RelFace}.
* {@link BlockFace}.
* Opaque surfaces prevent surfaces behind them from being included in
* chunk models.
*
* @param blockFace the face to query
* @return {@code true} iff the surface is opaque.
*/
boolean isOpaque(RelFace blockFace);
boolean isOpaque(BlockFace blockFace);
}
/**
@ -163,29 +159,29 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
}
@Override
public void addBlock(BlockRender block, Vec3i relBlockInChunk) {
public void addBlock(BlockRender block, Vec3i pos) {
if (!(block instanceof BlockOptimizedSurface))
return;
BlockOptimizedSurface bos = (BlockOptimizedSurface) block;
addBlock(relBlockInChunk, bos);
addBlock(pos, bos);
}
@Override
public void addTile(TileRender tile, Vec3i relBlockInChunk, RelFace face) {
public void addTile(TileRender tile, Vec3i pos, BlockFace face) {
if (!(tile instanceof TileOptimizedSurface))
return;
TileOptimizedSurface tos = (TileOptimizedSurface) tile;
addTile(relBlockInChunk, face, tos);
addTile(pos, face, tos);
}
private void addBlock(Vec3i relBlockInChunk, BlockOptimizedSurface block) {
getBlock(relBlockInChunk).block = block;
protected void addBlock(Vec3i pos, BlockOptimizedSurface block) {
getBlock(pos).block = block;
}
private void addTile(Vec3i relBlockInChunk, RelFace face, TileOptimizedSurface tile) {
FaceInfo faceInfo = getFace(relBlockInChunk, face);
private void addTile(Vec3i pos, BlockFace face, TileOptimizedSurface tile) {
FaceInfo faceInfo = getFace(pos, face);
int index = faceInfo.tileCount;
faceInfo.tileCount++;
@ -201,58 +197,63 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
}
}
protected BlockInfo getBlock(Vec3i relBlockInChunk) {
return data[relBlockInChunk.x][relBlockInChunk.y][relBlockInChunk.z];
protected BlockInfo getBlock(Vec3i cursor) {
return data[cursor.x][cursor.y][cursor.z];
}
protected FaceInfo getFace(Vec3i relBlockInChunk, RelFace face) {
return getBlock(relBlockInChunk).faces[face.getId()];
protected FaceInfo getFace(Vec3i cursor, BlockFace face) {
return getBlock(cursor).faces[face.getId()];
}
@Override
public Renderable endRender() {
Collection<ShapePart> shapeParts = new ArrayList<>(
Collection<Face> shapeFaces = new ArrayList<>(
BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * 3
);
Consumer<ShapePart> consumer = shapeParts::add;
Vec3i cursor = new Vec3i();
Consumer<Face> consumer = shapeFaces::add;
GenericChunks.forEachBiC(relBlockInChunk -> {
processInnerFaces(relBlockInChunk, consumer);
processOuterFaces(relBlockInChunk, consumer);
});
for (cursor.x = 0; cursor.x < BLOCKS_PER_CHUNK; ++cursor.x) {
for (cursor.y = 0; cursor.y < BLOCKS_PER_CHUNK; ++cursor.y) {
for (cursor.z = 0; cursor.z < BLOCKS_PER_CHUNK; ++cursor.z) {
processInnerFaces(cursor, consumer);
processOuterFaces(cursor, consumer);
}
}
}
if (shapeParts.isEmpty()) {
if (shapeFaces.isEmpty()) {
return null;
}
return new Shape(
Usage.STATIC,
WorldRenderProgram.getDefault(),
shapeParts.toArray(new ShapePart[shapeParts.size()])
shapeFaces.toArray(new Face[shapeFaces.size()])
);
}
private void processOuterFaces(
Vec3i relBlockInChunk,
Consumer<ShapePart> output
Vec3i blockInChunk,
Consumer<Face> output
) {
for (RelFace blockFace : RelFace.getFaces()) {
processOuterFace(relBlockInChunk, blockFace, output);
for (BlockFace blockFace : BlockFace.getFaces()) {
processOuterFace(blockInChunk, blockFace, output);
}
}
private void processOuterFace(Vec3i relBlockInChunk, RelFace blockFace, Consumer<ShapePart> output) {
if (!shouldRenderOuterFace(relBlockInChunk, blockFace))
private void processOuterFace(Vec3i blockInChunk, BlockFace blockFace, Consumer<Face> output) {
if (!shouldRenderOuterFace(blockInChunk, blockFace))
return;
FaceInfo info = getFace(relBlockInChunk, blockFace);
FaceInfo info = getFace(blockInChunk, 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);
Vec3 faceOrigin = new Vec3(blockInChunk.x, blockInChunk.y, blockInChunk.z);
Vec3 offset = new Vec3(blockFace.getFloatVector()).mul(OVERLAY_OFFSET);
for (
int layer = info.topOpaqueSurface;
@ -263,29 +264,32 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
if (surface == null)
continue; // layer may be BLOCK_LAYER, then block may be null
surface.getShapeParts(chunk.getData(), relBlockInChunk, blockFace, false, output, faceOrigin);
surface.getFaces(chunk.getData(), blockInChunk, 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 processInnerFaces(
Vec3i blockInChunk,
Consumer<Face> output
) {
for (BlockFace blockFace : BlockFace.getFaces()) {
processInnerFace(blockInChunk, blockFace, output);
}
}
private void processInnerFace(Vec3i relBlockInChunk, RelFace blockFace, Consumer<ShapePart> output) {
if (!shouldRenderInnerFace(relBlockInChunk, blockFace))
private void processInnerFace(Vec3i blockInChunk, BlockFace blockFace, Consumer<Face> output) {
if (!shouldRenderInnerFace(blockInChunk, blockFace))
return;
FaceInfo info = getFace(relBlockInChunk, blockFace);
FaceInfo info = getFace(blockInChunk, 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);
Vec3 faceOrigin = new Vec3(blockInChunk.x, blockInChunk.y, blockInChunk.z);
Vec3 offset = new Vec3(blockFace.getFloatVector()).mul(OVERLAY_OFFSET);
for (
int layer = FaceInfo.BLOCK_LAYER;
@ -296,35 +300,35 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
if (surface == null)
continue; // layer may be BLOCK_LAYER, then block may be null
surface.getShapeParts(chunk.getData(), relBlockInChunk, blockFace, true, output, faceOrigin);
surface.getFaces(chunk.getData(), blockInChunk, blockFace, true, output, faceOrigin);
faceOrigin.add(offset);
}
}
private boolean shouldRenderOuterFace(Vec3i relBlockInChunk, RelFace face) {
relBlockInChunk.add(face.getRelVector());
private boolean shouldRenderOuterFace(Vec3i blockInChunk, BlockFace face) {
blockInChunk.add(face.getVector());
try {
return shouldRenderWhenFacing(relBlockInChunk, face);
return shouldRenderWhenFacing(blockInChunk, face);
} finally {
relBlockInChunk.sub(face.getRelVector());
blockInChunk.sub(face.getVector());
}
}
private boolean shouldRenderInnerFace(Vec3i relBlockInChunk, RelFace face) {
return shouldRenderWhenFacing(relBlockInChunk, face);
private boolean shouldRenderInnerFace(Vec3i blockInChunk, BlockFace face) {
return shouldRenderWhenFacing(blockInChunk, face);
}
private boolean shouldRenderWhenFacing(Vec3i relBlockInChunk, RelFace face) {
if (GenericChunks.containsBiC(relBlockInChunk)) {
return shouldRenderWhenFacingLocal(relBlockInChunk, face);
private boolean shouldRenderWhenFacing(Vec3i blockInChunk, BlockFace face) {
if (chunk.containsBiC(blockInChunk)) {
return shouldRenderWhenFacingLocal(blockInChunk, face);
} else {
return shouldRenderWhenFacingNeighbor(relBlockInChunk, face);
return shouldRenderWhenFacingNeighbor(blockInChunk, face);
}
}
private boolean shouldRenderWhenFacingLocal(Vec3i relBlockInChunk, RelFace face) {
BlockOptimizedSurface block = getBlock(relBlockInChunk).block;
private boolean shouldRenderWhenFacingLocal(Vec3i blockInChunk, BlockFace face) {
BlockOptimizedSurface block = getBlock(blockInChunk).block;
if (block == null) {
return true;
@ -336,37 +340,36 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
return true;
}
private boolean shouldRenderWhenFacingNeighbor(Vec3i relBlockInLocalChunk, RelFace face) {
Vec3i blockInChunk = Vectors.grab3i();
chunk.resolve(relBlockInLocalChunk, blockInChunk);
private boolean shouldRenderWhenFacingNeighbor(Vec3i blockInLocalChunk, BlockFace face) {
Vec3i blockInChunk = Vectors.grab3i().set(blockInLocalChunk.x, blockInLocalChunk.y, blockInLocalChunk.z);
Vec3i chunkPos = Vectors.grab3i().set(chunk.getX(), chunk.getY(), chunk.getZ());
try {
// Determine blockInChunk and chunkPos
if (blockInChunk.x == -1) {
if (blockInLocalChunk.x == -1) {
blockInChunk.x = BLOCKS_PER_CHUNK - 1;
chunkPos.x -= 1;
} else if (blockInChunk.x == BLOCKS_PER_CHUNK) {
} else if (blockInLocalChunk.x == BLOCKS_PER_CHUNK) {
blockInChunk.x = 0;
chunkPos.x += 1;
} else if (blockInChunk.y == -1) {
} else if (blockInLocalChunk.y == -1) {
blockInChunk.y = BLOCKS_PER_CHUNK - 1;
chunkPos.y -= 1;
} else if (blockInChunk.y == BLOCKS_PER_CHUNK) {
} else if (blockInLocalChunk.y == BLOCKS_PER_CHUNK) {
blockInChunk.y = 0;
chunkPos.y += 1;
} else if (blockInChunk.z == -1) {
} else if (blockInLocalChunk.z == -1) {
blockInChunk.z = BLOCKS_PER_CHUNK - 1;
chunkPos.z -= 1;
} else if (blockInChunk.z == BLOCKS_PER_CHUNK) {
} else if (blockInLocalChunk.z == BLOCKS_PER_CHUNK) {
blockInChunk.z = 0;
chunkPos.z += 1;
} else {
throw new AssertionError(
"Requested incorrent neighbor ("
+ relBlockInLocalChunk.x + "; "
+ relBlockInLocalChunk.y + "; "
+ relBlockInLocalChunk.z + ")"
+ blockInLocalChunk.x + "; "
+ blockInLocalChunk.y + "; "
+ blockInLocalChunk.z + ")"
);
}
@ -379,11 +382,8 @@ public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
return true;
BlockOptimizedSurface bos = (BlockOptimizedSurface) block;
RelFace rotatedFace = face.rotate(this.chunk.getUp(), chunk.getUp());
if (!bos.isOpaque(rotatedFace)) {
if (!bos.isOpaque(face))
return true;
}
return false;

View File

@ -42,7 +42,7 @@ public class EntityRenderRegistry extends NamespacedInstanceRegistry<EntityRende
ResourceManager.getTextureResource(
"entities/" + name
),
new TextureSettings(false, false)
new TextureSettings(false)
).getData()
);
} catch (IOException e) {

View File

@ -15,49 +15,22 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.world.entity;
import 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.ShapeRenderHelper;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.generic.EntityGeneric;
import ru.windcorp.progressia.common.world.generic.GenericEntity;
public abstract class EntityRenderable implements Renderable, EntityGeneric {
public abstract class EntityRenderable implements Renderable, GenericEntity {
private final EntityData data;
private long stateComputedForFrame = -1;
public EntityRenderable(EntityData 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() {
return data;
}
@ -71,42 +44,8 @@ public abstract class EntityRenderable implements Renderable, EntityGeneric {
public String getId() {
return getData().getId();
}
@Override
public long getEntityId() {
return getData().getEntityId();
}
public final Vec3 getLookingAt(Vec3 output) {
if (output == null) output = new Vec3();
updateIfNecessary();
doGetLookingAt(output);
return output;
}
protected void doGetLookingAt(Vec3 output) {
output.set(getData().getLookingAt());
}
public final Vec3 getUpVector(Vec3 output) {
if (output == null) output = new Vec3();
updateIfNecessary();
doGetUpVector(output);
return output;
}
protected void doGetUpVector(Vec3 output) {
output.set(getData().getUpVector());
}
public final Vec3 getViewPoint(Vec3 output) {
if (output == null) output = new Vec3();
updateIfNecessary();
doGetViewPoint(output);
return output;
}
protected void doGetViewPoint(Vec3 output) {
public void getViewPoint(Vec3 output) {
output.set(0, 0, 0);
}

View File

@ -43,7 +43,6 @@ public class HumanoidModel extends NPedModel {
@Override
protected void applyTransform(Mat4 mat, NPedModel model) {
super.applyTransform(mat, model);
float phase = model.getWalkingFrequency() * model.getWalkingParameter() + animationOffset;
float value = sin(phase);
float amplitude = getSwingAmplitude((HumanoidModel) model) * model.getVelocityParameter();

View File

@ -18,8 +18,11 @@
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.toRadians;
import static ru.windcorp.progressia.common.util.FloatMathUtil.normalizeAngle;
import glm.Glm;
import glm.mat._4.Mat4;
@ -29,9 +32,6 @@ import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
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;
public abstract class NPedModel extends EntityRenderable {
@ -51,30 +51,29 @@ public abstract class NPedModel extends EntityRenderable {
ShapeRenderHelper renderer,
NPedModel model
) {
renderer.pushTransform().translate(translation);
applyTransform(renderer.pushTransform(), model);
renderable.render(renderer);
renderer.popTransform();
renderer.popTransform();
}
protected void applyTransform(Mat4 mat, NPedModel model) {
mat.translate(getTranslation());
}
protected abstract void applyTransform(Mat4 mat, NPedModel model);
public Vec3 getTranslation() {
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 Body(Renderable renderable) {
super(renderable, null);
}
@Override
protected void applyTransform(Mat4 mat, NPedModel model) {
// Do nothing
}
}
public static class Head extends BodyPart {
@ -98,8 +97,7 @@ public abstract class NPedModel extends EntityRenderable {
@Override
protected void applyTransform(Mat4 mat, NPedModel model) {
super.applyTransform(mat, model);
mat.rotateZ(-model.getHeadYaw()).rotateY(-model.getHeadPitch());
mat.rotateZ(model.getHeadYaw()).rotateY(model.getHeadPitch());
}
public Vec3 getViewPoint() {
@ -107,8 +105,6 @@ public abstract class NPedModel extends EntityRenderable {
}
}
public static boolean flag;
protected final Body body;
protected final Head head;
@ -132,9 +128,7 @@ public abstract class NPedModel extends EntityRenderable {
private float walkingFrequency;
private final Vec3 bodyLookingAt = new Vec3().set(0);
private final Mat4 bodyTransform = new Mat4();
private float bodyYaw = Float.NaN;
private float headYaw;
private float headPitch;
@ -144,121 +138,68 @@ public abstract class NPedModel extends EntityRenderable {
this.head = head;
this.scale = scale;
computeRotations();
evaluateAngles();
}
@Override
protected void doRender(ShapeRenderHelper renderer) {
renderer.pushTransform().scale(scale).mul(bodyTransform);
public void render(ShapeRenderHelper renderer) {
renderer.pushTransform().scale(scale).rotateZ(bodyYaw);
renderBodyParts(renderer);
renderer.popTransform();
accountForVelocity();
evaluateAngles();
}
protected void renderBodyParts(ShapeRenderHelper renderer) {
body.render(renderer, this);
head.render(renderer, this);
}
@Override
protected void update() {
advanceTime();
computeRotations();
}
private void computeRotations() {
if (!bodyLookingAt.any()) {
getData().getForwardVector(bodyLookingAt);
private void evaluateAngles() {
float globalYaw = normalizeAngle(getData().getYaw());
if (Float.isNaN(bodyYaw)) {
bodyYaw = globalYaw;
headYaw = 0;
} else {
ensureBodyLookingAtIsPerpendicularToUpVector();
computeDesiredHeadYaw();
clampHeadYawAndChangeBodyLookingAt();
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;
}
}
recomputeBodyTransform();
setHeadPitch();
}
bodyYaw = normalizeAngle(bodyYaw);
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
headPitch = Glm.clamp(
getData().getPitch(),
-head.maxPitch,
head.maxPitch
);
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());
private void accountForVelocity() {
Vec3 horizontal = new Vec3(getData().getVelocity());
horizontal.z = 0;
velocity = horizontal.length();
computeVelocityParameter();
evaluateVelocityCoeff();
// TODO switch to world time
walkingParameter += velocity * GraphicsInterface.getFrameLength() * 1000;
rotateBodyWithMovement(horizontal);
bodyYaw += velocityParameter * normalizeAngle(
(float) (atan2(horizontal.y, horizontal.x) - bodyYaw)
) * min(1, GraphicsInterface.getFrameLength() * 10);
}
private void computeVelocityParameter() {
private void evaluateVelocityCoeff() {
if (velocity > maxEffectiveVelocity) {
velocityParameter = 1;
} else {
@ -266,39 +207,17 @@ 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
protected void doGetViewPoint(Vec3 output) {
public void getViewPoint(Vec3 output) {
Mat4 m = new Mat4();
Vec4 v = new Vec4();
m.identity()
.scale(scale)
.mul(bodyTransform);
head.getTransform(m, this);
.rotateZ(bodyYaw)
.translate(head.getTranslation())
.rotateZ(headYaw)
.rotateY(headPitch);
v.set(head.getViewPoint(), 1);
m.mul(v);
@ -313,9 +232,9 @@ public abstract class NPedModel extends EntityRenderable {
public Head getHead() {
return head;
}
public Vec3 getBodyLookingAt() {
return bodyLookingAt;
public float getBodyYaw() {
return bodyYaw;
}
public float getHeadYaw() {

Some files were not shown because too many files have changed in this diff Show More